rainbows 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -0
- data/.gitignore +1 -0
- data/.manifest +46 -18
- data/.wrongdoc.yml +8 -0
- data/ChangeLog +849 -374
- data/Documentation/comparison.haml +26 -21
- data/FAQ +6 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +23 -65
- data/LATEST +27 -0
- data/NEWS +53 -26
- data/README +7 -7
- data/Rakefile +1 -98
- data/Summary +0 -7
- data/TODO +2 -2
- data/lib/rainbows/app_pool.rb +2 -1
- data/lib/rainbows/base.rb +1 -0
- data/lib/rainbows/configurator.rb +9 -0
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/coolio/client.rb +191 -0
- data/lib/rainbows/coolio/core.rb +25 -0
- data/lib/rainbows/{rev → coolio}/deferred_chunk_response.rb +3 -2
- data/lib/rainbows/{rev → coolio}/deferred_response.rb +3 -3
- data/lib/rainbows/coolio/heartbeat.rb +20 -0
- data/lib/rainbows/{rev → coolio}/master.rb +2 -3
- data/lib/rainbows/{rev → coolio}/sendfile.rb +1 -1
- data/lib/rainbows/coolio/server.rb +11 -0
- data/lib/rainbows/coolio/thread_client.rb +36 -0
- data/lib/rainbows/coolio.rb +45 -0
- data/lib/rainbows/coolio_fiber_spawn.rb +26 -0
- data/lib/rainbows/coolio_support.rb +9 -0
- data/lib/rainbows/coolio_thread_pool/client.rb +8 -0
- data/lib/rainbows/coolio_thread_pool/watcher.rb +14 -0
- data/lib/rainbows/coolio_thread_pool.rb +57 -0
- data/lib/rainbows/coolio_thread_spawn/client.rb +8 -0
- data/lib/rainbows/coolio_thread_spawn.rb +27 -0
- data/lib/rainbows/dev_fd_response.rb +6 -2
- data/lib/rainbows/ev_core/cap_input.rb +3 -2
- data/lib/rainbows/ev_core.rb +13 -3
- data/lib/rainbows/event_machine/client.rb +124 -0
- data/lib/rainbows/event_machine/response_pipe.rb +1 -2
- data/lib/rainbows/event_machine/server.rb +15 -0
- data/lib/rainbows/event_machine.rb +13 -137
- data/lib/rainbows/fiber/base.rb +6 -7
- data/lib/rainbows/fiber/body.rb +4 -2
- data/lib/rainbows/fiber/coolio/heartbeat.rb +15 -0
- data/lib/rainbows/fiber/{rev → coolio}/methods.rb +4 -5
- data/lib/rainbows/fiber/{rev → coolio}/server.rb +1 -1
- data/lib/rainbows/fiber/{rev → coolio}/sleeper.rb +2 -2
- data/lib/rainbows/fiber/coolio.rb +12 -0
- data/lib/rainbows/fiber/io/methods.rb +6 -0
- data/lib/rainbows/fiber/io.rb +8 -10
- data/lib/rainbows/fiber/queue.rb +24 -30
- data/lib/rainbows/fiber.rb +7 -4
- data/lib/rainbows/fiber_pool.rb +1 -1
- data/lib/rainbows/http_server.rb +9 -2
- data/lib/rainbows/max_body.rb +3 -1
- data/lib/rainbows/never_block/core.rb +15 -0
- data/lib/rainbows/never_block/event_machine.rb +8 -3
- data/lib/rainbows/never_block.rb +37 -70
- data/lib/rainbows/process_client.rb +3 -6
- data/lib/rainbows/rack_input.rb +17 -0
- data/lib/rainbows/response/body.rb +18 -19
- data/lib/rainbows/response.rb +1 -1
- data/lib/rainbows/rev.rb +21 -43
- data/lib/rainbows/rev_fiber_spawn.rb +4 -19
- data/lib/rainbows/rev_thread_pool.rb +21 -75
- data/lib/rainbows/rev_thread_spawn.rb +18 -36
- data/lib/rainbows/revactor/body.rb +4 -1
- data/lib/rainbows/revactor/tee_socket.rb +44 -0
- data/lib/rainbows/revactor.rb +13 -48
- data/lib/rainbows/socket_proxy.rb +24 -0
- data/lib/rainbows/sync_close.rb +37 -0
- data/lib/rainbows/thread_pool.rb +66 -70
- data/lib/rainbows/thread_spawn.rb +40 -50
- data/lib/rainbows/thread_timeout.rb +33 -27
- data/lib/rainbows/timed_read.rb +5 -1
- data/lib/rainbows/worker_yield.rb +16 -0
- data/lib/rainbows/writer_thread_pool/client.rb +19 -0
- data/lib/rainbows/writer_thread_pool.rb +60 -91
- data/lib/rainbows/writer_thread_spawn/client.rb +69 -0
- data/lib/rainbows/writer_thread_spawn.rb +37 -117
- data/lib/rainbows.rb +12 -4
- data/rainbows.gemspec +15 -19
- data/t/GNUmakefile +4 -4
- data/t/close-has-env.ru +65 -0
- data/t/simple-http_Coolio.ru +9 -0
- data/t/simple-http_CoolioFiberSpawn.ru +10 -0
- data/t/simple-http_CoolioThreadPool.ru +9 -0
- data/t/simple-http_CoolioThreadSpawn.ru +9 -0
- data/t/t0004-heartbeat-timeout.sh +2 -2
- data/t/t0007-worker-follows-master-to-death.sh +1 -1
- data/t/t0015-working_directory.sh +7 -1
- data/t/t0017-keepalive-timeout-zero.sh +1 -1
- data/t/t0019-keepalive-cpu-usage.sh +62 -0
- data/t/t0040-keepalive_requests-setting.sh +51 -0
- data/t/t0050-response-body-close-has-env.sh +109 -0
- data/t/t0102-rack-input-short.sh +6 -6
- data/t/t0106-rack-input-keepalive.sh +48 -2
- data/t/t0113-rewindable-input-false.sh +28 -0
- data/t/t0113.ru +12 -0
- data/t/t0114-rewindable-input-true.sh +28 -0
- data/t/t0114.ru +12 -0
- data/t/t9100-thread-timeout.sh +24 -2
- data/t/t9101-thread-timeout-threshold.sh +6 -13
- data/t/test-lib.sh +2 -1
- data/t/test_isolate.rb +9 -4
- data/t/times.ru +6 -0
- metadata +109 -42
- data/GIT-VERSION-FILE +0 -1
- data/lib/rainbows/fiber/rev/heartbeat.rb +0 -8
- data/lib/rainbows/fiber/rev/kato.rb +0 -22
- data/lib/rainbows/fiber/rev.rb +0 -13
- data/lib/rainbows/rev/client.rb +0 -194
- data/lib/rainbows/rev/core.rb +0 -41
- data/lib/rainbows/rev/heartbeat.rb +0 -23
- data/lib/rainbows/rev/thread.rb +0 -46
- data/man/man1/rainbows.1 +0 -193
@@ -30,61 +30,67 @@ require 'thread'
|
|
30
30
|
# Timed-out requests will cause this middleware to return with a
|
31
31
|
# "408 Request Timeout" response.
|
32
32
|
|
33
|
-
class Rainbows::ThreadTimeout
|
34
|
-
:threshold, :watchdog,
|
35
|
-
:active, :lock)
|
33
|
+
class Rainbows::ThreadTimeout
|
36
34
|
|
37
35
|
# :stopdoc:
|
38
|
-
class ExecutionExpired <
|
36
|
+
class ExecutionExpired < Exception
|
39
37
|
end
|
40
38
|
|
41
39
|
def initialize(app, opts)
|
42
|
-
timeout = opts[:timeout]
|
43
|
-
Numeric === timeout or
|
44
|
-
raise TypeError, "timeout=#{timeout.inspect} is not numeric"
|
40
|
+
@timeout = opts[:timeout]
|
41
|
+
Numeric === @timeout or
|
42
|
+
raise TypeError, "timeout=#{@timeout.inspect} is not numeric"
|
45
43
|
|
46
|
-
if threshold = opts[:threshold]
|
47
|
-
Integer === threshold or
|
48
|
-
raise TypeError, "threshold=#{threshold.inspect} is not an integer"
|
49
|
-
threshold == 0 and
|
44
|
+
if @threshold = opts[:threshold]
|
45
|
+
Integer === @threshold or
|
46
|
+
raise TypeError, "threshold=#{@threshold.inspect} is not an integer"
|
47
|
+
@threshold == 0 and
|
50
48
|
raise ArgumentError, "threshold=0 does not make sense"
|
51
|
-
threshold < 0 and
|
52
|
-
threshold += Rainbows::G.server.worker_connections
|
49
|
+
@threshold < 0 and
|
50
|
+
@threshold += Rainbows::G.server.worker_connections
|
53
51
|
end
|
54
|
-
|
52
|
+
@app = app
|
53
|
+
@active = {}
|
54
|
+
@lock = Mutex.new
|
55
55
|
end
|
56
56
|
|
57
57
|
def call(env)
|
58
|
-
lock.synchronize do
|
59
|
-
start_watchdog unless watchdog
|
60
|
-
active[Thread.current] = Time.now + timeout
|
58
|
+
@lock.synchronize do
|
59
|
+
start_watchdog unless @watchdog
|
60
|
+
@active[Thread.current] = Time.now + @timeout
|
61
61
|
end
|
62
62
|
begin
|
63
|
-
app.call(env)
|
63
|
+
@app.call(env)
|
64
64
|
ensure
|
65
|
-
lock.synchronize { active.delete(Thread.current) }
|
65
|
+
@lock.synchronize { @active.delete(Thread.current) }
|
66
66
|
end
|
67
67
|
rescue ExecutionExpired
|
68
68
|
[ 408, { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }, [] ]
|
69
69
|
end
|
70
70
|
|
71
71
|
def start_watchdog
|
72
|
-
|
72
|
+
@watchdog = Thread.new do
|
73
73
|
begin
|
74
|
-
if next_wake = lock.synchronize { active.values }.min
|
74
|
+
if next_wake = @lock.synchronize { @active.values }.min
|
75
75
|
next_wake -= Time.now
|
76
|
-
|
76
|
+
|
77
|
+
# because of the lack of GVL-releasing syscalls in this branch
|
78
|
+
# of the thread loop, we need Thread.pass to ensure other threads
|
79
|
+
# get scheduled appropriately under 1.9. This is likely a threading
|
80
|
+
# bug in 1.9 that warrants further investigation when we're in a
|
81
|
+
# better mood.
|
82
|
+
next_wake > 0 ? sleep(next_wake) : Thread.pass
|
77
83
|
else
|
78
|
-
sleep(timeout)
|
84
|
+
sleep(@timeout)
|
79
85
|
end
|
80
86
|
|
81
87
|
# "active.size" is atomic in MRI 1.8 and 1.9
|
82
|
-
next if threshold && active.size < threshold
|
88
|
+
next if @threshold && @active.size < @threshold
|
83
89
|
|
84
90
|
now = Time.now
|
85
|
-
lock.synchronize do
|
86
|
-
active.delete_if do |thread, time|
|
87
|
-
|
91
|
+
@lock.synchronize do
|
92
|
+
@active.delete_if do |thread, time|
|
93
|
+
now >= time and thread.raise(ExecutionExpired).nil?
|
88
94
|
end
|
89
95
|
end
|
90
96
|
end while true
|
data/lib/rainbows/timed_read.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
module Rainbows::TimedRead
|
4
4
|
G = Rainbows::G # :nodoc:
|
5
5
|
|
6
|
+
def read_expire
|
7
|
+
Time.now + G.kato
|
8
|
+
end
|
9
|
+
|
6
10
|
def kgio_wait_readable
|
7
11
|
IO.select([self], nil, nil, G.kato)
|
8
12
|
end
|
@@ -14,7 +18,7 @@ module Rainbows::TimedRead
|
|
14
18
|
case rv = kgio_tryread(16384, buf)
|
15
19
|
when :wait_readable
|
16
20
|
return if expire && expire < Time.now
|
17
|
-
expire ||=
|
21
|
+
expire ||= read_expire
|
18
22
|
kgio_wait_readable
|
19
23
|
else
|
20
24
|
return rv
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::WorkerYield
|
4
|
+
|
5
|
+
# Sleep if we're busy (and let other threads run). Another less busy
|
6
|
+
# worker process may take it for us if we sleep. This is gross but
|
7
|
+
# other options still suck because they require expensive/complicated
|
8
|
+
# synchronization primitives for _every_ case, not just this unlikely
|
9
|
+
# one. Since this case is (or should be) uncommon, just busy wait
|
10
|
+
# when we have to. We don't use Thread.pass because it needlessly
|
11
|
+
# spins the CPU during I/O wait, CPU cycles that can be better used by
|
12
|
+
# other worker _processes_.
|
13
|
+
def worker_yield
|
14
|
+
sleep(0.01)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# used to wrap a BasicSocket to use with +q+ for all writes
|
4
|
+
# this is compatible with IO.select
|
5
|
+
class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
|
6
|
+
include Rainbows::SocketProxy
|
7
|
+
|
8
|
+
def write(buf)
|
9
|
+
q << [ to_io, buf ]
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
q << [ to_io, :close ]
|
14
|
+
end
|
15
|
+
|
16
|
+
def closed?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
@@ -1,105 +1,74 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
|
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
|
-
to_io.kgio_addr
|
31
|
-
end
|
32
|
-
|
33
|
-
def kgio_read(size, buf = "")
|
34
|
-
to_io.kgio_read(size, buf)
|
35
|
-
end
|
36
|
-
|
37
|
-
def kgio_read!(size, buf = "")
|
38
|
-
to_io.kgio_read!(size, buf)
|
3
|
+
# This concurrency model implements a single-threaded app dispatch
|
4
|
+
# with a separate thread pool for writing responses.
|
5
|
+
#
|
6
|
+
# Unlike most \Rainbows! concurrency models, WriterThreadPool is
|
7
|
+
# designed to run behind nginx just like Unicorn is. This concurrency
|
8
|
+
# model may be useful for existing Unicorn users looking for more
|
9
|
+
# output concurrency than socket buffers can provide while still
|
10
|
+
# maintaining a single-threaded application dispatch (though if the
|
11
|
+
# response body is dynamically generated, it must be thread safe).
|
12
|
+
#
|
13
|
+
# For serving large or streaming responses, using more threads (via
|
14
|
+
# the +worker_connections+ setting) and setting "proxy_buffering off"
|
15
|
+
# in nginx is recommended. If your application does not handle
|
16
|
+
# uploads, then using any HTTP-aware proxy like haproxy is fine.
|
17
|
+
# Using a non-HTTP-aware proxy will leave you vulnerable to
|
18
|
+
# slow client denial-of-service attacks.
|
19
|
+
module Rainbows::WriterThreadPool
|
20
|
+
# :stopdoc:
|
21
|
+
include Rainbows::Base
|
22
|
+
|
23
|
+
@@nr = 0
|
24
|
+
@@q = nil
|
25
|
+
|
26
|
+
def async_write_body(qclient, body, range)
|
27
|
+
if body.respond_to?(:close)
|
28
|
+
Rainbows::SyncClose.new(body) do |body|
|
29
|
+
qclient.q << [ qclient.to_io, :body, body, range ]
|
39
30
|
end
|
40
|
-
|
41
|
-
def kgio_trywrite(buf)
|
42
|
-
to_io.kgio_trywrite(buf)
|
43
|
-
end
|
44
|
-
|
45
|
-
def timed_read(buf)
|
46
|
-
to_io.timed_read(buf)
|
47
|
-
end
|
48
|
-
|
49
|
-
def write(buf)
|
50
|
-
q << [ to_io, buf ]
|
51
|
-
end
|
52
|
-
|
53
|
-
def close
|
54
|
-
q << [ to_io, :close ]
|
55
|
-
end
|
56
|
-
|
57
|
-
def closed?
|
58
|
-
false
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
@@nr = 0
|
63
|
-
@@q = nil
|
64
|
-
|
65
|
-
def async_write_body(qclient, body, range)
|
31
|
+
else
|
66
32
|
qclient.q << [ qclient.to_io, :body, body, range ]
|
67
33
|
end
|
34
|
+
end
|
68
35
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
36
|
+
def process_client(client) # :nodoc:
|
37
|
+
@@nr += 1
|
38
|
+
super(Client.new(client, @@q[@@nr %= @@q.size]))
|
39
|
+
end
|
73
40
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
41
|
+
def init_worker_process(worker)
|
42
|
+
super
|
43
|
+
self.class.__send__(:alias_method, :sync_write_body, :write_body)
|
44
|
+
Rainbows::WriterThreadPool.__send__(
|
45
|
+
:alias_method, :write_body, :async_write_body)
|
46
|
+
end
|
79
47
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
end
|
93
|
-
rescue => err
|
94
|
-
Error.write(io, err)
|
48
|
+
def worker_loop(worker) # :nodoc:
|
49
|
+
# we have multiple, single-thread queues since we don't want to
|
50
|
+
# interleave writes from the same client
|
51
|
+
qp = (1..worker_connections).map do |n|
|
52
|
+
Rainbows::QueuePool.new(1) do |response|
|
53
|
+
begin
|
54
|
+
io, arg1, arg2, arg3 = response
|
55
|
+
case arg1
|
56
|
+
when :body then sync_write_body(io, arg2, arg3)
|
57
|
+
when :close then io.close unless io.closed?
|
58
|
+
else
|
59
|
+
io.write(arg1)
|
95
60
|
end
|
61
|
+
rescue => err
|
62
|
+
Rainbows::Error.write(io, err)
|
96
63
|
end
|
97
64
|
end
|
98
|
-
|
99
|
-
@@q = qp.map { |q| q.queue }
|
100
|
-
super(worker) # accept loop from Unicorn
|
101
|
-
qp.map { |q| q.quit! }
|
102
65
|
end
|
103
|
-
|
66
|
+
|
67
|
+
@@q = qp.map { |q| q.queue }
|
68
|
+
super(worker) # accept loop from Unicorn
|
69
|
+
qp.map { |q| q.quit! }
|
104
70
|
end
|
71
|
+
# :startdoc:
|
105
72
|
end
|
73
|
+
# :enddoc:
|
74
|
+
require 'rainbows/writer_thread_pool/client'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# used to wrap a BasicSocket to use with +q+ for all writes
|
4
|
+
# this is compatible with IO.select
|
5
|
+
class Rainbows::WriterThreadSpawn::Client < Struct.new(:to_io, :q, :thr)
|
6
|
+
include Rainbows::Response
|
7
|
+
include Rainbows::SocketProxy
|
8
|
+
include Rainbows::WorkerYield
|
9
|
+
|
10
|
+
CUR = {} # :nodoc:
|
11
|
+
|
12
|
+
def self.quit
|
13
|
+
g = Rainbows::G
|
14
|
+
CUR.delete_if do |t,q|
|
15
|
+
q << nil
|
16
|
+
g.tick
|
17
|
+
t.alive? ? t.join(0.01) : true
|
18
|
+
end until CUR.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def queue_writer
|
22
|
+
until CUR.size < MAX
|
23
|
+
CUR.delete_if { |t,_|
|
24
|
+
t.alive? ? t.join(0) : true
|
25
|
+
}.size >= MAX and worker_yield
|
26
|
+
end
|
27
|
+
|
28
|
+
q = Queue.new
|
29
|
+
self.thr = Thread.new(to_io, q) do |io, q|
|
30
|
+
while response = q.shift
|
31
|
+
begin
|
32
|
+
arg1, arg2, arg3 = response
|
33
|
+
case arg1
|
34
|
+
when :body then write_body(io, arg2, arg3)
|
35
|
+
when :close
|
36
|
+
io.close unless io.closed?
|
37
|
+
break
|
38
|
+
else
|
39
|
+
io.write(arg1)
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
Rainbows::Error.write(io, e)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
CUR.delete(Thread.current)
|
46
|
+
end
|
47
|
+
CUR[thr] = q
|
48
|
+
end
|
49
|
+
|
50
|
+
def write(buf)
|
51
|
+
(self.q ||= queue_writer) << buf
|
52
|
+
end
|
53
|
+
|
54
|
+
def queue_body(body, range)
|
55
|
+
(self.q ||= queue_writer) << [ :body, body, range ]
|
56
|
+
end
|
57
|
+
|
58
|
+
def close
|
59
|
+
if q
|
60
|
+
q << :close
|
61
|
+
else
|
62
|
+
to_io.close
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def closed?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
@@ -1,125 +1,45 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'thread'
|
3
|
-
|
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
|
-
# used to wrap a BasicSocket to use with +q+ for all writes
|
30
|
-
# this is compatible with IO.select
|
31
|
-
class MySocket < Struct.new(:to_io, :q, :thr) # :nodoc: all
|
32
|
-
include Rainbows::Response
|
33
|
-
|
34
|
-
def kgio_addr
|
35
|
-
to_io.kgio_addr
|
36
|
-
end
|
37
|
-
|
38
|
-
def kgio_read(size, buf = "")
|
39
|
-
to_io.kgio_read(size, buf)
|
40
|
-
end
|
41
|
-
|
42
|
-
def kgio_read!(size, buf = "")
|
43
|
-
to_io.kgio_read!(size, buf)
|
44
|
-
end
|
45
|
-
|
46
|
-
def kgio_trywrite(buf)
|
47
|
-
to_io.kgio_trywrite(buf)
|
48
|
-
end
|
49
|
-
|
50
|
-
def timed_read(buf)
|
51
|
-
to_io.timed_read(buf)
|
52
|
-
end
|
53
|
-
|
54
|
-
def queue_writer
|
55
|
-
# not using Thread.pass here because that spins the CPU during
|
56
|
-
# I/O wait and will eat cycles from other worker processes.
|
57
|
-
until CUR.size < MAX
|
58
|
-
CUR.delete_if { |t,_|
|
59
|
-
t.alive? ? t.join(0) : true
|
60
|
-
}.size >= MAX and sleep(0.01)
|
61
|
-
end
|
62
|
-
|
63
|
-
q = Queue.new
|
64
|
-
self.thr = Thread.new(to_io, q) do |io, q|
|
65
|
-
while response = q.shift
|
66
|
-
begin
|
67
|
-
arg1, arg2, arg3 = response
|
68
|
-
case arg1
|
69
|
-
when :body then write_body(io, arg2, arg3)
|
70
|
-
when :close
|
71
|
-
io.close unless io.closed?
|
72
|
-
break
|
73
|
-
else
|
74
|
-
io.write(arg1)
|
75
|
-
end
|
76
|
-
rescue => e
|
77
|
-
Error.write(io, e)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
CUR.delete(Thread.current)
|
81
|
-
end
|
82
|
-
CUR[thr] = q
|
83
|
-
end
|
84
|
-
|
85
|
-
def write(buf)
|
86
|
-
(self.q ||= queue_writer) << buf
|
87
|
-
end
|
88
|
-
|
89
|
-
def queue_body(body, range)
|
90
|
-
(self.q ||= queue_writer) << [ :body, body, range ]
|
91
|
-
end
|
92
|
-
|
93
|
-
def close
|
94
|
-
if q
|
95
|
-
q << :close
|
96
|
-
else
|
97
|
-
to_io.close
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def closed?
|
102
|
-
false
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def write_body(my_sock, body, range) # :nodoc:
|
3
|
+
# This concurrency model implements a single-threaded app dispatch and
|
4
|
+
# spawns a new thread for writing responses. This concurrency model
|
5
|
+
# should be ideal for apps that serve large responses or stream
|
6
|
+
# responses slowly.
|
7
|
+
#
|
8
|
+
# Unlike most \Rainbows! concurrency models, WriterThreadSpawn is
|
9
|
+
# designed to run behind nginx just like Unicorn is. This concurrency
|
10
|
+
# model may be useful for existing Unicorn users looking for more
|
11
|
+
# output concurrency than socket buffers can provide while still
|
12
|
+
# maintaining a single-threaded application dispatch (though if the
|
13
|
+
# response body is generated on-the-fly, it must be thread safe).
|
14
|
+
#
|
15
|
+
# For serving large or streaming responses, setting
|
16
|
+
# "proxy_buffering off" in nginx is recommended. If your application
|
17
|
+
# does not handle uploads, then using any HTTP-aware proxy like
|
18
|
+
# haproxy is fine. Using a non-HTTP-aware proxy will leave you
|
19
|
+
# vulnerable to slow client denial-of-service attacks.
|
20
|
+
|
21
|
+
module Rainbows::WriterThreadSpawn
|
22
|
+
# :stopdoc:
|
23
|
+
include Rainbows::Base
|
24
|
+
|
25
|
+
def write_body(my_sock, body, range) # :nodoc:
|
26
|
+
if body.respond_to?(:close)
|
27
|
+
Rainbows::SyncClose.new(body) { |body| my_sock.queue_body(body, range) }
|
28
|
+
else
|
107
29
|
my_sock.queue_body(body, range)
|
108
30
|
end
|
31
|
+
end
|
109
32
|
|
110
|
-
|
111
|
-
|
112
|
-
|
33
|
+
def process_client(client) # :nodoc:
|
34
|
+
super(Client.new(client))
|
35
|
+
end
|
113
36
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
q << nil
|
119
|
-
G.tick
|
120
|
-
t.alive? ? t.join(0.01) : true
|
121
|
-
end until CUR.empty?
|
122
|
-
end
|
123
|
-
# :startdoc:
|
37
|
+
def worker_loop(worker) # :nodoc:
|
38
|
+
Client.const_set(:MAX, worker_connections)
|
39
|
+
super # accept loop from Unicorn
|
40
|
+
Client.quit
|
124
41
|
end
|
42
|
+
# :startdoc:
|
125
43
|
end
|
44
|
+
# :enddoc:
|
45
|
+
require 'rainbows/writer_thread_spawn/client'
|
data/lib/rainbows.rb
CHANGED
@@ -46,20 +46,22 @@ module Rainbows
|
|
46
46
|
autoload :DevFdResponse, 'rainbows/dev_fd_response'
|
47
47
|
autoload :MaxBody, 'rainbows/max_body'
|
48
48
|
autoload :QueuePool, 'rainbows/queue_pool'
|
49
|
+
autoload :EvCore, 'rainbows/ev_core'
|
50
|
+
autoload :SocketProxy, 'rainbows/socket_proxy'
|
49
51
|
|
50
52
|
class << self
|
51
53
|
|
52
54
|
# Sleeps the current application dispatch. This will pick the
|
53
55
|
# optimal method to sleep depending on the concurrency model chosen
|
54
56
|
# (which may still suck and block the entire process). Using this
|
55
|
-
# with the basic :
|
57
|
+
# with the basic :Coolio or :EventMachine models is not recommended.
|
56
58
|
# This should be used within your Rack application.
|
57
59
|
def sleep(nr)
|
58
60
|
case G.server.use
|
59
61
|
when :FiberPool, :FiberSpawn
|
60
62
|
Rainbows::Fiber.sleep(nr)
|
61
|
-
when :RevFiberSpawn
|
62
|
-
Rainbows::Fiber::
|
63
|
+
when :RevFiberSpawn, :CoolioFiberSpawn
|
64
|
+
Rainbows::Fiber::Coolio::Sleeper.new(nr)
|
63
65
|
when :Revactor
|
64
66
|
Actor.sleep(nr)
|
65
67
|
else
|
@@ -96,12 +98,16 @@ module Rainbows
|
|
96
98
|
:Rev => 50,
|
97
99
|
:RevThreadSpawn => 50,
|
98
100
|
:RevThreadPool => 50,
|
101
|
+
:RevFiberSpawn => 50,
|
102
|
+
:Coolio => 50,
|
103
|
+
:CoolioThreadSpawn => 50,
|
104
|
+
:CoolioThreadPool => 50,
|
105
|
+
:CoolioFiberSpawn => 50,
|
99
106
|
:EventMachine => 50,
|
100
107
|
:FiberSpawn => 50,
|
101
108
|
:FiberPool => 50,
|
102
109
|
:ActorSpawn => 50,
|
103
110
|
:NeverBlock => 50,
|
104
|
-
:RevFiberSpawn => 50,
|
105
111
|
}.each do |model, _|
|
106
112
|
u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" }
|
107
113
|
autoload model, "rainbows/#{u.downcase!}"
|
@@ -111,6 +117,8 @@ module Rainbows
|
|
111
117
|
autoload :StreamFile, 'rainbows/stream_file'
|
112
118
|
autoload :HttpResponse, 'rainbows/http_response' # deprecated
|
113
119
|
autoload :ThreadTimeout, 'rainbows/thread_timeout'
|
120
|
+
autoload :WorkerYield, 'rainbows/worker_yield'
|
121
|
+
autoload :SyncClose, 'rainbows/sync_close'
|
114
122
|
end
|
115
123
|
|
116
124
|
require 'rainbows/error'
|
data/rainbows.gemspec
CHANGED
@@ -9,32 +9,24 @@ manifest = File.readlines('.manifest').map! { |x| x.chomp! }
|
|
9
9
|
test_files = manifest.grep(%r{\Atest/unit/test_.*\.rb\z}).map do |f|
|
10
10
|
File.readlines(f).grep(/\bfork\b/).empty? ? f : nil
|
11
11
|
end.compact
|
12
|
+
require 'wrongdoc'
|
13
|
+
extend Wrongdoc::Gemspec
|
14
|
+
name, summary, title = readme_metadata
|
12
15
|
|
13
16
|
Gem::Specification.new do |s|
|
14
17
|
s.name = %q{rainbows}
|
15
|
-
s.version = ENV["VERSION"]
|
18
|
+
s.version = ENV["VERSION"].dup
|
16
19
|
|
17
|
-
s.authors = ["
|
20
|
+
s.authors = ["#{name} hackers"]
|
18
21
|
s.date = Time.now.utc.strftime('%Y-%m-%d')
|
19
|
-
s.description =
|
22
|
+
s.description = readme_description
|
20
23
|
s.email = %q{rainbows-talk@rubyforge.org}
|
21
24
|
s.executables = %w(rainbows)
|
22
|
-
|
23
|
-
s.extra_rdoc_files = File.readlines('.document').map! do |x|
|
24
|
-
x.chomp!
|
25
|
-
if File.directory?(x)
|
26
|
-
manifest.grep(%r{\A#{x}/})
|
27
|
-
elsif File.file?(x)
|
28
|
-
x
|
29
|
-
else
|
30
|
-
nil
|
31
|
-
end
|
32
|
-
end.flatten.compact
|
33
|
-
|
25
|
+
s.extra_rdoc_files = extra_rdoc_files(manifest)
|
34
26
|
s.files = manifest
|
35
|
-
s.homepage =
|
36
|
-
s.summary =
|
37
|
-
s.rdoc_options =
|
27
|
+
s.homepage = Wrongdoc.config[:rdoc_url]
|
28
|
+
s.summary = summary
|
29
|
+
s.rdoc_options = rdoc_options
|
38
30
|
s.require_paths = %w(lib)
|
39
31
|
s.rubyforge_project = %q{rainbows}
|
40
32
|
|
@@ -44,8 +36,9 @@ Gem::Specification.new do |s|
|
|
44
36
|
s.add_dependency(%q<rack>, ['~> 1.1'])
|
45
37
|
|
46
38
|
# we need Unicorn for the HTTP parser and process management
|
47
|
-
s.add_dependency(%q<unicorn>, ["~> 3.
|
39
|
+
s.add_dependency(%q<unicorn>, ["~> 3.2"])
|
48
40
|
s.add_development_dependency(%q<isolate>, "~> 3.0.0")
|
41
|
+
s.add_development_dependency(%q<wrongdoc>, "~> 1.0.1")
|
49
42
|
|
50
43
|
# optional runtime dependencies depending on configuration
|
51
44
|
# see t/test_isolate.rb for the exact versions we've tested with
|
@@ -56,6 +49,9 @@ Gem::Specification.new do |s|
|
|
56
49
|
# Revactor depends on Rev, too, 0.3.0 got the ability to attach IOs
|
57
50
|
# s.add_dependency(%q<rev>, [">= 0.3.2"])
|
58
51
|
#
|
52
|
+
# Cool.io is the new Rev, but it doesn't work with Revactor
|
53
|
+
# s.add_dependency(%q<cool.io>, [">= 1.0"])
|
54
|
+
#
|
59
55
|
# Rev depends on IOBuffer, which got faster in 0.1.3
|
60
56
|
# s.add_dependency(%q<iobuffer>, [">= 0.1.3"])
|
61
57
|
#
|