public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
Search Repo:
* Add threaded option to run server in threaded mode, calling the 
application in a
  thread allowing for concurrency in the Rack adapter, closes #46
macournoyer (author)
Fri Mar 28 20:37:58 -0700 2008
commit  4d3709fa0c2555fd63c91aff3ded50bd4774f6b2
tree    e587db69a3ee6471a2210782b72a017d59f166ce
parent  1e7d9b9926bb8fecf42ac06987f9aae739952d3a
...
1
 
 
2
3
4
...
1
2
3
4
5
6
0
@@ -1,4 +1,6 @@
0
 == 0.8.0 ??? release
0
+ * Add threaded option to run server in threaded mode, calling the application in a
0
+ thread allowing for concurrency in the Rack adapter, closes #46
0
  * Guess which adapter to use from directory (chdir option)
0
    or use specified one in 'adapter' option, re #47.
0
 
...
23
24
25
 
 
 
 
26
27
28
...
110
111
112
 
113
114
115
...
23
24
25
26
27
28
29
30
31
32
...
114
115
116
117
118
119
120
0
@@ -23,6 +23,10 @@ module Thin
0
       # Maximum number of connections that can be persistent
0
       attr_accessor :maximum_persistent_connections
0
       
0
+ # Allow using threads in the backend.
0
+ attr_writer :threaded
0
+ def threaded?; @threaded end
0
+
0
       # Number of persistent connections currently opened
0
       attr_accessor :persistent_connection_count
0
       
0
@@ -110,6 +114,7 @@ module Thin
0
           connection.backend = self
0
           connection.app = @server.app
0
           connection.comm_inactivity_timeout = @timeout
0
+ connection.threaded = @threaded
0
 
0
           # We control the number of persistent connections by keeping
0
           # a count of the total one allowed yet.
...
19
20
21
 
 
 
 
22
23
24
...
38
39
40
 
 
 
 
 
 
 
 
41
42
43
44
45
 
 
 
 
 
 
 
 
 
 
 
46
47
48
...
57
58
59
 
 
 
 
 
 
60
61
62
63
 
 
 
64
65
66
...
19
20
21
22
23
24
25
26
27
28
...
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
 
57
58
59
60
61
62
63
64
65
66
67
68
69
70
...
79
80
81
82
83
84
85
86
87
88
89
90
 
91
92
93
94
95
96
0
@@ -19,6 +19,10 @@ module Thin
0
     # Next response sent through the connection
0
     attr_accessor :response
0
     
0
+ # Calling the application in a threaded allowing
0
+ # concurrent processing of requests.
0
+ attr_accessor :threaded
0
+
0
     # Get the connection ready to process a request.
0
     def post_init
0
       @request = Request.new
0
@@ -38,11 +42,29 @@ module Thin
0
     # Called when all data was received and the request
0
     # is ready to be processed.
0
     def process
0
+ if @threaded
0
+ EventMachine.defer(method(:pre_process), method(:post_process))
0
+ else
0
+ post_process(pre_process)
0
+ end
0
+ end
0
+
0
+ def pre_process
0
       # Add client info to the request env
0
       @request.remote_address = remote_address
0
       
0
       # Process the request calling the Rack adapter
0
- @response.status, @response.headers, @response.body = @app.call(@request.env)
0
+ @app.call(@request.env)
0
+ rescue
0
+ handle_error
0
+ terminate_request
0
+ nil # Signal to post_process that the request could not be processed
0
+ end
0
+
0
+ def post_process(result)
0
+ return unless result
0
+
0
+ @response.status, @response.headers, @response.body = result
0
       
0
       # Make the response persistent if requested by the client
0
       @response.persistent! if @request.persistent?
0
@@ -57,10 +79,18 @@ module Thin
0
       close_connection_after_writing unless persistent?
0
       
0
     rescue
0
+ handle_error
0
+ ensure
0
+ terminate_request
0
+ end
0
+
0
+ def handle_error
0
       log "!! Unexpected error while processing request: #{$!.message}"
0
       log_error
0
       close_connection rescue nil
0
- ensure
0
+ end
0
+
0
+ def terminate_request
0
       @request.close rescue nil
0
       @response.close rescue nil
0
       
...
51
52
53
 
54
55
56
...
51
52
53
54
55
56
57
0
@@ -51,6 +51,7 @@ module Thin
0
         server.timeout = @options[:timeout]
0
         server.maximum_connections = @options[:max_conns]
0
         server.maximum_persistent_connections = @options[:max_persistent_conns]
0
+ server.threaded = @options[:threaded]
0
 
0
         # Detach the process, after this line the current process returns
0
         server.daemonize if @options[:daemonize]
...
107
108
109
 
