rainbows 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/rainbows/rev.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rainbows/rev/heartbeat'
3
- require 'rainbows/ev_core'
2
+ require 'rainbows/rev/core'
3
+ require 'rainbows/rev/client'
4
+ require 'rainbows/rev/deferred_response'
4
5
 
5
6
  module Rainbows
6
7
 
@@ -23,161 +24,6 @@ module Rainbows
23
24
  # temporary file before the application is entered.
24
25
 
25
26
  module Rev
26
-
27
- include Base
28
-
29
- class Client < ::Rev::IO
30
- include Rainbows::EvCore
31
- G = Rainbows::G
32
-
33
- def initialize(io)
34
- G.cur += 1
35
- super(io)
36
- post_init
37
- @deferred_bodies = [] # for (fast) regular files only
38
- end
39
-
40
- # queued, optional response bodies, it should only be unpollable "fast"
41
- # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
42
- # are also part of this. We'll also stick DeferredResponse bodies in
43
- # here to prevent connections from being closed on us.
44
- def defer_body(io)
45
- @deferred_bodies << io
46
- on_write_complete unless @hp.headers? # triggers a write
47
- end
48
-
49
- def app_call
50
- begin
51
- (@env[RACK_INPUT] = @input).rewind
52
- alive = @hp.keepalive?
53
- @env[REMOTE_ADDR] = @remote_addr
54
- response = G.app.call(@env.update(RACK_DEFAULTS))
55
- alive &&= G.alive
56
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
57
-
58
- DeferredResponse.write(self, response, out)
59
- if alive
60
- @env.clear
61
- @hp.reset
62
- @state = :headers
63
- # keepalive requests are always body-less, so @input is unchanged
64
- @hp.headers(@env, @buf) and next
65
- else
66
- quit
67
- end
68
- return
69
- end while true
70
- end
71
-
72
- def on_write_complete
73
- if body = @deferred_bodies.first
74
- return if DeferredResponse === body
75
- begin
76
- begin
77
- write(body.sysread(CHUNK_SIZE))
78
- rescue EOFError # expected at file EOF
79
- @deferred_bodies.shift
80
- body.close
81
- close if :close == @state && @deferred_bodies.empty?
82
- end
83
- rescue Object => e
84
- handle_error(e)
85
- end
86
- else
87
- close if :close == @state
88
- end
89
- end
90
-
91
- def on_close
92
- G.cur -= 1
93
- end
94
- end
95
-
96
- class Server < ::Rev::IO
97
- G = Rainbows::G
98
-
99
- def on_readable
100
- return if G.cur >= G.max
101
- begin
102
- Client.new(@_io.accept_nonblock).attach(::Rev::Loop.default)
103
- rescue Errno::EAGAIN, Errno::ECONNABORTED
104
- end
105
- end
106
-
107
- end
108
-
109
- class DeferredResponse < ::Rev::IO
110
- include Unicorn
111
- include Rainbows::Const
112
- G = Rainbows::G
113
-
114
- def self.defer!(client, response, out)
115
- body = response.last
116
- headers = Rack::Utils::HeaderHash.new(response[1])
117
-
118
- # to_io is not part of the Rack spec, but make an exception
119
- # here since we can't get here without checking to_path first
120
- io = body.to_io if body.respond_to?(:to_io)
121
- io ||= ::IO.new($1.to_i) if body.to_path =~ %r{\A/dev/fd/(\d+)\z}
122
- io ||= File.open(body.to_path, 'rb')
123
- st = io.stat
124
-
125
- if st.socket? || st.pipe?
126
- do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
127
- do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
128
- # too tricky to support keepalive/pipelining when a response can
129
- # take an indeterminate amount of time here.
130
- if out.nil?
131
- do_chunk = false
132
- else
133
- out[0] = CONN_CLOSE
134
- end
135
-
136
- io = new(io, client, do_chunk, body).attach(::Rev::Loop.default)
137
- elsif st.file?
138
- headers.delete('Transfer-Encoding')
139
- headers['Content-Length'] ||= st.size.to_s
140
- else # char/block device, directory, whatever... nobody cares
141
- return response
142
- end
143
- client.defer_body(io)
144
- [ response.first, headers.to_hash, [] ]
145
- end
146
-
147
- def self.write(client, response, out)
148
- response.last.respond_to?(:to_path) and
149
- response = defer!(client, response, out)
150
- HttpResponse.write(client, response, out)
151
- end
152
-
153
- def initialize(io, client, do_chunk, body)
154
- super(io)
155
- @client, @do_chunk, @body = client, do_chunk, body
156
- end
157
-
158
- def on_read(data)
159
- @do_chunk and @client.write(sprintf("%x\r\n", data.size))
160
- @client.write(data)
161
- @do_chunk and @client.write("\r\n")
162
- end
163
-
164
- def on_close
165
- @do_chunk and @client.write("0\r\n\r\n")
166
- @client.quit
167
- @body.respond_to?(:close) and @body.close
168
- end
169
- end
170
-
171
- # runs inside each forked worker, this sits around and waits
172
- # for connections and doesn't die until the parent dies (or is
173
- # given a INT, QUIT, or TERM signal)
174
- def worker_loop(worker)
175
- init_worker_process(worker)
176
- rloop = ::Rev::Loop.default
177
- Heartbeat.new(worker.tmp).attach(rloop)
178
- LISTENERS.map! { |s| Server.new(s).attach(rloop) }
179
- rloop.run
180
- end
181
-
27
+ include Core
182
28
  end
