rainbows 2.0.1 → 2.1.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/.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
|
#
|