110
111
112
...
107
108
109
110
111
112
113
0
@@ -107,6 +107,7 @@ module Thin
0
         opts.on( "--max-persistent-conns NUM",
0
                                        "Maximum number of persistent connections",
0
                                        "(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
0
+ opts.on( "--threaded", "Call the Rack application in threads") { @options[:threaded] = true }
0
         
0
         opts.separator ""
0
         opts.separator "Common options:"
...
64
65
66
67
 
68
69
70
 
71
72
73
74
75
76
 
 
 
 
77
78
79
 
80
81
82
 
83
84
85
...
129
130
131
132
 
133
134
135
136
 
137
138
139
...
64
65
66
 
67
68
69
 
70
71
72
73
74
75
 
76
77
78
79
80
81
 
82
83
84
 
85
86
87
88
...
132
133
134
 
135
136
137
138
139
140
141
142
143
0
@@ -64,22 +64,25 @@ module Thin
0
     
0
     # Maximum number of seconds for incoming data to arrive before the connection
0
     # is dropped.
0
- def_delegators :@backend, :timeout, :timeout=
0
+ def_delegators :backend, :timeout, :timeout=
0
     
0
     # Maximum number of file or socket descriptors that the server may open.
0
- def_delegators :@backend, :maximum_connections, :maximum_connections=
0
+ def_delegators :backend, :maximum_connections, :maximum_connections=
0
     
0
     # Maximum number of connection that can be persistent at the same time.
0
     # Most browser never close the connection so most of the time they are closed
0
     # when the timeout occur. If we don't control the number of persistent connection,
0
     # if would be very easy to overflow the server for a DoS attack.
0
- def_delegators :@backend, :maximum_persistent_connections, :maximum_persistent_connections=
0
+ def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
0
+
0
+ # Allow using threads in the backend.
0
+ def_delegators :backend, :threaded?, :threaded=
0
     
0
     # Address and port on which the server is listening for connections.
0
- def_delegators :@backend, :host, :port
0
+ def_delegators :backend, :host, :port
0
     
0
     # UNIX domain socket on which the server is listening for connections.
0
- def_delegator :@backend, :socket
0
+ def_delegator :backend, :socket
0
     
0
     def initialize(host_or_socket_or_backend, port=DEFAULT_PORT, app=nil, &block)
0
       # Try to intelligently select which backend to use.
0
@@ -129,11 +132,12 @@ module Thin
0
       raise ArgumentError, 'app required' unless @app
0
       
0
       setup_signals
0
-
0
+
0
       log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
0
       debug ">> Debugging ON"
0
       trace ">> Tracing ON"
0
       
0
+ log ">> Threaded mode #{@backend.threaded? ? 'ON' : 'OFF'}"
0
       log ">> Maximum connections set to #{@backend.maximum_connections}"
0
       log ">> Listening on #{@backend}, CTRL+C to stop"
0
       
...
68
69
70
 
 
 
 
 
 
 
71
72
73
...
68
69
70
71
72
73
74
75
76
77
78
79
80
0
@@ -68,6 +68,13 @@ describe Controller, 'start' do
0
     
0
     @server.app.class.should == Proc
0
   end
0
+
0
+ it "should set server as threaded" do
0
+ @controller.options[:threaded] = true
0
+ @controller.start
0
+
0
+ @server.threaded.should be_true
0
+ end
0
 end
0
 
0
 describe Controller do
...
10
11
12
13
 
14
15
16
...
10
11
12
 
13
14
15
16
0
@@ -10,7 +10,7 @@ else
0
       end
0
       wait_for_socket('0.0.0.0', 3333)
0
       sleep 2 # HACK ooh boy, I wish I knew how to make those specs more stable...
0
- start_server(Backends::SwiftiplyClient.new('0.0.0.0', 5555, nil), nil, false) do |env|
0
+ start_server(Backends::SwiftiplyClient.new('0.0.0.0', 5555, nil), nil, :wait_for_socket => false) do |env|
0
         body = env.inspect + env['rack.input'].read
0
         [200, { 'Content-Type' => 'text/html', 'Content-Length' => body.size.to_s }, body]
0
       end
...
16
17
18
 
 
 
 
 
 
 
 
 
19
20
...
16
17
18
19
20
21
22
23
24
25
26
27
28
29
0
@@ -16,4 +16,13 @@ describe Server do
0
     @server.config
0
     @server.maximum_connections.should < 100_000
0
   end
0
+
0
+ it "should default to non-threaded" do
0
+ @server.should_not be_threaded
0
+ end
0
+
0
+ it "should set backend to threaded" do
0
+ @server.threaded = true
0
+ @server.backend.should be_threaded
0
+ end
0
 end
0
\ No newline at end of file
...
14
15
16
17
 
 
 
 
 
18
19
20
...
138
139
140
141
 
142
 
143
144
145
146
 
147
148
149
...
157
158
159
160
 
161
162
163
...
14
15
16
 
17
18
19
20
21
22
23
24
...
142
143
144
 
145
146
147
148
149
150
 
151
152
153
154
...
162
163
164
 
165
166
167
168
0
@@ -14,7 +14,11 @@ FileUtils.mkdir_p File.dirname(__FILE__) + '/../log'
0
 Command.script = File.dirname(__FILE__) + '/../bin/thin'
0
 Logging.silent = true
0
 
0
-SWIFTIPLY_PATH = `which swiftiply`.chomp unless Object.const_defined?(:SWIFTIPLY_PATH)
0
+unless Object.const_defined?(:SWIFTIPLY_PATH)
0
+ SWIFTIPLY_PATH = `which swiftiply`.chomp
0
+ DEFAULT_TEST_ADDRESS = '0.0.0.0'
0
+ DEFAULT_TEST_PORT = 3333
0
+end
0
 
0
 module Matchers
0
   class BeFasterThen
0
@@ -138,12 +142,13 @@ module Helpers
0
     request
0
   end
0
   
0
- def start_server(address='0.0.0.0', port=3333, wait_for_socket=true, &app)
0
+ def start_server(address=DEFAULT_TEST_ADDRESS, port=DEFAULT_TEST_PORT, options={}, &app)
0
     @server = Thin::Server.new(address, port, app)
0
+ @server.threaded = options[:threaded]
0
     @server.timeout = 3
0
     
0
     @thread = Thread.new { @server.start }
0
- if wait_for_socket
0
+ if options[:wait_for_socket]
0
       wait_for_socket(address, port)
0
     else
0
       # If we can't ping the address fallback to just wait for the server to run
0
@@ -157,7 +162,7 @@ module Helpers
0
     raise "Reactor still running, wtf?" if EventMachine.reactor_running?
0
   end
0
   
0
- def wait_for_socket(address='0.0.0.0', port=3333, timeout=5)
0
+ def wait_for_socket(address=DEFAULT_TEST_ADDRESS, port=DEFAULT_TEST_PORT, timeout=5)
0
     Timeout.timeout(timeout) do
0
       loop do
0
         begin
...
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
23
24
 
 
 
 
 
25
26
27
28
29
 
30
31
32
...
7
8
9
 
 
 
 
 
 
 
 
 
 
 
 
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
30
31
32
33
34
35
36
 
 
 
37
38
39
40
0
@@ -7,26 +7,34 @@ if RUBY_1_9 # RSpec not yet working w/ Ruby 1.9
0
 else
0
   require 'spec/rake/spectask'
0
   
0
- desc "Run all examples"
0
- Spec::Rake::SpecTask.new(:spec) do |t|
0
- t.spec_opts = %w(-fs -c)
0
- t.spec_files = FileList['spec/**/*_spec.rb'] - FileList['spec/perf/*_spec.rb']
0
- if WIN
0
- t.spec_files -= [
0
- 'spec/backends/unix_server_spec.rb',
0
- 'spec/controllers/service_spec.rb',
0
- 'spec/daemonizing_spec.rb',
0
- 'spec/server/unix_socket_spec.rb',
0
- 'spec/server/swiftiply_spec.rb'
0
- ]
0
+ PERF_SPECS = FileList['spec/perf/*_spec.rb']
0
+ WIN_SPECS = %w(
0
+ spec/backends/unix_server_spec.rb
0
+ spec/controllers/service_spec.rb
0
+ spec/daemonizing_spec.rb
0
+ spec/server/unix_socket_spec.rb
0
+ spec/server/swiftiply_spec.rb
0
+ )
0
+ # HACK Event machine causes some problems when running multiple
0
+ # tests in the same VM so we split the specs in 2 before I find
0
+ # a better solution...
0
+ SPECS2 = %w(spec/server/threaded_spec.rb spec/server/tcp_spec.rb)
0
+ SPECS = FileList['spec/**/*_spec.rb'] - PERF_SPECS - SPECS2
0
+
0
+ def spec_task(name, specs)
0
+ Spec::Rake::SpecTask.new(name) do |t|
0
+ t.spec_opts = %w(-fs -c)
0
+ t.spec_files = specs
0
     end
0
   end
0
- task :spec => :compile
0
+
0
+ desc "Run all examples"
0
+ spec_task :spec, SPECS
0
+ spec_task :spec2, SPECS2
0
+ task :spec => [:compile, :spec2]
0
   
0
   desc "Run all performance examples"
0
- Spec::Rake::SpecTask.new('spec:perf') do |t|
0
- t.spec_files = FileList['spec/perf/*_spec.rb']
0
- end
0
+ spec_task 'spec:perf', PERF_SPECS
0
   
0
   task :check_benchmark_unit_gem do
0
     begin

Comments

    No one has commented yet.