183
29
  end
@@ -0,0 +1,73 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rainbows/ev_core'
3
+ module Rainbows
4
+ module Rev
5
+
6
+ class Client < ::Rev::IO
7
+ include Rainbows::EvCore
8
+ G = Rainbows::G
9
+
10
+ def initialize(io)
11
+ G.cur += 1
12
+ super(io)
13
+ post_init
14
+ @deferred_bodies = [] # for (fast) regular files only
15
+ end
16
+
17
+ # queued, optional response bodies, it should only be unpollable "fast"
18
+ # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
19
+ # are also part of this. We'll also stick DeferredResponse bodies in
20
+ # here to prevent connections from being closed on us.
21
+ def defer_body(io, out_headers)
22
+ @deferred_bodies << io
23
+ schedule_write unless out_headers # triggers a write
24
+ end
25
+
26
+ def app_call
27
+ begin
28
+ (@env[RACK_INPUT] = @input).rewind
29
+ @env[REMOTE_ADDR] = @remote_addr
30
+ response = APP.call(@env.update(RACK_DEFAULTS))
31
+ alive = @hp.keepalive? && G.alive
32
+ out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
33
+
34
+ DeferredResponse.write(self, response, out)
35
+ if alive
36
+ @env.clear
37
+ @hp.reset
38
+ @state = :headers
39
+ # keepalive requests are always body-less, so @input is unchanged
40
+ @hp.headers(@env, @buf) and next
41
+ else
42
+ quit
43
+ end
44
+ return
45
+ end while true
46
+ end
47
+
48
+ def on_write_complete
49
+ if body = @deferred_bodies.first
50
+ return if DeferredResponse === body
51
+ begin
52
+ begin
53
+ write(body.sysread(CHUNK_SIZE))
54
+ rescue EOFError # expected at file EOF
55
+ @deferred_bodies.shift
56
+ body.close
57
+ close if :close == @state && @deferred_bodies.empty?
58
+ end
59
+ rescue => e
60
+ handle_error(e)
61
+ end
62
+ else
63
+ close if :close == @state
64
+ end
65
+ end
66
+
67
+ def on_close
68
+ G.cur -= 1
69
+ end
70
+
71
+ end # module Client
72
+ end # module Rev
73
+ end # module Rainbows
@@ -0,0 +1,42 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rev'
3
+ Rev::VERSION >= '0.3.0' or abort 'rev >= 0.3.0 is required'
4
+ require 'rainbows/rev/heartbeat'
5
+
6
+ module Rainbows
7
+ module Rev
8
+ class Server < ::Rev::IO
9
+ G = Rainbows::G
10
+ LOOP = ::Rev::Loop.default
11
+ # CL and MAX will be defined in the corresponding worker loop
12
+
13
+ def on_readable
14
+ return if G.cur >= MAX
15
+ begin
16
+ CL.new(@_io.accept_nonblock).attach(LOOP)
17
+ rescue Errno::EAGAIN, Errno::ECONNABORTED
18
+ end
19
+ end
20
+ end # class Server
21
+
22
+ module Core
23
+ include Base
24
+
25
+ # runs inside each forked worker, this sits around and waits
26
+ # for connections and doesn't die until the parent dies (or is
27
+ # given a INT, QUIT, or TERM signal)
28
+ def worker_loop(worker)
29
+ init_worker_process(worker)
30
+ mod = self.class.const_get(@use)
31
+ Server.const_set(:MAX, @worker_connections)
32
+ Server.const_set(:CL, mod.const_get(:Client))
33
+ EvCore.setup(EvCore)
34
+ rloop = ::Rev::Loop.default
35
+ Heartbeat.new(1, true).attach(rloop)
36
+ LISTENERS.map! { |s| Server.new(s).attach(rloop) }
37
+ rloop.run
38
+ end
39
+
40
+ end # module Core
41
+ end # module Rev
42
+ end # module Rainbows
@@ -0,0 +1,74 @@
1
+ # -*- encoding: binary -*-
2
+ module Rainbows
3
+ module Rev
4
+
5
+ # this is class is specific to Rev for writing large static files
6
+ # or proxying IO-derived objects
7
+ class DeferredResponse < ::Rev::IO
8
+ include Unicorn
9
+ include Rainbows::Const
10
+ G = Rainbows::G
11
+ HH = Rack::Utils::HeaderHash
12
+
13
+ # we only want to attach to the Rev::Loop belonging to the
14
+ # main thread in Ruby 1.9
15
+ LOOP = ::Rev::Loop.default
16
+
17
+ def self.defer!(client, response, out)
18
+ body = response.last
19
+ headers = HH.new(response[1])
20
+
21
+ # to_io is not part of the Rack spec, but make an exception
22
+ # here since we can't get here without checking to_path first
23
+ io = body.to_io if body.respond_to?(:to_io)
24
+ io ||= ::IO.new($1.to_i) if body.to_path =~ %r{\A/dev/fd/(\d+)\z}
25
+ io ||= File.open(body.to_path, 'rb')
26
+ st = io.stat
27
+
28
+ if st.socket? || st.pipe?
29
+ do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
30
+ do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
31
+ # too tricky to support keepalive/pipelining when a response can
32
+ # take an indeterminate amount of time here.
33
+ if out.nil?
34
+ do_chunk = false
35
+ else
36
+ out[0] = CONN_CLOSE
37
+ end
38
+
39
+ io = new(io, client, do_chunk, body).attach(LOOP)
40
+ elsif st.file?
41
+ headers.delete('Transfer-Encoding')
42
+ headers['Content-Length'] ||= st.size.to_s
43
+ else # char/block device, directory, whatever... nobody cares
44
+ return response
45
+ end
46
+ client.defer_body(io, out)
47
+ [ response.first, headers.to_hash, [] ]
48
+ end
49
+
50
+ def self.write(client, response, out)
51
+ response.last.respond_to?(:to_path) and
52
+ response = defer!(client, response, out)
53
+ HttpResponse.write(client, response, out)
54
+ end
55
+
56
+ def initialize(io, client, do_chunk, body)
57
+ super(io)
58
+ @client, @do_chunk, @body = client, do_chunk, body
59
+ end
60
+
61
+ def on_read(data)
62
+ @do_chunk and @client.write(sprintf("%x\r\n", data.size))
63
+ @client.write(data)
64
+ @do_chunk and @client.write("\r\n")
65
+ end
66
+
67
+ def on_close
68
+ @do_chunk and @client.write("0\r\n\r\n")
69
+ @client.quit
70
+ @body.respond_to?(:close) and @body.close
71
+ end
72
+ end # class DeferredResponse
73
+ end # module Rev
74
+ end # module Rainbows
@@ -1,7 +1,4 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rev'
3
- Rev::VERSION >= '0.3.0' or abort 'rev >= 0.3.0 is required'
4
-
5
2
  module Rainbows
