rainbows 0.94.0 → 0.95.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/.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
|