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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
# A combination of the Coolio and ThreadPool models. This allows Ruby
|
|
4
|
+
# Thread-based concurrency for application processing. It DOES NOT
|
|
5
|
+
# expose a streamable "rack.input" for upload processing within the
|
|
6
|
+
# app. DevFdResponse should be used with this class to proxy
|
|
7
|
+
# asynchronous responses. All network I/O between the client and
|
|
8
|
+
# server are handled by the main thread and outside of the core
|
|
9
|
+
# application dispatch.
|
|
10
|
+
#
|
|
11
|
+
# Unlike ThreadPool, Cool.io makes this model highly suitable for
|
|
12
|
+
# slow clients and applications with medium-to-slow response times
|
|
13
|
+
# (I/O bound), but less suitable for sleepy applications.
|
|
14
|
+
#
|
|
15
|
+
# This concurrency model is designed for Ruby 1.9, and Ruby 1.8
|
|
16
|
+
# users are NOT advised to use this due to high CPU usage.
|
|
17
|
+
module Rainbows::CoolioThreadPool
|
|
18
|
+
# :stopdoc:
|
|
19
|
+
DEFAULTS = {
|
|
20
|
+
:pool_size => 20, # same default size as ThreadPool (w/o Coolio)
|
|
21
|
+
}
|
|
22
|
+
#:startdoc:
|
|
23
|
+
|
|
24
|
+
def self.setup # :nodoc:
|
|
25
|
+
o = Rainbows::O
|
|
26
|
+
DEFAULTS.each { |k,v| o[k] ||= v }
|
|
27
|
+
Integer === o[:pool_size] && o[:pool_size] > 0 or
|
|
28
|
+
raise ArgumentError, "pool_size must a be an Integer > 0"
|
|
29
|
+
end
|
|
30
|
+
include Rainbows::Coolio::Core
|
|
31
|
+
|
|
32
|
+
def init_worker_threads(master, queue) # :nodoc:
|
|
33
|
+
Rainbows::O[:pool_size].times.map do
|
|
34
|
+
Thread.new do
|
|
35
|
+
begin
|
|
36
|
+
client = queue.pop
|
|
37
|
+
master << [ client, client.app_response ]
|
|
38
|
+
rescue => e
|
|
39
|
+
Rainbows::Error.listen_loop(e)
|
|
40
|
+
end while true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def init_worker_process(worker) # :nodoc:
|
|
46
|
+
super
|
|
47
|
+
cloop = Coolio::Loop.default
|
|
48
|
+
master = Rainbows::Coolio::Master.new(Queue.new).attach(cloop)
|
|
49
|
+
queue = Client.const_set(:QUEUE, Queue.new)
|
|
50
|
+
threads = init_worker_threads(master, queue)
|
|
51
|
+
Watcher.new(threads).attach(cloop)
|
|
52
|
+
logger.info "CoolioThreadPool pool_size=#{Rainbows::O[:pool_size]}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
# :enddoc:
|
|
56
|
+
require 'rainbows/coolio_thread_pool/client'
|
|
57
|
+
require 'rainbows/coolio_thread_pool/watcher'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# A combination of the Coolio and ThreadSpawn models. This allows Ruby
|
|
3
|
+
# Thread-based concurrency for application processing. It DOES NOT
|
|
4
|
+
# expose a streamable "rack.input" for upload processing within the
|
|
5
|
+
# app. DevFdResponse should be used with this class to proxy
|
|
6
|
+
# asynchronous responses. All network I/O between the client and
|
|
7
|
+
# server are handled by the main thread and outside of the core
|
|
8
|
+
# application dispatch.
|
|
9
|
+
#
|
|
10
|
+
# Unlike ThreadSpawn, Cool.io makes this model highly suitable for
|
|
11
|
+
# slow clients and applications with medium-to-slow response times
|
|
12
|
+
# (I/O bound), but less suitable for sleepy applications.
|
|
13
|
+
#
|
|
14
|
+
# This concurrency model is designed for Ruby 1.9, and Ruby 1.8
|
|
15
|
+
# users are NOT advised to use this due to high CPU usage.
|
|
16
|
+
module Rainbows::CoolioThreadSpawn
|
|
17
|
+
include Rainbows::Coolio::Core
|
|
18
|
+
|
|
19
|
+
def init_worker_process(worker) # :nodoc:
|
|
20
|
+
super
|
|
21
|
+
master = Rainbows::Coolio::Master.new(Queue.new)
|
|
22
|
+
master.attach(Coolio::Loop.default)
|
|
23
|
+
Client.const_set(:MASTER, master)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
# :enddoc:
|
|
27
|
+
require 'rainbows/coolio_thread_spawn/client'
|
|
@@ -29,7 +29,11 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
|
29
29
|
status, headers, body = response = app.call(env)
|
|
30
30
|
|
|
31
31
|
# totally uninteresting to us if there's no body
|
|
32
|
-
|
|
32
|
+
if STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) ||
|
|
33
|
+
File === body ||
|
|
34
|
+
(body.respond_to?(:to_path) && File.file?(body.to_path))
|
|
35
|
+
return response
|
|
36
|
+
end
|
|
33
37
|
|
|
34
38
|
io = body.to_io if body.respond_to?(:to_io)
|
|
35
39
|
io ||= File.open(body.to_path) if body.respond_to?(:to_path)
|
|
@@ -53,7 +57,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
|
53
57
|
|
|
54
58
|
# we need to make sure our pipe output is Fiber-compatible
|
|
55
59
|
case env["rainbows.model"]
|
|
56
|
-
when :FiberSpawn, :FiberPool, :RevFiberSpawn
|
|
60
|
+
when :FiberSpawn, :FiberPool, :RevFiberSpawn, :CoolioFiberSpawn
|
|
57
61
|
io.respond_to?(:kgio_wait_readable) or
|
|
58
62
|
io = Rainbows::Fiber::IO.new(io)
|
|
59
63
|
when :Revactor
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
2
|
# :enddoc:
|
|
3
3
|
class Rainbows::EvCore::CapInput
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
def initialize(io, client, max)
|
|
6
|
+
@io, @client, @bytes_left = io, client, max
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
def <<(buf)
|
data/lib/rainbows/ev_core.rb
CHANGED
|
@@ -86,6 +86,8 @@ module Rainbows::EvCore
|
|
|
86
86
|
@hp.filter_body(@buf2, @buf << data)
|
|
87
87
|
@input << @buf2
|
|
88
88
|
on_read("")
|
|
89
|
+
else
|
|
90
|
+
want_more
|
|
89
91
|
end
|
|
90
92
|
when :trailers
|
|
91
93
|
if @hp.trailers(@env, @buf << data)
|
|
@@ -106,8 +108,12 @@ module Rainbows::EvCore
|
|
|
106
108
|
raise IOError, msg, []
|
|
107
109
|
end
|
|
108
110
|
|
|
109
|
-
MAX_BODY = Unicorn::Const::MAX_BODY
|
|
110
111
|
TmpIO = Unicorn::TmpIO
|
|
112
|
+
|
|
113
|
+
def io_for(bytes)
|
|
114
|
+
bytes <= CBB ? StringIO.new("") : TmpIO.new
|
|
115
|
+
end
|
|
116
|
+
|
|
111
117
|
def mkinput
|
|
112
118
|
max = Rainbows.max_bytes
|
|
113
119
|
len = @hp.content_length
|
|
@@ -115,9 +121,13 @@ module Rainbows::EvCore
|
|
|
115
121
|
if max && (len > max)
|
|
116
122
|
err_413("Content-Length too big: #{len} > #{max}")
|
|
117
123
|
end
|
|
118
|
-
len
|
|
124
|
+
io_for(len)
|
|
119
125
|
else
|
|
120
|
-
max ? CapInput.new(
|
|
126
|
+
max ? CapInput.new(io_for(max), self, max) : TmpIO.new
|
|
121
127
|
end
|
|
122
128
|
end
|
|
129
|
+
|
|
130
|
+
def self.setup
|
|
131
|
+
const_set :CBB, Unicorn::TeeInput.client_body_buffer_size
|
|
132
|
+
end
|
|
123
133
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
class Rainbows::EventMachine::Client < EM::Connection
|
|
4
|
+
attr_writer :body
|
|
5
|
+
include Rainbows::EvCore
|
|
6
|
+
|
|
7
|
+
def initialize(io)
|
|
8
|
+
@_io = io
|
|
9
|
+
@body = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
alias write send_data
|
|
13
|
+
|
|
14
|
+
def receive_data(data)
|
|
15
|
+
# To avoid clobbering the current streaming response
|
|
16
|
+
# (often a static file), we do not attempt to process another
|
|
17
|
+
# request on the same connection until the first is complete
|
|
18
|
+
if @body
|
|
19
|
+
if data
|
|
20
|
+
@buf << data
|
|
21
|
+
@_io.shutdown(Socket::SHUT_RD) if @buf.size > 0x1c000
|
|
22
|
+
end
|
|
23
|
+
EM.next_tick { receive_data(nil) } unless @buf.empty?
|
|
24
|
+
else
|
|
25
|
+
on_read(data || "") if (@buf.size > 0) || data
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def quit
|
|
30
|
+
super
|
|
31
|
+
close_connection_after_writing
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def app_call
|
|
35
|
+
set_comm_inactivity_timeout 0
|
|
36
|
+
@env[RACK_INPUT] = @input
|
|
37
|
+
@env[REMOTE_ADDR] = @_io.kgio_addr
|
|
38
|
+
@env[ASYNC_CALLBACK] = method(:em_write_response)
|
|
39
|
+
@env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
|
|
40
|
+
|
|
41
|
+
response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
|
|
42
|
+
|
|
43
|
+
# too tricky to support pipelining with :async since the
|
|
44
|
+
# second (pipelined) request could be a stuck behind a
|
|
45
|
+
# long-running async response
|
|
46
|
+
(response.nil? || -1 == response[0]) and return @state = :close
|
|
47
|
+
|
|
48
|
+
if @hp.next? && G.alive && G.kato > 0
|
|
49
|
+
@state = :headers
|
|
50
|
+
em_write_response(response, true)
|
|
51
|
+
if @buf.empty?
|
|
52
|
+
set_comm_inactivity_timeout(G.kato)
|
|
53
|
+
elsif @body.nil?
|
|
54
|
+
EM.next_tick { receive_data(nil) }
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
em_write_response(response, false)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def em_write_response(response, alive = false)
|
|
62
|
+
status, headers, body = response
|
|
63
|
+
if @hp.headers?
|
|
64
|
+
headers = HH.new(headers)
|
|
65
|
+
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
|
66
|
+
else
|
|
67
|
+
headers = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if body.respond_to?(:errback) && body.respond_to?(:callback)
|
|
71
|
+
@body = body
|
|
72
|
+
body.callback { quit }
|
|
73
|
+
body.errback { quit }
|
|
74
|
+
# async response, this could be a trickle as is in comet-style apps
|
|
75
|
+
headers[CONNECTION] = CLOSE if headers
|
|
76
|
+
alive = true
|
|
77
|
+
elsif body.respond_to?(:to_path)
|
|
78
|
+
st = File.stat(path = body.to_path)
|
|
79
|
+
|
|
80
|
+
if st.file?
|
|
81
|
+
write(response_header(status, headers)) if headers
|
|
82
|
+
@body = stream_file_data(path)
|
|
83
|
+
@body.errback do
|
|
84
|
+
body.close if body.respond_to?(:close)
|
|
85
|
+
quit
|
|
86
|
+
end
|
|
87
|
+
@body.callback do
|
|
88
|
+
body.close if body.respond_to?(:close)
|
|
89
|
+
@body = nil
|
|
90
|
+
alive ? receive_data(nil) : quit
|
|
91
|
+
end
|
|
92
|
+
return
|
|
93
|
+
elsif st.socket? || st.pipe?
|
|
94
|
+
@body = io = body_to_io(body)
|
|
95
|
+
chunk = stream_response_headers(status, headers) if headers
|
|
96
|
+
m = chunk ? Rainbows::EventMachine::ResponseChunkPipe :
|
|
97
|
+
Rainbows::EventMachine::ResponsePipe
|
|
98
|
+
return EM.watch(io, m, self, alive, body).notify_readable = true
|
|
99
|
+
end
|
|
100
|
+
# char or block device... WTF? fall through to body.each
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
write(response_header(status, headers)) if headers
|
|
104
|
+
write_body_each(self, body)
|
|
105
|
+
quit unless alive
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def next!
|
|
109
|
+
@hp.keepalive? ? receive_data(@body = nil) : quit
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def unbind
|
|
113
|
+
async_close = @env[ASYNC_CLOSE] and async_close.succeed
|
|
114
|
+
@body.respond_to?(:fail) and @body.fail
|
|
115
|
+
begin
|
|
116
|
+
@_io.close
|
|
117
|
+
rescue Errno::EBADF
|
|
118
|
+
# EventMachine's EventableDescriptor::Close() may close
|
|
119
|
+
# the underlying file descriptor without invalidating the
|
|
120
|
+
# associated IO object on errors, so @_io.closed? isn't
|
|
121
|
+
# sufficient.
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
module Rainbows::EventMachine::Server # :nodoc: all
|
|
3
|
+
def close
|
|
4
|
+
detach
|
|
5
|
+
@io.close
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# CL, CUR and MAX will be set when worker_loop starts
|
|
9
|
+
def notify_readable
|
|
10
|
+
return if CUR.size >= MAX
|
|
11
|
+
io = @io.kgio_tryaccept or return
|
|
12
|
+
sig = EM.attach_fd(io.fileno, false)
|
|
13
|
+
CUR[sig] = CL.new(sig, io)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
2
|
require 'eventmachine'
|
|
3
3
|
EM::VERSION >= '0.12.10' or abort 'eventmachine 0.12.10 is required'
|
|
4
|
-
require 'rainbows/ev_core'
|
|
5
4
|
|
|
6
5
|
# Implements a basic single-threaded event model with
|
|
7
6
|
# {EventMachine}[http://rubyeventmachine.com/]. It is capable of
|
|
@@ -42,141 +41,11 @@ require 'rainbows/ev_core'
|
|
|
42
41
|
# "rack.input" will be fully buffered in memory or to a temporary file
|
|
43
42
|
# before the application is entered.
|
|
44
43
|
module Rainbows::EventMachine
|
|
45
|
-
|
|
46
|
-
include Rainbows::Base
|
|
47
44
|
autoload :ResponsePipe, 'rainbows/event_machine/response_pipe'
|
|
48
45
|
autoload :ResponseChunkPipe, 'rainbows/event_machine/response_chunk_pipe'
|
|
49
46
|
autoload :TryDefer, 'rainbows/event_machine/try_defer'
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
attr_writer :body
|
|
53
|
-
include Rainbows::EvCore
|
|
54
|
-
|
|
55
|
-
def initialize(io)
|
|
56
|
-
@_io = io
|
|
57
|
-
@body = nil
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
alias write send_data
|
|
61
|
-
|
|
62
|
-
def receive_data(data)
|
|
63
|
-
# To avoid clobbering the current streaming response
|
|
64
|
-
# (often a static file), we do not attempt to process another
|
|
65
|
-
# request on the same connection until the first is complete
|
|
66
|
-
if @body
|
|
67
|
-
@buf << data
|
|
68
|
-
@_io.shutdown(Socket::SHUT_RD) if @buf.size > 0x1c000
|
|
69
|
-
EM.next_tick { receive_data('') }
|
|
70
|
-
else
|
|
71
|
-
on_read(data)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def quit
|
|
76
|
-
super
|
|
77
|
-
close_connection_after_writing
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def app_call
|
|
81
|
-
set_comm_inactivity_timeout 0
|
|
82
|
-
@env[RACK_INPUT] = @input
|
|
83
|
-
@env[REMOTE_ADDR] = @_io.kgio_addr
|
|
84
|
-
@env[ASYNC_CALLBACK] = method(:em_write_response)
|
|
85
|
-
@env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
|
|
86
|
-
|
|
87
|
-
response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
|
|
88
|
-
|
|
89
|
-
# too tricky to support pipelining with :async since the
|
|
90
|
-
# second (pipelined) request could be a stuck behind a
|
|
91
|
-
# long-running async response
|
|
92
|
-
(response.nil? || -1 == response[0]) and return @state = :close
|
|
93
|
-
|
|
94
|
-
alive = @hp.keepalive? && G.alive && G.kato > 0
|
|
95
|
-
em_write_response(response, alive)
|
|
96
|
-
if alive
|
|
97
|
-
@hp.reset
|
|
98
|
-
@state = :headers
|
|
99
|
-
if @buf.empty?
|
|
100
|
-
set_comm_inactivity_timeout(G.kato)
|
|
101
|
-
else
|
|
102
|
-
EM.next_tick { receive_data('') }
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def em_write_response(response, alive = false)
|
|
108
|
-
status, headers, body = response
|
|
109
|
-
if @hp.headers?
|
|
110
|
-
headers = HH.new(headers)
|
|
111
|
-
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
|
112
|
-
else
|
|
113
|
-
headers = nil
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
if body.respond_to?(:errback) && body.respond_to?(:callback)
|
|
117
|
-
@body = body
|
|
118
|
-
body.callback { quit }
|
|
119
|
-
body.errback { quit }
|
|
120
|
-
# async response, this could be a trickle as is in comet-style apps
|
|
121
|
-
headers[CONNECTION] = CLOSE if headers
|
|
122
|
-
alive = true
|
|
123
|
-
elsif body.respond_to?(:to_path)
|
|
124
|
-
st = File.stat(path = body.to_path)
|
|
125
|
-
|
|
126
|
-
if st.file?
|
|
127
|
-
write(response_header(status, headers)) if headers
|
|
128
|
-
@body = stream_file_data(path)
|
|
129
|
-
@body.errback do
|
|
130
|
-
body.close if body.respond_to?(:close)
|
|
131
|
-
quit
|
|
132
|
-
end
|
|
133
|
-
@body.callback do
|
|
134
|
-
body.close if body.respond_to?(:close)
|
|
135
|
-
@body = nil
|
|
136
|
-
alive ? receive_data('') : quit
|
|
137
|
-
end
|
|
138
|
-
return
|
|
139
|
-
elsif st.socket? || st.pipe?
|
|
140
|
-
@body = io = body_to_io(body)
|
|
141
|
-
chunk = stream_response_headers(status, headers) if headers
|
|
142
|
-
m = chunk ? ResponseChunkPipe : ResponsePipe
|
|
143
|
-
return EM.watch(io, m, self, alive, body).notify_readable = true
|
|
144
|
-
end
|
|
145
|
-
# char or block device... WTF? fall through to body.each
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
write(response_header(status, headers)) if headers
|
|
149
|
-
write_body_each(self, body)
|
|
150
|
-
quit unless alive
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def unbind
|
|
154
|
-
async_close = @env[ASYNC_CLOSE] and async_close.succeed
|
|
155
|
-
@body.respond_to?(:fail) and @body.fail
|
|
156
|
-
begin
|
|
157
|
-
@_io.close
|
|
158
|
-
rescue Errno::EBADF
|
|
159
|
-
# EventMachine's EventableDescriptor::Close() may close
|
|
160
|
-
# the underlying file descriptor without invalidating the
|
|
161
|
-
# associated IO object on errors, so @_io.closed? isn't
|
|
162
|
-
# sufficient.
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
module Server # :nodoc: all
|
|
168
|
-
def close
|
|
169
|
-
detach
|
|
170
|
-
@io.close
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def notify_readable
|
|
174
|
-
return if CUR.size >= MAX
|
|
175
|
-
io = @io.kgio_tryaccept or return
|
|
176
|
-
sig = EM.attach_fd(io.fileno, false)
|
|
177
|
-
CUR[sig] = CL.new(sig, io)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
48
|
+
include Rainbows::Base
|
|
180
49
|
|
|
181
50
|
def init_worker_process(worker) # :nodoc:
|
|
182
51
|
Rainbows::Response.setup(Rainbows::EventMachine::Client)
|
|
@@ -189,20 +58,22 @@ module Rainbows::EventMachine
|
|
|
189
58
|
def worker_loop(worker) # :nodoc:
|
|
190
59
|
init_worker_process(worker)
|
|
191
60
|
G.server.app.respond_to?(:deferred?) and
|
|
192
|
-
G.server.app = TryDefer[G.server.app]
|
|
61
|
+
G.server.app = Rainbows::EventMachine::TryDefer[G.server.app]
|
|
193
62
|
|
|
194
63
|
# enable them both, should be non-fatal if not supported
|
|
195
64
|
EM.epoll
|
|
196
65
|
EM.kqueue
|
|
197
66
|
logger.info "#@use: epoll=#{EM.epoll?} kqueue=#{EM.kqueue?}"
|
|
198
67
|
client_class = Rainbows.const_get(@use).const_get(:Client)
|
|
199
|
-
|
|
200
|
-
Server.const_set(:
|
|
68
|
+
max = worker_connections + LISTENERS.size
|
|
69
|
+
Rainbows::EventMachine::Server.const_set(:MAX, max)
|
|
70
|
+
Rainbows::EventMachine::Server.const_set(:CL, client_class)
|
|
201
71
|
client_class.const_set(:APP, G.server.app)
|
|
72
|
+
Rainbows::EvCore.setup
|
|
202
73
|
EM.run {
|
|
203
74
|
conns = EM.instance_variable_get(:@conns) or
|
|
204
75
|
raise RuntimeError, "EM @conns instance variable not accessible!"
|
|
205
|
-
Server.const_set(:CUR, conns)
|
|
76
|
+
Rainbows::EventMachine::Server.const_set(:CUR, conns)
|
|
206
77
|
EM.add_periodic_timer(1) do
|
|
207
78
|
unless G.tick
|
|
208
79
|
conns.each_value { |c| client_class === c and c.quit }
|
|
@@ -210,8 +81,13 @@ module Rainbows::EventMachine
|
|
|
210
81
|
end
|
|
211
82
|
end
|
|
212
83
|
LISTENERS.map! do |s|
|
|
213
|
-
EM.watch(s, Server)
|
|
84
|
+
EM.watch(s, Rainbows::EventMachine::Server) do |c|
|
|
85
|
+
c.notify_readable = true
|
|
86
|
+
end
|
|
214
87
|
end
|
|
215
88
|
}
|
|
216
89
|
end
|
|
217
90
|
end
|
|
91
|
+
# :enddoc:
|
|
92
|
+
require 'rainbows/event_machine/client'
|
|
93
|
+
require 'rainbows/event_machine/server'
|
data/lib/rainbows/fiber/base.rb
CHANGED
|
@@ -18,11 +18,10 @@ module Rainbows::Fiber::Base
|
|
|
18
18
|
# for one second (returned by the schedule_sleepers method) which
|
|
19
19
|
# will cause it.
|
|
20
20
|
def schedule(&block)
|
|
21
|
-
|
|
21
|
+
begin
|
|
22
22
|
G.tick
|
|
23
|
-
RD.compact.each { |c| c.f.resume } # attempt to time out idle clients
|
|
24
23
|
t = schedule_sleepers
|
|
25
|
-
|
|
24
|
+
ret = select(RD.compact.concat(LISTENERS), WR.compact, nil, t)
|
|
26
25
|
rescue Errno::EINTR
|
|
27
26
|
retry
|
|
28
27
|
rescue Errno::EBADF, TypeError
|
|
@@ -30,15 +29,15 @@ module Rainbows::Fiber::Base
|
|
|
30
29
|
raise
|
|
31
30
|
end or return
|
|
32
31
|
|
|
33
|
-
# active writers first, then
|
|
34
|
-
ret[1].concat(RD.compact).each { |c| c.f.resume }
|
|
32
|
+
# active writers first, then readers
|
|
33
|
+
ret[1].concat(RD.compact & ret[0]).each { |c| c.f.resume }
|
|
35
34
|
|
|
36
35
|
# accept is an expensive syscall, filter out listeners we don't want
|
|
37
36
|
(ret[0] & LISTENERS).each(&block)
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
# wakes up any sleepers that need to be
|
|
41
|
-
# returns an interval to IO.select on
|
|
39
|
+
# wakes up any sleepers or keepalive-timeout violators that need to be
|
|
40
|
+
# woken and returns an interval to IO.select on
|
|
42
41
|
def schedule_sleepers
|
|
43
42
|
max = nil
|
|
44
43
|
now = Time.now
|
data/lib/rainbows/fiber/body.rb
CHANGED
|
@@ -11,9 +11,9 @@ module Rainbows::Fiber::Body # :nodoc:
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
# the sendfile 1.0.0+ gem includes IO#sendfile_nonblock
|
|
14
|
-
if
|
|
14
|
+
if IO.method_defined?(:sendfile_nonblock)
|
|
15
15
|
def write_body_file(client, body, range)
|
|
16
|
-
sock, n = client.to_io, nil
|
|
16
|
+
sock, n, body = client.to_io, nil, body_to_io(body)
|
|
17
17
|
offset, count = range ? range : [ 0, body.stat.size ]
|
|
18
18
|
begin
|
|
19
19
|
offset += (n = sock.sendfile_nonblock(body, offset, count))
|
|
@@ -23,6 +23,8 @@ module Rainbows::Fiber::Body # :nodoc:
|
|
|
23
23
|
rescue EOFError
|
|
24
24
|
break
|
|
25
25
|
end while (count -= n) > 0
|
|
26
|
+
ensure
|
|
27
|
+
close_if_private(body)
|
|
26
28
|
end
|
|
27
29
|
else
|
|
28
30
|
ALIASES[:write_body] = :write_body_each
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
class Rainbows::Fiber::Coolio::Heartbeat < Coolio::TimerWatcher
|
|
4
|
+
G = Rainbows::G
|
|
5
|
+
|
|
6
|
+
# ZZ gets populated by read_expire in rainbows/fiber/io/methods
|
|
7
|
+
ZZ = Rainbows::Fiber::ZZ
|
|
8
|
+
def on_timer
|
|
9
|
+
exit if (! G.tick && G.cur <= 0)
|
|
10
|
+
now = Time.now
|
|
11
|
+
fibs = []
|
|
12
|
+
ZZ.delete_if { |fib, time| now >= time ? fibs << fib : ! fib.alive? }
|
|
13
|
+
fibs.each { |fib| fib.resume if fib.alive? }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
2
|
# :enddoc:
|
|
3
|
-
module Rainbows::Fiber::
|
|
4
|
-
class Watcher <
|
|
3
|
+
module Rainbows::Fiber::Coolio::Methods
|
|
4
|
+
class Watcher < Coolio::IOWatcher
|
|
5
5
|
def initialize(fio, flag)
|
|
6
6
|
@f = Fiber.current
|
|
7
7
|
super(fio, flag)
|
|
8
|
-
attach(
|
|
8
|
+
attach(Coolio::Loop.default)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def on_readable
|
|
@@ -31,7 +31,6 @@ module Rainbows::Fiber::Rev::Methods
|
|
|
31
31
|
def kgio_wait_readable
|
|
32
32
|
@r = Watcher.new(self, :r) unless defined?(@r)
|
|
33
33
|
@r.enable unless @r.enabled?
|
|
34
|
-
KATO << Fiber.current
|
|
35
34
|
Fiber.yield
|
|
36
35
|
@r.disable
|
|
37
36
|
end
|
|
@@ -44,5 +43,5 @@ end
|
|
|
44
43
|
Rainbows::Fiber::IO::Socket,
|
|
45
44
|
Rainbows::Fiber::IO::Pipe
|
|
46
45
|
].each do |klass|
|
|
47
|
-
klass.__send__(:include, Rainbows::Fiber::
|
|
46
|
+
klass.__send__(:include, Rainbows::Fiber::Coolio::Methods)
|
|
48
47
|
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
2
|
# :enddoc:
|
|
3
|
-
class Rainbows::Fiber::
|
|
3
|
+
class Rainbows::Fiber::Coolio::Sleeper < Coolio::TimerWatcher
|
|
4
4
|
|
|
5
5
|
def initialize(seconds)
|
|
6
6
|
@f = Fiber.current
|
|
7
7
|
super(seconds, false)
|
|
8
|
-
attach(
|
|
8
|
+
attach(Coolio::Loop.default)
|
|
9
9
|
Fiber.yield
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
require 'rainbows/coolio_support'
|
|
4
|
+
require 'rainbows/fiber'
|
|
5
|
+
require 'rainbows/fiber/io'
|
|
6
|
+
|
|
7
|
+
module Rainbows::Fiber::Coolio
|
|
8
|
+
autoload :Heartbeat, 'rainbows/fiber/coolio/heartbeat'
|
|
9
|
+
autoload :Server, 'rainbows/fiber/coolio/server'
|
|
10
|
+
autoload :Sleeper, 'rainbows/fiber/coolio/sleeper'
|
|
11
|
+
end
|
|
12
|
+
require 'rainbows/fiber/coolio/methods'
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
module Rainbows::Fiber::IO::Methods
|
|
9
9
|
RD = Rainbows::Fiber::RD
|
|
10
10
|
WR = Rainbows::Fiber::WR
|
|
11
|
+
ZZ = Rainbows::Fiber::ZZ
|
|
11
12
|
attr_accessor :f
|
|
12
13
|
|
|
14
|
+
def read_expire
|
|
15
|
+
ZZ[Fiber.current] = super
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
# for wrapping output response bodies
|
|
14
19
|
def each(&block)
|
|
15
20
|
if buf = kgio_read(16384)
|
|
@@ -30,6 +35,7 @@ module Rainbows::Fiber::IO::Methods
|
|
|
30
35
|
@f = Fiber.current
|
|
31
36
|
RD[fd] = self
|
|
32
37
|
Fiber.yield
|
|
38
|
+
ZZ.delete @f
|
|
33
39
|
RD[fd] = nil
|
|
34
40
|
end
|
|
35
41
|
|