rainbows 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.wrongdoc.yml +2 -2
- data/Documentation/comparison.haml +6 -6
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +16 -128
- data/README +2 -3
- data/Rakefile +3 -3
- data/examples/reverse_proxy.ru +9 -0
- data/lib/rainbows.rb +14 -6
- data/lib/rainbows/base.rb +0 -1
- data/lib/rainbows/const.rb +1 -10
- data/lib/rainbows/coolio/client.rb +8 -4
- data/lib/rainbows/coolio/core.rb +0 -3
- data/lib/rainbows/coolio/thread_client.rb +2 -2
- data/lib/rainbows/coolio_fiber_spawn.rb +6 -6
- data/lib/rainbows/dev_fd_response.rb +16 -9
- data/lib/rainbows/epoll.rb +43 -0
- data/lib/rainbows/epoll/client.rb +232 -0
- data/lib/rainbows/epoll/response_chunk_pipe.rb +18 -0
- data/lib/rainbows/epoll/response_pipe.rb +32 -0
- data/lib/rainbows/epoll/server.rb +31 -0
- data/lib/rainbows/error.rb +1 -9
- data/lib/rainbows/ev_core.rb +12 -12
- data/lib/rainbows/ev_core/cap_input.rb +1 -1
- data/lib/rainbows/event_machine.rb +0 -6
- data/lib/rainbows/event_machine/client.rb +3 -3
- data/lib/rainbows/event_machine/response_chunk_pipe.rb +5 -7
- data/lib/rainbows/event_machine/response_pipe.rb +7 -8
- data/lib/rainbows/fiber/base.rb +2 -2
- data/lib/rainbows/fiber/io.rb +21 -63
- data/lib/rainbows/fiber/io/methods.rb +1 -1
- data/lib/rainbows/http_server.rb +4 -4
- data/lib/rainbows/join_threads.rb +18 -0
- data/lib/rainbows/max_body.rb +2 -1
- data/lib/rainbows/max_body/wrapper.rb +1 -1
- data/lib/rainbows/never_block/event_machine.rb +2 -2
- data/lib/rainbows/process_client.rb +9 -1
- data/lib/rainbows/queue_pool.rb +2 -2
- data/lib/rainbows/response.rb +1 -1
- data/lib/rainbows/rev_fiber_spawn.rb +4 -4
- data/lib/rainbows/revactor/client.rb +4 -5
- data/lib/rainbows/revactor/proxy.rb +1 -1
- data/lib/rainbows/reverse_proxy.rb +189 -0
- data/lib/rainbows/reverse_proxy/coolio.rb +61 -0
- data/lib/rainbows/reverse_proxy/ev_client.rb +39 -0
- data/lib/rainbows/reverse_proxy/event_machine.rb +46 -0
- data/lib/rainbows/reverse_proxy/multi_thread.rb +7 -0
- data/lib/rainbows/reverse_proxy/synchronous.rb +21 -0
- data/lib/rainbows/sendfile.rb +1 -1
- data/lib/rainbows/sync_close.rb +2 -2
- data/lib/rainbows/thread_pool.rb +1 -1
- data/lib/rainbows/writer_thread_pool.rb +1 -1
- data/lib/rainbows/xepoll.rb +24 -0
- data/lib/rainbows/xepoll/client.rb +45 -0
- data/pkg.mk +171 -0
- data/rainbows.gemspec +2 -4
- data/t/GNUmakefile +4 -0
- data/t/bin/content-md5-put +1 -0
- data/t/kgio-pipe-response.ru +9 -1
- data/t/rack-fiber_pool/app.ru +6 -1
- data/t/simple-http_Epoll.ru +9 -0
- data/t/simple-http_XEpoll.ru +9 -0
- data/t/t0014-config-conflict.sh +5 -3
- data/t/t0023-sendfile-byte-range.sh +1 -7
- data/t/t0034-pipelined-pipe-response.sh +2 -1
- data/t/t0035-kgio-pipe-response.sh +0 -7
- data/t/t0041-optional-pool-size.sh +51 -0
- data/t/t0050-response-body-close-has-env.sh +2 -1
- data/t/t0104-rack-input-limit-tiny.sh +6 -4
- data/t/t0105-rack-input-limit-bigger.sh +6 -4
- data/t/t0106-rack-input-keepalive.sh +2 -0
- data/t/t0107-rack-input-limit-zero.sh +60 -0
- data/t/t0113-rewindable-input-false.sh +1 -0
- data/t/t0114-rewindable-input-true.sh +1 -0
- data/t/t0202-async-response-one-oh.sh +56 -0
- data/t/test_isolate.rb +5 -2
- metadata +42 -37
- data/lib/rainbows/rack_input.rb +0 -17
data/lib/rainbows/ev_core.rb
CHANGED
@@ -7,6 +7,8 @@ module Rainbows::EvCore
|
|
7
7
|
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
8
8
|
HttpParser = Rainbows::HttpParser
|
9
9
|
autoload :CapInput, 'rainbows/ev_core/cap_input'
|
10
|
+
RBUF = ""
|
11
|
+
Z = "".freeze
|
10
12
|
|
11
13
|
# Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
|
12
14
|
ASYNC_CALLBACK = "async.callback".freeze
|
@@ -54,7 +56,7 @@ module Rainbows::EvCore
|
|
54
56
|
rv = false
|
55
57
|
else
|
56
58
|
rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
|
57
|
-
rv = false
|
59
|
+
rv = false unless @env["rainbows.autochunk"]
|
58
60
|
end
|
59
61
|
write_headers(status, headers, alive)
|
60
62
|
rv
|
@@ -70,7 +72,7 @@ module Rainbows::EvCore
|
|
70
72
|
@input = mkinput
|
71
73
|
@hp.filter_body(@buf2 = "", @buf)
|
72
74
|
@input << @buf2
|
73
|
-
on_read(
|
75
|
+
on_read(Z)
|
74
76
|
end
|
75
77
|
|
76
78
|
# TeeInput doesn't map too well to this right now...
|
@@ -81,8 +83,7 @@ module Rainbows::EvCore
|
|
81
83
|
@hp.parse or return want_more
|
82
84
|
@state = :body
|
83
85
|
if 0 == @hp.content_length
|
84
|
-
|
85
|
-
app_call # common case
|
86
|
+
app_call NULL_IO # common case
|
86
87
|
else # nil or len > 0
|
87
88
|
prepare_request_body
|
88
89
|
end
|
@@ -90,7 +91,7 @@ module Rainbows::EvCore
|
|
90
91
|
if @hp.body_eof?
|
91
92
|
if @hp.content_length
|
92
93
|
@input.rewind
|
93
|
-
app_call
|
94
|
+
app_call @input
|
94
95
|
else
|
95
96
|
@state = :trailers
|
96
97
|
on_read(data)
|
@@ -98,14 +99,14 @@ module Rainbows::EvCore
|
|
98
99
|
elsif data.size > 0
|
99
100
|
@hp.filter_body(@buf2, @buf << data)
|
100
101
|
@input << @buf2
|
101
|
-
on_read(
|
102
|
+
on_read(Z)
|
102
103
|
else
|
103
104
|
want_more
|
104
105
|
end
|
105
106
|
when :trailers
|
106
107
|
if @hp.trailers(@env, @buf << data)
|
107
108
|
@input.rewind
|
108
|
-
app_call
|
109
|
+
app_call @input
|
109
110
|
else
|
110
111
|
want_more
|
111
112
|
end
|
@@ -114,14 +115,17 @@ module Rainbows::EvCore
|
|
114
115
|
handle_error(e)
|
115
116
|
end
|
116
117
|
|
118
|
+
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
|
119
|
+
|
117
120
|
def err_413(msg)
|
118
|
-
write(
|
121
|
+
write(ERROR_413_RESPONSE)
|
119
122
|
quit
|
120
123
|
# zip back up the stack
|
121
124
|
raise IOError, msg, []
|
122
125
|
end
|
123
126
|
|
124
127
|
TmpIO = Unicorn::TmpIO
|
128
|
+
CBB = Unicorn::TeeInput.client_body_buffer_size
|
125
129
|
|
126
130
|
def io_for(bytes)
|
127
131
|
bytes <= CBB ? StringIO.new("") : TmpIO.new
|
@@ -139,8 +143,4 @@ module Rainbows::EvCore
|
|
139
143
|
max ? CapInput.new(io_for(max), self, max) : TmpIO.new
|
140
144
|
end
|
141
145
|
end
|
142
|
-
|
143
|
-
def self.setup
|
144
|
-
const_set :CBB, Unicorn::TeeInput.client_body_buffer_size
|
145
|
-
end
|
146
146
|
end
|
@@ -48,11 +48,6 @@ module Rainbows::EventMachine
|
|
48
48
|
|
49
49
|
include Rainbows::Base
|
50
50
|
|
51
|
-
def init_worker_process(worker) # :nodoc:
|
52
|
-
Rainbows::Response.setup(Rainbows::EventMachine::Client)
|
53
|
-
super
|
54
|
-
end
|
55
|
-
|
56
51
|
# runs inside each forked worker, this sits around and waits
|
57
52
|
# for connections and doesn't die until the parent dies (or is
|
58
53
|
# given a INT, QUIT, or TERM signal)
|
@@ -71,7 +66,6 @@ module Rainbows::EventMachine
|
|
71
66
|
Rainbows::EventMachine::Server.const_set(:MAX, max)
|
72
67
|
Rainbows::EventMachine::Server.const_set(:CL, client_class)
|
73
68
|
client_class.const_set(:APP, Rainbows.server.app)
|
74
|
-
Rainbows::EvCore.setup
|
75
69
|
EM.run {
|
76
70
|
conns = EM.instance_variable_get(:@conns) or
|
77
71
|
raise RuntimeError, "EM @conns instance variable not accessible!"
|
@@ -21,7 +21,7 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
21
21
|
end
|
22
22
|
EM.next_tick { receive_data(nil) } unless @buf.empty?
|
23
23
|
else
|
24
|
-
on_read(data ||
|
24
|
+
on_read(data || Z) if (@buf.size > 0) || data
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -30,9 +30,9 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
30
30
|
close_connection_after_writing
|
31
31
|
end
|
32
32
|
|
33
|
-
def app_call
|
33
|
+
def app_call input
|
34
34
|
set_comm_inactivity_timeout 0
|
35
|
-
@env[RACK_INPUT] =
|
35
|
+
@env[RACK_INPUT] = input
|
36
36
|
@env[REMOTE_ADDR] = @_io.kgio_addr
|
37
37
|
@env[ASYNC_CALLBACK] = method(:write_async_response)
|
38
38
|
@env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
|
@@ -9,17 +9,15 @@ module Rainbows::EventMachine::ResponseChunkPipe
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def notify_readable
|
12
|
-
|
13
|
-
|
12
|
+
case data = Kgio.tryread(@io, 16384, RBUF)
|
13
|
+
when String
|
14
14
|
@client.write("#{data.size.to_s(16)}\r\n")
|
15
15
|
@client.write(data)
|
16
16
|
@client.write("\r\n")
|
17
|
-
|
18
|
-
rescue Errno::EAGAIN
|
19
|
-
return
|
20
|
-
rescue EOFError
|
21
|
-
detach
|
17
|
+
when :wait_readable
|
22
18
|
return
|
19
|
+
when nil
|
20
|
+
return detach
|
23
21
|
end while true
|
24
22
|
end
|
25
23
|
end
|
@@ -3,21 +3,20 @@
|
|
3
3
|
module Rainbows::EventMachine::ResponsePipe
|
4
4
|
# garbage avoidance, EM always uses this in a single thread,
|
5
5
|
# so a single buffer for all clients will work safely
|
6
|
-
|
6
|
+
RBUF = Rainbows::EvCore::RBUF
|
7
7
|
|
8
8
|
def initialize(client)
|
9
9
|
@client = client
|
10
10
|
end
|
11
11
|
|
12
12
|
def notify_readable
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
return
|
18
|
-
rescue EOFError
|
19
|
-
detach
|
13
|
+
case data = Kgio.tryread(@io, 16384, RBUF)
|
14
|
+
when String
|
15
|
+
@client.write(data)
|
16
|
+
when :wait_readable
|
20
17
|
return
|
18
|
+
when nil
|
19
|
+
return detach
|
21
20
|
end while true
|
22
21
|
end
|
23
22
|
|
data/lib/rainbows/fiber/base.rb
CHANGED
@@ -17,7 +17,7 @@ module Rainbows::Fiber::Base
|
|
17
17
|
# schedules ones that were blocked on I/O. At most it'll sleep
|
18
18
|
# for one second (returned by the schedule_sleepers method) which
|
19
19
|
# will cause it.
|
20
|
-
def schedule
|
20
|
+
def schedule
|
21
21
|
begin
|
22
22
|
Rainbows.tick
|
23
23
|
t = schedule_sleepers
|
@@ -33,7 +33,7 @@ module Rainbows::Fiber::Base
|
|
33
33
|
ret[1].concat(RD.compact & ret[0]).each { |c| c.f.resume }
|
34
34
|
|
35
35
|
# accept is an expensive syscall, filter out listeners we don't want
|
36
|
-
(ret[0] & LISTENERS).each
|
36
|
+
(ret[0] & LISTENERS).each { |x| yield x }
|
37
37
|
end
|
38
38
|
|
39
39
|
# wakes up any sleepers or keepalive-timeout violators that need to be
|
data/lib/rainbows/fiber/io.rb
CHANGED
@@ -28,7 +28,7 @@ class Rainbows::Fiber::IO
|
|
28
28
|
end
|
29
29
|
|
30
30
|
# for wrapping output response bodies
|
31
|
-
def each
|
31
|
+
def each
|
32
32
|
buf = readpartial(16384)
|
33
33
|
yield buf
|
34
34
|
yield buf while readpartial(16384, buf)
|
@@ -45,78 +45,36 @@ class Rainbows::Fiber::IO
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def write(buf)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
buf = rv
|
55
|
-
when :wait_writable
|
56
|
-
kgio_wait_writable
|
57
|
-
end
|
58
|
-
end while true
|
59
|
-
else
|
60
|
-
begin
|
61
|
-
(rv = @to_io.write_nonblock(buf)) == buf.bytesize and return
|
62
|
-
buf = byte_slice(buf, rv)
|
63
|
-
rescue Errno::EAGAIN
|
64
|
-
kgio_wait_writable
|
65
|
-
end while true
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def byte_slice(buf, start) # :nodoc:
|
70
|
-
buf.encoding == Encoding::BINARY or
|
71
|
-
buf = buf.dup.force_encoding(Encoding::BINARY)
|
72
|
-
buf.slice(start, buf.size)
|
48
|
+
case rv = Kgio.trywrite(buf)
|
49
|
+
when String
|
50
|
+
buf = rv
|
51
|
+
when :wait_writable
|
52
|
+
kgio_wait_writable
|
53
|
+
end until nil == rv
|
73
54
|
end
|
74
55
|
|
75
56
|
# used for reading headers (respecting keepalive_timeout)
|
76
57
|
def timed_read(buf)
|
77
58
|
expire = nil
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
expire ||= read_expire
|
84
|
-
kgio_wait_readable
|
85
|
-
else
|
86
|
-
return rv
|
87
|
-
end
|
88
|
-
end while true
|
59
|
+
case rv = Kgio.tryread(@to_io, 16384, buf)
|
60
|
+
when :wait_readable
|
61
|
+
return if expire && expire < Time.now
|
62
|
+
expire ||= read_expire
|
63
|
+
kgio_wait_readable
|
89
64
|
else
|
90
|
-
|
91
|
-
|
92
|
-
rescue Errno::EAGAIN
|
93
|
-
return if expire && expire < Time.now
|
94
|
-
expire ||= read_expire
|
95
|
-
kgio_wait_readable
|
96
|
-
end while true
|
97
|
-
end
|
65
|
+
return rv
|
66
|
+
end while true
|
98
67
|
end
|
99
68
|
|
100
69
|
def readpartial(length, buf = "")
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
raise EOFError, "end of file reached", []
|
107
|
-
when :wait_readable
|
108
|
-
kgio_wait_readable
|
109
|
-
else
|
110
|
-
return rv
|
111
|
-
end
|
112
|
-
end while true
|
70
|
+
case rv = Kgio.tryread(@to_io, length, buf)
|
71
|
+
when nil
|
72
|
+
raise EOFError, "end of file reached", []
|
73
|
+
when :wait_readable
|
74
|
+
kgio_wait_readable
|
113
75
|
else
|
114
|
-
|
115
|
-
|
116
|
-
rescue Errno::EAGAIN
|
117
|
-
kgio_wait_readable
|
118
|
-
end while true
|
119
|
-
end
|
76
|
+
return rv
|
77
|
+
end while true
|
120
78
|
end
|
121
79
|
|
122
80
|
def kgio_read(*args)
|
data/lib/rainbows/http_server.rb
CHANGED
@@ -43,7 +43,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
43
43
|
|
44
44
|
def ready_pipe=(v)
|
45
45
|
# hacky hook got force Rainbows! to load modules only in workers
|
46
|
-
if @master_pid && @master_pid == Process.ppid
|
46
|
+
if defined?(@master_pid) && @master_pid == Process.ppid
|
47
47
|
extend(Rainbows.const_get(@use))
|
48
48
|
end
|
49
49
|
super
|
@@ -63,8 +63,8 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
63
63
|
raise ArgumentError, "concurrency model #{model.inspect} not supported"
|
64
64
|
args.each do |opt|
|
65
65
|
case opt
|
66
|
-
when Hash; O.update(opt)
|
67
|
-
when Symbol; O[opt] = true
|
66
|
+
when Hash; Rainbows::O.update(opt)
|
67
|
+
when Symbol; Rainbows::O[opt] = true
|
68
68
|
else; raise ArgumentError, "can't handle option: #{opt.inspect}"
|
69
69
|
end
|
70
70
|
end
|
@@ -72,7 +72,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
72
72
|
new_defaults = {
|
73
73
|
'rainbows.model' => (@use = model.to_sym),
|
74
74
|
'rack.multithread' => !!(model.to_s =~ /Thread/),
|
75
|
-
'rainbows.autochunk' => [:Coolio,:Rev,
|
75
|
+
'rainbows.autochunk' => [:Coolio,:Rev,:Epoll,:XEpoll,
|
76
76
|
:EventMachine,:NeverBlock].include?(@use),
|
77
77
|
}
|
78
78
|
Rainbows::Const::RACK_DEFAULTS.update(new_defaults)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# This module only gets loaded on shutdown
|
4
|
+
module Rainbows::JoinThreads
|
5
|
+
|
6
|
+
# blocking acceptor threads must be forced to run
|
7
|
+
def self.acceptors(threads)
|
8
|
+
threads.delete_if do |thr|
|
9
|
+
Rainbows.tick
|
10
|
+
begin
|
11
|
+
thr.run
|
12
|
+
thr.join(0.01)
|
13
|
+
rescue
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end until threads.empty?
|
17
|
+
end
|
18
|
+
end
|
data/lib/rainbows/max_body.rb
CHANGED
@@ -55,7 +55,8 @@ class Rainbows::MaxBody
|
|
55
55
|
case Rainbows.server.use
|
56
56
|
when :Rev, :Coolio, :EventMachine, :NeverBlock,
|
57
57
|
:RevThreadSpawn, :RevThreadPool,
|
58
|
-
:CoolioThreadSpawn, :CoolioThreadPool
|
58
|
+
:CoolioThreadSpawn, :CoolioThreadPool,
|
59
|
+
:Epoll, :XEpoll
|
59
60
|
return
|
60
61
|
end
|
61
62
|
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
2
3
|
module Rainbows::ProcessClient
|
3
4
|
include Rainbows::Response
|
4
|
-
include Rainbows::RackInput
|
5
5
|
include Rainbows::Const
|
6
6
|
|
7
|
+
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
8
|
+
RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
|
9
|
+
IC = Unicorn::HttpRequest.input_class
|
10
|
+
|
7
11
|
def process_loop
|
8
12
|
@hp = hp = Rainbows::HttpParser.new
|
9
13
|
kgio_read!(16384, buf = hp.buf) or return
|
@@ -38,4 +42,8 @@ module Rainbows::ProcessClient
|
|
38
42
|
def handle_error(e)
|
39
43
|
Rainbows::Error.write(self, e)
|
40
44
|
end
|
45
|
+
|
46
|
+
def set_input(env, hp)
|
47
|
+
env[RACK_INPUT] = 0 == hp.content_length ? NULL_IO : IC.new(self, hp)
|
48
|
+
end
|
41
49
|
end
|
data/lib/rainbows/queue_pool.rb
CHANGED
@@ -6,12 +6,12 @@ require 'thread'
|
|
6
6
|
# This is NOT used for the ThreadPool class, since that class does not
|
7
7
|
# need a userspace Queue.
|
8
8
|
class Rainbows::QueuePool < Struct.new(:queue, :threads)
|
9
|
-
def initialize(size = 20
|
9
|
+
def initialize(size = 20)
|
10
10
|
q = Queue.new
|
11
11
|
self.threads = (1..size).map do
|
12
12
|
Thread.new do
|
13
13
|
while job = q.shift
|
14
|
-
|
14
|
+
yield job
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|