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
@@ -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
|
|