6
3
  module Rev
7
4
 
@@ -11,15 +8,9 @@ module Rainbows
11
8
  # will also detect and execute the graceful exit if triggered
12
9
  # by SIGQUIT
13
10
  class Heartbeat < ::Rev::TimerWatcher
14
- # +tmp+ must be a +File+ that responds to +chmod+
15
- def initialize(tmp)
16
- @m, @tmp = 0, tmp
17
- super(1, true)
18
- end
19
11
 
20
12
  def on_timer
21
- @tmp.chmod(@m = 0 == @m ? 1 : 0)
22
- exit if (! G.alive && G.cur <= 0)
13
+ exit if (! G.tick && G.cur <= 0)
23
14
  end
24
15
 
25
16
  end
@@ -0,0 +1,94 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rainbows/rev'
3
+ require 'rainbows/ev_thread_core'
4
+
5
+ warn "Rainbows::RevThreadSpawn is extremely experimental"
6
+
7
+ module Rainbows
8
+
9
+ # This concurrency model is EXTREMELY experimental and does
10
+ # not perform very well.
11
+ #
12
+ # A combination of the Rev and ThreadSpawn models. This allows Ruby
13
+ # 1.8 and 1.9 to effectively serve more than ~1024 concurrent clients
14
+ # on systems that support kqueue or epoll while still using
15
+ # Thread-based concurrency for application processing. It exposes
16
+ # Unicorn::TeeInput for a streamable "rack.input" for upload
17
+ # processing within the app. Threads are spawned immediately after
18
+ # header processing is done for calling the application. Rack
19
+ # applications running under this mode should be thread-safe.
20
+ # DevFdResponse should be used with this class to proxy asynchronous
21
+ # responses. All network I/O between the client and server are
22
+ # handled by the main thread (even when streaming "rack.input").
23
+ #
24
+ # Caveats:
25
+ #
26
+ # * TeeInput performance under Ruby 1.8 is terrible unless you
27
+ # match the length argument of your env["rack.input"]#read
28
+ # calls so that it is greater than or equal to Rev::IO::INPUT_SIZE.
29
+ # Most applications depending on Rack to do multipart POST
30
+ # processing should be alright as the current Rev::IO::INPUT_SIZE
31
+ # of 16384 bytes matches the read size used by
32
+ # Rack::Utils::Multipart::parse_multipart.
33
+
34
+ module RevThreadSpawn
35
+ class Client < Rainbows::Rev::Client
36
+ include EvThreadCore
37
+ LOOP = ::Rev::Loop.default
38
+ DR = Rainbows::Rev::DeferredResponse
39
+ TEE_RESUMER = ::Rev::AsyncWatcher.new
40
+
41
+ def pause
42
+ @lock.synchronize { disable if enabled? }
43
+ end
44
+
45
+ def resume
46
+ @lock.synchronize { enable unless enabled? }
47
+ TEE_RESUMER.signal
48
+ end
49
+
50
+ def write(data)
51
+ if Thread.current != @thread && @lock.locked?
52
+ # we're being called inside on_writable
53
+ super
54
+ else
55
+ @lock.synchronize { super }
56
+ end
57
+ end
58
+
59
+ def defer_body(io, out_headers)
60
+ @lock.synchronize { super }
61
+ end
62
+
63
+ def response_write(response, out)
64
+ DR.write(self, response, out)
65
+ (out && CONN_ALIVE == out.first) or
66
+ @lock.synchronize {
67
+ quit
68
+ schedule_write
69
+ }
70
+ end
71
+
72
+ def on_writable
73
+ # don't ever want to block in the main loop with lots of clients,
74
+ # libev is level-triggered so we'll always get another chance later
75
+ if @lock.try_lock
76
+ begin
77
+ super
78
+ ensure
79
+ @lock.unlock
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ include Rainbows::Rev::Core
87
+
88
+ def init_worker_process(worker)
89
+ super
90
+ Client::TEE_RESUMER.attach(::Rev::Loop.default)
91
+ end
92
+
93
+ end
94
+ end