rainbows 0.94.0 → 0.95.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -0
- data/.manifest +18 -0
- data/ChangeLog +394 -226
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +6 -4
- data/NEWS +18 -0
- data/README +13 -5
- data/Static_Files +71 -0
- data/TODO +12 -0
- data/Test_Suite +1 -1
- data/bin/rainbows +1 -4
- data/lib/rainbows/actor_spawn.rb +1 -1
- data/lib/rainbows/app_pool.rb +1 -1
- data/lib/rainbows/base.rb +79 -89
- data/lib/rainbows/byte_slice.rb +17 -0
- data/lib/rainbows/configurator.rb +46 -0
- data/lib/rainbows/const.rb +2 -2
- data/lib/rainbows/dev_fd_response.rb +52 -44
- data/lib/rainbows/error.rb +1 -0
- data/lib/rainbows/ev_core.rb +3 -2
- data/lib/rainbows/event_machine.rb +26 -24
- data/lib/rainbows/fiber/base.rb +30 -40
- data/lib/rainbows/fiber/body.rb +34 -0
- data/lib/rainbows/fiber/io.rb +28 -8
- data/lib/rainbows/fiber/queue.rb +1 -0
- data/lib/rainbows/fiber/rev.rb +4 -2
- data/lib/rainbows/fiber.rb +1 -0
- data/lib/rainbows/fiber_pool.rb +2 -2
- data/lib/rainbows/fiber_spawn.rb +2 -2
- data/lib/rainbows/http_response.rb +20 -31
- data/lib/rainbows/http_server.rb +3 -4
- data/lib/rainbows/max_body.rb +1 -0
- data/lib/rainbows/never_block/event_machine.rb +2 -0
- data/lib/rainbows/never_block.rb +5 -4
- data/lib/rainbows/queue_pool.rb +1 -0
- data/lib/rainbows/response/body.rb +119 -0
- data/lib/rainbows/response.rb +43 -0
- data/lib/rainbows/rev/client.rb +79 -9
- data/lib/rainbows/rev/core.rb +4 -0
- data/lib/rainbows/rev/deferred_response.rb +1 -44
- data/lib/rainbows/rev/heartbeat.rb +1 -0
- data/lib/rainbows/rev/master.rb +1 -0
- data/lib/rainbows/rev/sendfile.rb +26 -0
- data/lib/rainbows/rev/thread.rb +2 -1
- data/lib/rainbows/rev.rb +2 -0
- data/lib/rainbows/rev_fiber_spawn.rb +3 -1
- data/lib/rainbows/rev_thread_pool.rb +7 -5
- data/lib/rainbows/rev_thread_spawn.rb +2 -2
- data/lib/rainbows/revactor.rb +146 -146
- data/lib/rainbows/sendfile.rb +10 -21
- data/lib/rainbows/server_token.rb +39 -0
- data/lib/rainbows/stream_file.rb +14 -0
- data/lib/rainbows/tee_input.rb +1 -0
- data/lib/rainbows/thread_pool.rb +12 -7
- data/lib/rainbows/thread_spawn.rb +2 -3
- data/lib/rainbows/writer_thread_pool.rb +13 -7
- data/lib/rainbows/writer_thread_spawn.rb +12 -9
- data/lib/rainbows.rb +16 -45
- data/rainbows.gemspec +8 -8
- data/t/.gitignore +1 -1
- data/t/GNUmakefile +26 -16
- data/t/README +1 -1
- data/t/async-response-no-autochunk.ru +0 -1
- data/t/async-response.ru +0 -1
- data/t/cramp/rainsocket.ru +26 -0
- data/t/fork-sleep.ru +0 -1
- data/t/my-tap-lib.sh +3 -2
- data/t/simple-http_ActorSpawn.ru +9 -0
- data/t/t0009-broken-app.sh +1 -1
- data/t/t0009.ru +0 -1
- data/t/t0011-close-on-exec-set.sh +1 -1
- data/t/t0015-working_directory.sh +56 -0
- data/t/t0016-onenine-encoding-is-tricky.sh +28 -0
- data/t/t0016.rb +15 -0
- data/t/t0020-large-sendfile-response.sh +141 -0
- data/t/t0300-async_sinatra.sh +0 -6
- data/t/t0501-cramp-rainsocket.sh +38 -0
- data/t/t9001-sendfile-to-path.sh +5 -4
- data/t/t9002-server-token.sh +37 -0
- data/t/t9002.ru +4 -0
- data/t/test-lib.sh +1 -1
- data/t/test_isolate.rb +14 -11
- metadata +87 -18
data/lib/rainbows/revactor.rb
CHANGED
@@ -2,174 +2,174 @@
|
|
2
2
|
require 'revactor'
|
3
3
|
Revactor::VERSION >= '0.1.5' or abort 'revactor 0.1.5 is required'
|
4
4
|
|
5
|
-
|
6
|
-
|
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
|
-
buf = client.read(*rd_args)
|
41
|
-
hp = HttpParser.new
|
42
|
-
env = {}
|
43
|
-
alive = true
|
44
|
-
|
45
|
-
begin
|
46
|
-
while ! hp.headers(env, buf)
|
47
|
-
buf << client.read(*rd_args)
|
48
|
-
end
|
49
|
-
|
50
|
-
env[Const::CLIENT_IO] = client
|
51
|
-
env[Const::RACK_INPUT] = 0 == hp.content_length ?
|
52
|
-
HttpRequest::NULL_IO :
|
53
|
-
TeeInput.new(PartialSocket.new(client), env, hp, buf)
|
54
|
-
env[Const::REMOTE_ADDR] = remote_addr
|
55
|
-
response = app.call(env.update(RACK_DEFAULTS))
|
56
|
-
|
57
|
-
if 100 == response.first.to_i
|
58
|
-
client.write(Const::EXPECT_100_RESPONSE)
|
59
|
-
env.delete(Const::HTTP_EXPECT)
|
60
|
-
response = app.call(env)
|
61
|
-
end
|
62
|
-
|
63
|
-
alive = hp.keepalive? && G.alive
|
64
|
-
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
65
|
-
HttpResponse.write(client, response, out)
|
66
|
-
end while alive and hp.reset.nil? and env.clear
|
67
|
-
rescue ::Revactor::TCP::ReadError
|
68
|
-
rescue => e
|
69
|
-
Error.write(io, e)
|
70
|
-
ensure
|
71
|
-
client.close
|
5
|
+
# Enables use of the Actor model through
|
6
|
+
# {Revactor}[http://revactor.org] under Ruby 1.9. It spawns one
|
7
|
+
# long-lived Actor for every listen socket in the process and spawns a
|
8
|
+
# new Actor for every client connection accept()-ed.
|
9
|
+
# +worker_connections+ will limit the number of client Actors we have
|
10
|
+
# running at any one time.
|
11
|
+
#
|
12
|
+
# Applications using this model are required to be reentrant, but do
|
13
|
+
# not have to worry about race conditions unless they use threads
|
14
|
+
# internally. \Rainbows! does not spawn threads under this model.
|
15
|
+
# Multiple instances of the same app may run in the same address space
|
16
|
+
# sequentially (but at interleaved points). Any network dependencies
|
17
|
+
# in the application using this model should be implemented using the
|
18
|
+
# \Revactor library as well, to take advantage of the networking
|
19
|
+
# concurrency features this model provides.
|
20
|
+
module Rainbows::Revactor
|
21
|
+
|
22
|
+
# :stopdoc:
|
23
|
+
RD_ARGS = {}
|
24
|
+
|
25
|
+
include Rainbows::Base
|
26
|
+
LOCALHOST = Unicorn::HttpRequest::LOCALHOST
|
27
|
+
TCP = ::Revactor::TCP::Socket
|
28
|
+
|
29
|
+
# once a client is accepted, it is processed in its entirety here
|
30
|
+
# in 3 easy steps: read request, call app, write app response
|
31
|
+
def process_client(client) # :nodoc:
|
32
|
+
io = client.instance_variable_get(:@_io)
|
33
|
+
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
34
|
+
rd_args = [ nil ]
|
35
|
+
remote_addr = if TCP === client
|
36
|
+
rd_args << RD_ARGS
|
37
|
+
client.remote_addr
|
38
|
+
else
|
39
|
+
LOCALHOST
|
72
40
|
end
|
41
|
+
buf = client.read(*rd_args)
|
42
|
+
hp = HttpParser.new
|
43
|
+
env = {}
|
44
|
+
alive = true
|
45
|
+
|
46
|
+
begin
|
47
|
+
buf << client.read(*rd_args) until hp.headers(env, buf)
|
48
|
+
|
49
|
+
env[CLIENT_IO] = client
|
50
|
+
env[RACK_INPUT] = 0 == hp.content_length ?
|
51
|
+
NULL_IO : TeeInput.new(PartialSocket.new(client), env, hp, buf)
|
52
|
+
env[REMOTE_ADDR] = remote_addr
|
53
|
+
response = app.call(env.update(RACK_DEFAULTS))
|
54
|
+
|
55
|
+
if 100 == response[0].to_i
|
56
|
+
client.write(EXPECT_100_RESPONSE)
|
57
|
+
env.delete(HTTP_EXPECT)
|
58
|
+
response = app.call(env)
|
59
|
+
end
|
73
60
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
revactorize_listeners.each do |l, close, accept|
|
85
|
-
Actor.spawn(l, close, accept) do |l, close, accept|
|
86
|
-
Actor.current.trap_exit = true
|
87
|
-
l.controller = l.instance_variable_set(:@receiver, Actor.current)
|
88
|
-
begin
|
89
|
-
while nr >= limit
|
90
|
-
l.disable if l.enabled?
|
91
|
-
logger.info "busy: clients=#{nr} >= limit=#{limit}"
|
92
|
-
Actor.receive do |f|
|
93
|
-
f.when(close) {}
|
94
|
-
f.when(actor_exit) { nr -= 1 }
|
95
|
-
f.after(0.01) {} # another listener could've gotten an exit
|
96
|
-
end
|
97
|
-
end
|
61
|
+
alive = hp.keepalive? && G.alive
|
62
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
63
|
+
write_response(client, response, out)
|
64
|
+
end while alive and hp.reset.nil? and env.clear
|
65
|
+
rescue ::Revactor::TCP::ReadError
|
66
|
+
rescue => e
|
67
|
+
Rainbows::Error.write(io, e)
|
68
|
+
ensure
|
69
|
+
client.close
|
70
|
+
end
|
98
71
|
|
99
|
-
|
72
|
+
# runs inside each forked worker, this sits around and waits
|
73
|
+
# for connections and doesn't die until the parent dies (or is
|
74
|
+
# given a INT, QUIT, or TERM signal)
|
75
|
+
def worker_loop(worker) #:nodoc:
|
76
|
+
init_worker_process(worker)
|
77
|
+
self.class.__send__(:alias_method, :write_body, :write_body_each)
|
78
|
+
RD_ARGS[:timeout] = G.kato if G.kato > 0
|
79
|
+
nr = 0
|
80
|
+
limit = worker_connections
|
81
|
+
actor_exit = Case[:exit, Actor, Object]
|
82
|
+
|
83
|
+
revactorize_listeners.each do |l, close, accept|
|
84
|
+
Actor.spawn(l, close, accept) do |l, close, accept|
|
85
|
+
Actor.current.trap_exit = true
|
86
|
+
l.controller = l.instance_variable_set(:@receiver, Actor.current)
|
87
|
+
begin
|
88
|
+
while nr >= limit
|
89
|
+
l.disable if l.enabled?
|
90
|
+
logger.info "busy: clients=#{nr} >= limit=#{limit}"
|
100
91
|
Actor.receive do |f|
|
101
92
|
f.when(close) {}
|
102
93
|
f.when(actor_exit) { nr -= 1 }
|
103
|
-
f.
|
104
|
-
nr += 1
|
105
|
-
Actor.spawn_link(s) { |c| process_client(c) }
|
106
|
-
end
|
94
|
+
f.after(0.01) {} # another listener could've gotten an exit
|
107
95
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
96
|
+
end
|
97
|
+
|
98
|
+
l.enable unless l.enabled?
|
111
99
|
Actor.receive do |f|
|
112
100
|
f.when(close) {}
|
113
101
|
f.when(actor_exit) { nr -= 1 }
|
114
|
-
|
115
|
-
|
102
|
+
f.when(accept) do |_, _, s|
|
103
|
+
nr += 1
|
104
|
+
Actor.spawn_link(s) { |c| process_client(c) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue => e
|
108
|
+
Rainbows::Error.listen_loop(e)
|
109
|
+
end while G.alive
|
110
|
+
Actor.receive do |f|
|
111
|
+
f.when(close) {}
|
112
|
+
f.when(actor_exit) { nr -= 1 }
|
113
|
+
end while nr > 0
|
116
114
|
end
|
117
|
-
|
118
|
-
Actor.sleep 1 while G.tick || nr > 0
|
119
|
-
rescue Errno::EMFILE
|
120
|
-
# ignore, let another worker process take it
|
121
115
|
end
|
122
116
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
117
|
+
Actor.sleep 1 while G.tick || nr > 0
|
118
|
+
rescue Errno::EMFILE
|
119
|
+
# ignore, let another worker process take it
|
120
|
+
end
|
121
|
+
|
122
|
+
def revactorize_listeners
|
123
|
+
LISTENERS.map do |s|
|
124
|
+
case s
|
125
|
+
when TCPServer
|
126
|
+
l = ::Revactor::TCP.listen(s, nil)
|
127
|
+
[ l, T[:tcp_closed, ::Revactor::TCP::Socket],
|
128
|
+
T[:tcp_connection, l, ::Revactor::TCP::Socket] ]
|
129
|
+
when UNIXServer
|
130
|
+
l = ::Revactor::UNIX.listen(s)
|
131
|
+
[ l, T[:unix_closed, ::Revactor::UNIX::Socket ],
|
132
|
+
T[:unix_connection, l, ::Revactor::UNIX::Socket] ]
|
135
133
|
end
|
136
134
|
end
|
135
|
+
end
|
137
136
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
137
|
+
# Revactor Sockets do not implement readpartial, so we emulate just
|
138
|
+
# enough to avoid mucking with TeeInput internals. Fortunately
|
139
|
+
# this code is not heavily used so we can usually avoid the overhead
|
140
|
+
# of adding a userspace buffer.
|
141
|
+
class PartialSocket < Struct.new(:socket, :rbuf)
|
142
|
+
def initialize(socket)
|
143
|
+
# IO::Buffer is used internally by Rev which Revactor is based on
|
144
|
+
# so we'll always have it available
|
145
|
+
super(socket, IO::Buffer.new)
|
146
|
+
end
|
148
147
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
# always check and return from the userspace buffer first
|
154
|
-
rbuf.size > 0 and return dst.replace(rbuf.read(length))
|
148
|
+
# Revactor socket reads always return an unspecified amount,
|
149
|
+
# sometimes too much
|
150
|
+
def readpartial(length, dst = "")
|
151
|
+
return dst.replace("") if length == 0
|
155
152
|
|
156
|
-
|
157
|
-
|
153
|
+
# always check and return from the userspace buffer first
|
154
|
+
rbuf.size > 0 and return dst.replace(rbuf.read(length))
|
158
155
|
|
159
|
-
|
160
|
-
|
161
|
-
tmp.size <= length and return dst.replace(tmp)
|
156
|
+
# read off the socket since there was nothing in rbuf
|
157
|
+
tmp = socket.read
|
162
158
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
159
|
+
# we didn't read too much, good, just return it straight back
|
160
|
+
# to avoid needlessly wasting memory bandwidth
|
161
|
+
tmp.size <= length and return dst.replace(tmp)
|
167
162
|
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
end
|
163
|
+
# ugh, read returned too much
|
164
|
+
rbuf << tmp[length, tmp.size]
|
165
|
+
dst.replace(tmp[0, length])
|
172
166
|
end
|
173
167
|
|
168
|
+
# just proxy any remaining methods TeeInput may use
|
169
|
+
def close
|
170
|
+
socket.close
|
171
|
+
end
|
174
172
|
end
|
173
|
+
|
174
|
+
# :startdoc:
|
175
175
|
end
|
data/lib/rainbows/sendfile.rb
CHANGED
@@ -11,8 +11,8 @@ module Rainbows
|
|
11
11
|
# efficiently using sendfile() or similar. With multithreaded models
|
12
12
|
# under Ruby 1.9, IO.copy_stream will be used.
|
13
13
|
#
|
14
|
-
# This middleware is the opposite of Rack::
|
15
|
-
# reverses the effect of Rack
|
14
|
+
# This middleware is the opposite of Rack::Sendfile as it
|
15
|
+
# reverses the effect of Rack:::Sendfile. Unlike many Ruby
|
16
16
|
# web servers, some configurations of \Rainbows! are capable of
|
17
17
|
# serving static files efficiently.
|
18
18
|
#
|
@@ -57,37 +57,26 @@ class Sendfile < Struct.new(:app)
|
|
57
57
|
# Body wrapper, this allows us to fall back gracefully to
|
58
58
|
# +each+ in case a given concurrency model does not optimize
|
59
59
|
# +to_path+ calls.
|
60
|
-
class Body < Struct.new(:
|
61
|
-
|
62
|
-
def initialize(path, headers)
|
63
|
-
# Rainbows! will try #to_io if #to_path exists to avoid unnecessary
|
64
|
-
# open() calls.
|
65
|
-
self.to_io = File.open(path, 'rb')
|
60
|
+
class Body < Struct.new(:to_path) # :nodoc: all
|
66
61
|
|
62
|
+
def self.new(path, headers)
|
67
63
|
unless headers['Content-Length']
|
68
|
-
stat =
|
64
|
+
stat = File.stat(path)
|
69
65
|
headers['Content-Length'] = stat.size.to_s if stat.file?
|
70
66
|
end
|
71
|
-
|
72
|
-
|
73
|
-
def to_path
|
74
|
-
to_io.path
|
67
|
+
super(path)
|
75
68
|
end
|
76
69
|
|
77
70
|
# fallback in case our +to_path+ doesn't get handled for whatever reason
|
78
71
|
def each(&block)
|
79
|
-
|
80
|
-
|
81
|
-
yield buf
|
72
|
+
File.open(to_path, 'rb') do |fp|
|
73
|
+
buf = ''
|
74
|
+
yield buf while fp.read(0x4000, buf)
|
82
75
|
end
|
83
76
|
end
|
84
|
-
|
85
|
-
def close
|
86
|
-
to_io.close
|
87
|
-
end
|
88
77
|
end
|
89
78
|
|
90
|
-
def call(env)
|
79
|
+
def call(env) # :nodoc:
|
91
80
|
status, headers, body = app.call(env)
|
92
81
|
headers = HH.new(headers)
|
93
82
|
if path = headers.delete('X-Sendfile')
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
|
4
|
+
# An optional middleware to proudly display your usage of \Rainbows! in
|
5
|
+
# the "Server:" response header. This means you can help tell the world
|
6
|
+
# you're using \Rainbows! and spread fun and joy all over the Internet!
|
7
|
+
#
|
8
|
+
# ------ in your config.ru ------
|
9
|
+
# require 'rainbows/server_token'
|
10
|
+
# require 'rack/lobster'
|
11
|
+
# use Rainbows::ServerToken
|
12
|
+
# run Rack::Lobster.new
|
13
|
+
#
|
14
|
+
# If you're nervous about the exact version of \Rainbows! you're running,
|
15
|
+
# then you can actually specify anything you want:
|
16
|
+
#
|
17
|
+
# use Rainbows::ServerToken, "netcat 1.0"
|
18
|
+
#
|
19
|
+
|
20
|
+
class ServerToken < Struct.new(:app, :token)
|
21
|
+
|
22
|
+
# :stopdoc:
|
23
|
+
#
|
24
|
+
# Freeze constants as they're slightly faster when setting hashes
|
25
|
+
SERVER = "Server".freeze
|
26
|
+
|
27
|
+
def initialize(app, token = Const::RACK_DEFAULTS['SERVER_SOFTWARE'])
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
status, headers, body = app.call(env)
|
33
|
+
headers = Rack::Utils::HeaderHash.new(headers)
|
34
|
+
headers[SERVER] = token
|
35
|
+
[ status, headers, body ]
|
36
|
+
end
|
37
|
+
# :startdoc:
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
|
4
|
+
# Used to keep track of file offsets in IO#sendfile_nonblock + evented
|
5
|
+
# models. We always maintain our own file offsets in userspace because
|
6
|
+
# because sendfile() implementations offer pread()-like idempotency for
|
7
|
+
# concurrency (multiple clients can read the same underlying file handle).
|
8
|
+
class Rainbows::StreamFile < Struct.new(:offset, :to_io)
|
9
|
+
|
10
|
+
def close
|
11
|
+
to_io.close
|
12
|
+
self.to_io = nil
|
13
|
+
end
|
14
|
+
end
|
data/lib/rainbows/tee_input.rb
CHANGED
data/lib/rainbows/thread_pool.rb
CHANGED
@@ -25,7 +25,7 @@ module Rainbows
|
|
25
25
|
|
26
26
|
include Base
|
27
27
|
|
28
|
-
def worker_loop(worker)
|
28
|
+
def worker_loop(worker) # :nodoc:
|
29
29
|
init_worker_process(worker)
|
30
30
|
pool = (1..worker_connections).map do
|
31
31
|
Thread.new { LISTENERS.size == 1 ? sync_worker : async_worker }
|
@@ -41,8 +41,8 @@ module Rainbows
|
|
41
41
|
join_threads(pool)
|
42
42
|
end
|
43
43
|
|
44
|
-
def sync_worker
|
45
|
-
s = LISTENERS
|
44
|
+
def sync_worker # :nodoc:
|
45
|
+
s = LISTENERS[0]
|
46
46
|
begin
|
47
47
|
c = Rainbows.sync_accept(s) and process_client(c)
|
48
48
|
rescue => e
|
@@ -50,14 +50,14 @@ module Rainbows
|
|
50
50
|
end while G.alive
|
51
51
|
end
|
52
52
|
|
53
|
-
def async_worker
|
53
|
+
def async_worker # :nodoc:
|
54
54
|
begin
|
55
55
|
# TODO: check if select() or accept() is a problem on large
|
56
56
|
# SMP systems under Ruby 1.9. Hundreds of native threads
|
57
57
|
# all working off the same socket could be a thundering herd
|
58
58
|
# problem. On the other hand, a thundering herd may not
|
59
59
|
# even incur as much overhead as an extra Mutex#synchronize
|
60
|
-
ret = IO.select(LISTENERS, nil, nil, 1) and ret.
|
60
|
+
ret = IO.select(LISTENERS, nil, nil, 1) and ret[0].each do |s|
|
61
61
|
s = Rainbows.accept(s) and process_client(s)
|
62
62
|
end
|
63
63
|
rescue Errno::EINTR
|
@@ -66,11 +66,16 @@ module Rainbows
|
|
66
66
|
end while G.alive
|
67
67
|
end
|
68
68
|
|
69
|
-
def join_threads(threads)
|
69
|
+
def join_threads(threads) # :nodoc:
|
70
70
|
G.quit!
|
71
71
|
threads.delete_if do |thr|
|
72
72
|
G.tick
|
73
|
-
|
73
|
+
begin
|
74
|
+
thr.run
|
75
|
+
thr.join(0.01)
|
76
|
+
rescue
|
77
|
+
true
|
78
|
+
end
|
74
79
|
end until threads.empty?
|
75
80
|
end
|
76
81
|
|
@@ -17,10 +17,9 @@ module Rainbows
|
|
17
17
|
# capabilities
|
18
18
|
|
19
19
|
module ThreadSpawn
|
20
|
-
|
21
20
|
include Base
|
22
21
|
|
23
|
-
def accept_loop(klass)
|
22
|
+
def accept_loop(klass) #:nodoc:
|
24
23
|
lock = Mutex.new
|
25
24
|
limit = worker_connections
|
26
25
|
LISTENERS.each do |l|
|
@@ -55,7 +54,7 @@ module Rainbows
|
|
55
54
|
sleep 1 while G.tick || lock.synchronize { G.cur > 0 }
|
56
55
|
end
|
57
56
|
|
58
|
-
def worker_loop(worker)
|
57
|
+
def worker_loop(worker) #:nodoc:
|
59
58
|
init_worker_process(worker)
|
60
59
|
accept_loop(Thread)
|
61
60
|
end
|
@@ -24,7 +24,7 @@ module Rainbows
|
|
24
24
|
|
25
25
|
# used to wrap a BasicSocket to use with +q+ for all writes
|
26
26
|
# this is compatible with IO.select
|
27
|
-
class QueueSocket < Struct.new(:to_io, :q)
|
27
|
+
class QueueSocket < Struct.new(:to_io, :q) # :nodoc:
|
28
28
|
def readpartial(size, buf = "")
|
29
29
|
to_io.readpartial(size, buf)
|
30
30
|
end
|
@@ -46,19 +46,25 @@ module Rainbows
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
module Response # :nodoc:
|
50
|
+
def write_body(qclient, body)
|
51
|
+
qclient.q << [ qclient.to_io, :body, body ]
|
52
|
+
end
|
51
53
|
end
|
52
54
|
|
53
55
|
@@nr = 0
|
54
56
|
@@q = nil
|
55
57
|
|
56
|
-
def process_client(client)
|
58
|
+
def process_client(client) # :nodoc:
|
57
59
|
@@nr += 1
|
58
60
|
super(QueueSocket[client, @@q[@@nr %= @@q.size]])
|
59
61
|
end
|
60
62
|
|
61
|
-
def worker_loop(worker)
|
63
|
+
def worker_loop(worker) # :nodoc:
|
64
|
+
Rainbows::Response.setup(self.class)
|
65
|
+
self.class.__send__(:alias_method, :sync_write_body, :write_body)
|
66
|
+
self.class.__send__(:include, Response)
|
67
|
+
|
62
68
|
# we have multiple, single-thread queues since we don't want to
|
63
69
|
# interleave writes from the same client
|
64
70
|
qp = (1..worker_connections).map do |n|
|
@@ -66,13 +72,13 @@ module Rainbows
|
|
66
72
|
begin
|
67
73
|
io, arg1, arg2 = response
|
68
74
|
case arg1
|
69
|
-
when :body then
|
75
|
+
when :body then sync_write_body(io, arg2)
|
70
76
|
when :close then io.close unless io.closed?
|
71
77
|
else
|
72
78
|
io.write(arg1)
|
73
79
|
end
|
74
80
|
rescue => err
|
75
|
-
Error.
|
81
|
+
Error.write(io, err)
|
76
82
|
end
|
77
83
|
end
|
78
84
|
end
|
@@ -23,11 +23,13 @@ module Rainbows
|
|
23
23
|
module WriterThreadSpawn
|
24
24
|
include Base
|
25
25
|
|
26
|
-
CUR = {}
|
26
|
+
CUR = {} # :nodoc:
|
27
27
|
|
28
28
|
# used to wrap a BasicSocket to use with +q+ for all writes
|
29
29
|
# this is compatible with IO.select
|
30
|
-
class MySocket < Struct.new(:to_io, :q, :thr)
|
30
|
+
class MySocket < Struct.new(:to_io, :q, :thr) # :nodoc: all
|
31
|
+
include Rainbows::Response
|
32
|
+
|
31
33
|
def readpartial(size, buf = "")
|
32
34
|
to_io.readpartial(size, buf)
|
33
35
|
end
|
@@ -51,7 +53,7 @@ module Rainbows
|
|
51
53
|
begin
|
52
54
|
arg1, arg2 = response
|
53
55
|
case arg1
|
54
|
-
when :body then
|
56
|
+
when :body then write_body(io, arg2)
|
55
57
|
when :close
|
56
58
|
io.close unless io.closed?
|
57
59
|
break
|
@@ -59,7 +61,7 @@ module Rainbows
|
|
59
61
|
io.write(arg1)
|
60
62
|
end
|
61
63
|
rescue => e
|
62
|
-
Error.
|
64
|
+
Error.write(io, e)
|
63
65
|
end
|
64
66
|
end
|
65
67
|
CUR.delete(Thread.current)
|
@@ -71,7 +73,7 @@ module Rainbows
|
|
71
73
|
(self.q ||= queue_writer) << buf
|
72
74
|
end
|
73
75
|
|
74
|
-
def
|
76
|
+
def queue_body(body)
|
75
77
|
(self.q ||= queue_writer) << [ :body, body ]
|
76
78
|
end
|
77
79
|
|
@@ -88,16 +90,17 @@ module Rainbows
|
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
|
-
def write_body(my_sock, body)
|
92
|
-
my_sock.
|
93
|
+
def write_body(my_sock, body) # :nodoc:
|
94
|
+
my_sock.queue_body(body)
|
93
95
|
end
|
94
96
|
|
95
|
-
def process_client(client)
|
97
|
+
def process_client(client) # :nodoc:
|
96
98
|
super(MySocket[client])
|
97
99
|
end
|
98
100
|
|
99
|
-
def worker_loop(worker)
|
101
|
+
def worker_loop(worker) # :nodoc:
|
100
102
|
MySocket.const_set(:MAX, worker_connections)
|
103
|
+
Rainbows::Response.setup(MySocket)
|
101
104
|
super(worker) # accept loop from Unicorn
|
102
105
|
CUR.delete_if do |t,q|
|
103
106
|
q << nil
|