rainbows 0.95.1 → 0.96.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Documentation/comparison.haml +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -1
- data/Rakefile +16 -3
- data/Static_Files +11 -11
- data/TODO +1 -6
- data/lib/rainbows.rb +3 -2
- data/lib/rainbows/base.rb +12 -9
- data/lib/rainbows/const.rb +2 -4
- data/lib/rainbows/dev_fd_response.rb +16 -13
- data/lib/rainbows/error.rb +2 -0
- data/lib/rainbows/ev_core.rb +13 -0
- data/lib/rainbows/event_machine.rb +85 -128
- data/lib/rainbows/event_machine/response_chunk_pipe.rb +25 -0
- data/lib/rainbows/event_machine/response_pipe.rb +30 -0
- data/lib/rainbows/event_machine/try_defer.rb +27 -0
- data/lib/rainbows/fiber/body.rb +6 -4
- data/lib/rainbows/fiber/io.rb +4 -4
- data/lib/rainbows/fiber/rev.rb +16 -8
- data/lib/rainbows/http_response.rb +5 -6
- data/lib/rainbows/response.rb +37 -22
- data/lib/rainbows/response/body.rb +19 -16
- data/lib/rainbows/response/range.rb +34 -0
- data/lib/rainbows/rev.rb +2 -1
- data/lib/rainbows/rev/client.rb +105 -77
- data/lib/rainbows/rev/deferred_chunk_response.rb +16 -0
- data/lib/rainbows/rev/deferred_response.rb +16 -24
- data/lib/rainbows/rev/sendfile.rb +4 -13
- data/lib/rainbows/rev/thread.rb +3 -12
- data/lib/rainbows/rev_thread_pool.rb +2 -2
- data/lib/rainbows/revactor.rb +16 -9
- data/lib/rainbows/revactor/body.rb +42 -0
- data/lib/rainbows/revactor/proxy.rb +55 -0
- data/lib/rainbows/sendfile.rb +12 -14
- data/lib/rainbows/stream_file.rb +3 -3
- data/lib/rainbows/writer_thread_pool.rb +12 -12
- data/lib/rainbows/writer_thread_spawn.rb +6 -7
- data/t/GNUmakefile +2 -2
- data/t/close-pipe-response.ru +25 -0
- data/t/cramp/rainsocket.ru +1 -1
- data/t/fast-pipe-response.ru +10 -0
- data/t/file-wrap-to_path.ru +24 -0
- data/t/t0015-working_directory.sh +5 -1
- data/t/t0020-large-sendfile-response.sh +3 -3
- data/t/t0021-sendfile-wrap-to_path.sh +108 -0
- data/t/t0022-copy_stream-byte-range.sh +139 -0
- data/t/t0023-sendfile-byte-range.sh +63 -0
- data/t/t0024-pipelined-sendfile-response.sh +89 -0
- data/t/t0030-fast-pipe-response.sh +63 -0
- data/t/t0031-close-pipe-response.sh +96 -0
- data/t/t0034-pipelined-pipe-response.sh +87 -0
- data/t/t0105-rack-input-limit-bigger.sh +5 -2
- data/t/t0500-cramp-streaming.sh +0 -1
- data/t/t0501-cramp-rainsocket.sh +1 -0
- data/t/t9000-rack-app-pool.sh +1 -1
- data/t/test_isolate.rb +1 -0
- metadata +29 -5
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
#
|
4
|
+
# this is class is specific to Rev for proxying IO-derived objects
|
5
|
+
class Rainbows::Rev::DeferredChunkResponse < Rainbows::Rev::DeferredResponse
|
6
|
+
def on_read(data)
|
7
|
+
@client.write("#{data.size.to_s(16)}\r\n")
|
8
|
+
@client.write(data)
|
9
|
+
@client.write("\r\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_close
|
13
|
+
@client.write("0\r\n\r\n")
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
@@ -1,28 +1,20 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
|
-
|
4
|
-
|
3
|
+
#
|
4
|
+
# this is class is specific to Rev for writing large static files
|
5
|
+
# or proxying IO-derived objects
|
6
|
+
class Rainbows::Rev::DeferredResponse < ::Rev::IO
|
7
|
+
def initialize(io, client, body)
|
8
|
+
super(io)
|
9
|
+
@client, @body = client, body
|
10
|
+
end
|
5
11
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
include Rainbows::Const
|
10
|
-
def initialize(io, client, do_chunk, body)
|
11
|
-
super(io)
|
12
|
-
@client, @do_chunk, @body = client, do_chunk, body
|
13
|
-
end
|
12
|
+
def on_read(data)
|
13
|
+
@client.write(data)
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def on_close
|
22
|
-
@do_chunk and @client.write("0\r\n\r\n")
|
23
|
-
@client.quit
|
24
|
-
@body.respond_to?(:close) and @body.close
|
25
|
-
end
|
26
|
-
end # class DeferredResponse
|
27
|
-
end # module Rev
|
28
|
-
end # module Rainbows
|
16
|
+
def on_close
|
17
|
+
@client.next! if @client.attached? # attached? is false if write fails
|
18
|
+
@body.respond_to?(:close) and @body.close
|
19
|
+
end
|
20
|
+
end
|
@@ -2,25 +2,16 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
module Rainbows::Rev::Sendfile
|
4
4
|
if IO.method_defined?(:sendfile_nonblock)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
F[0, io]
|
9
|
-
end
|
10
|
-
|
11
|
-
def rev_sendfile(body)
|
12
|
-
body.offset += @_io.sendfile_nonblock(body, body.offset, 0x10000)
|
5
|
+
def rev_sendfile(sf) # +sf+ is a Rainbows::StreamFile object
|
6
|
+
sf.offset += (n = @_io.sendfile_nonblock(sf, sf.offset, sf.count))
|
7
|
+
0 == (sf.count -= n) and raise EOFError
|
13
8
|
enable_write_watcher
|
14
9
|
rescue Errno::EAGAIN
|
15
10
|
enable_write_watcher
|
16
11
|
end
|
17
12
|
else
|
18
|
-
def to_sendfile(io)
|
19
|
-
io
|
20
|
-
end
|
21
|
-
|
22
13
|
def rev_sendfile(body)
|
23
|
-
write(body.sysread(0x4000))
|
14
|
+
write(body.to_io.sysread(0x4000))
|
24
15
|
end
|
25
16
|
end
|
26
17
|
end
|
data/lib/rainbows/rev/thread.rb
CHANGED
@@ -13,29 +13,20 @@ module Rainbows
|
|
13
13
|
|
14
14
|
def app_call
|
15
15
|
KATO.delete(self)
|
16
|
-
disable
|
16
|
+
disable if enabled?
|
17
17
|
@env[RACK_INPUT] = @input
|
18
18
|
app_dispatch # must be implemented by subclass
|
19
19
|
end
|
20
20
|
|
21
21
|
# this is only called in the master thread
|
22
22
|
def response_write(response)
|
23
|
-
enable
|
24
23
|
alive = @hp.keepalive? && G.alive
|
25
|
-
|
26
|
-
|
27
|
-
return quit unless alive && G.alive
|
24
|
+
rev_write_response(response, alive)
|
25
|
+
return quit unless alive && :close != @state
|
28
26
|
|
29
27
|
@env.clear
|
30
28
|
@hp.reset
|
31
29
|
@state = :headers
|
32
|
-
# keepalive requests are always body-less, so @input is unchanged
|
33
|
-
if @hp.headers(@env, @buf)
|
34
|
-
@input = HttpRequest::NULL_IO
|
35
|
-
app_call
|
36
|
-
else
|
37
|
-
KATO[self] = Time.now
|
38
|
-
end
|
39
30
|
end
|
40
31
|
|
41
32
|
# fails-safe application dispatch, we absolutely cannot
|
@@ -15,8 +15,8 @@ module Rainbows
|
|
15
15
|
# slow clients and applications with medium-to-slow response times
|
16
16
|
# (I/O bound), but less suitable for sleepy applications.
|
17
17
|
#
|
18
|
-
#
|
19
|
-
#
|
18
|
+
# This concurrency model is designed for Ruby 1.9, and Ruby 1.8
|
19
|
+
# users are NOT advised to use this due to high CPU usage.
|
20
20
|
|
21
21
|
module RevThreadPool
|
22
22
|
|
data/lib/rainbows/revactor.rb
CHANGED
@@ -22,6 +22,8 @@ module Rainbows::Revactor
|
|
22
22
|
# :stopdoc:
|
23
23
|
RD_ARGS = {}
|
24
24
|
|
25
|
+
autoload :Proxy, 'rainbows/revactor/proxy'
|
26
|
+
|
25
27
|
include Rainbows::Base
|
26
28
|
LOCALHOST = Unicorn::HttpRequest::LOCALHOST
|
27
29
|
TCP = ::Revactor::TCP::Socket
|
@@ -41,7 +43,6 @@ module Rainbows::Revactor
|
|
41
43
|
buf = client.read(*rd_args)
|
42
44
|
hp = HttpParser.new
|
43
45
|
env = {}
|
44
|
-
alive = true
|
45
46
|
|
46
47
|
begin
|
47
48
|
buf << client.read(*rd_args) until hp.headers(env, buf)
|
@@ -50,18 +51,23 @@ module Rainbows::Revactor
|
|
50
51
|
env[RACK_INPUT] = 0 == hp.content_length ?
|
51
52
|
NULL_IO : TeeInput.new(PartialSocket.new(client), env, hp, buf)
|
52
53
|
env[REMOTE_ADDR] = remote_addr
|
53
|
-
|
54
|
+
status, headers, body = app.call(env.update(RACK_DEFAULTS))
|
54
55
|
|
55
|
-
if 100 ==
|
56
|
+
if 100 == status.to_i
|
56
57
|
client.write(EXPECT_100_RESPONSE)
|
57
58
|
env.delete(HTTP_EXPECT)
|
58
|
-
|
59
|
+
status, headers, body = app.call(env)
|
59
60
|
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
if hp.headers?
|
63
|
+
headers = HH.new(headers)
|
64
|
+
range = make_range!(env, status, headers) and status = range.shift
|
65
|
+
env = false unless hp.keepalive? && G.alive
|
66
|
+
headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
|
67
|
+
client.write(response_header(status, headers))
|
68
|
+
end
|
69
|
+
write_body(client, body, range)
|
70
|
+
end while env && env.clear && hp.reset.nil?
|
65
71
|
rescue ::Revactor::TCP::ReadError
|
66
72
|
rescue => e
|
67
73
|
Rainbows::Error.write(io, e)
|
@@ -74,7 +80,8 @@ module Rainbows::Revactor
|
|
74
80
|
# given a INT, QUIT, or TERM signal)
|
75
81
|
def worker_loop(worker) #:nodoc:
|
76
82
|
init_worker_process(worker)
|
77
|
-
|
83
|
+
require 'rainbows/revactor/body'
|
84
|
+
self.class.__send__(:include, Rainbows::Revactor::Body)
|
78
85
|
RD_ARGS[:timeout] = G.kato if G.kato > 0
|
79
86
|
nr = 0
|
80
87
|
limit = worker_connections
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::Revactor::Body
|
4
|
+
# TODO non-blocking splice(2) under Linux
|
5
|
+
ALIASES = {
|
6
|
+
:write_body_stream => :write_body_each
|
7
|
+
}
|
8
|
+
|
9
|
+
if IO.method_defined?(:sendfile_nonblock)
|
10
|
+
def write_body_file(client, body, range)
|
11
|
+
sock = client.instance_variable_get(:@_io)
|
12
|
+
pfx = ::Revactor::TCP::Socket === client ? :tcp : :unix
|
13
|
+
write_complete = T[:"#{pfx}_write_complete", client]
|
14
|
+
closed = T[:"#{pfx}_closed", client]
|
15
|
+
offset, count = range ? range : [ 0, body.stat.size ]
|
16
|
+
begin
|
17
|
+
offset += (n = sock.sendfile_nonblock(body, offset, count))
|
18
|
+
rescue Errno::EAGAIN
|
19
|
+
# The @_write_buffer is empty at this point, trigger the
|
20
|
+
# on_readable method which in turn triggers on_write_complete
|
21
|
+
# even though nothing was written
|
22
|
+
client.controller = Actor.current
|
23
|
+
client.__send__(:enable_write_watcher)
|
24
|
+
Actor.receive do |filter|
|
25
|
+
filter.when(write_complete) {}
|
26
|
+
filter.when(closed) { raise Errno::EPIPE }
|
27
|
+
end
|
28
|
+
retry
|
29
|
+
rescue EOFError
|
30
|
+
break
|
31
|
+
end while (count -= n) > 0
|
32
|
+
end
|
33
|
+
else
|
34
|
+
ALIASES[:write_body] = :write_body_each
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.included(klass)
|
38
|
+
ALIASES.each do |new_method, orig_method|
|
39
|
+
klass.__send__(:alias_method, new_method, orig_method)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# Generic IO wrapper for proxying pipe and socket objects
|
4
|
+
# this behaves more like Rainbows::Fiber::IO than anything,
|
5
|
+
# making it highly suitable for proxying data from pipes/sockets
|
6
|
+
class Rainbows::Revactor::Proxy < Rev::IO
|
7
|
+
def initialize(io)
|
8
|
+
@receiver = Actor.current
|
9
|
+
super(io)
|
10
|
+
attach(Rev::Loop.default)
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
if @_io
|
15
|
+
super
|
16
|
+
@_io = nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
# when yield-ing, Revactor::TCP#write may raise EOFError
|
22
|
+
# (instead of Errno::EPIPE), so we need to limit the rescue
|
23
|
+
# to just readpartial and let EOFErrors during yield bubble up
|
24
|
+
begin
|
25
|
+
buf = readpartial(INPUT_SIZE)
|
26
|
+
rescue EOFError
|
27
|
+
break
|
28
|
+
end while yield(buf) || true
|
29
|
+
end
|
30
|
+
|
31
|
+
# this may return more than the specified length, Rainbows! won't care...
|
32
|
+
def readpartial(length)
|
33
|
+
@receiver = Actor.current
|
34
|
+
enable if attached? && ! enabled?
|
35
|
+
|
36
|
+
Actor.receive do |filter|
|
37
|
+
filter.when(T[:rainbows_io_input, self]) do |_, _, data|
|
38
|
+
return data
|
39
|
+
end
|
40
|
+
|
41
|
+
filter.when(T[:rainbows_io_closed, self]) do
|
42
|
+
raise EOFError, "connection closed"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_close
|
48
|
+
@receiver << T[:rainbows_io_closed, self]
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_read(data)
|
52
|
+
@receiver << T[:rainbows_io_input, self, data ]
|
53
|
+
disable
|
54
|
+
end
|
55
|
+
end
|
data/lib/rainbows/sendfile.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
module Rainbows
|
3
|
-
|
4
2
|
# This middleware handles X-\Sendfile headers generated by applications
|
5
3
|
# or middlewares down the stack. It should be placed at the top
|
6
4
|
# (outermost layer) of the middleware stack to avoid having its
|
@@ -48,42 +46,42 @@ module Rainbows
|
|
48
46
|
# ]
|
49
47
|
# }
|
50
48
|
|
51
|
-
class Sendfile < Struct.new(:app)
|
52
|
-
|
53
|
-
# :stopdoc:
|
54
|
-
HH = Rack::Utils::HeaderHash
|
55
|
-
# :startdoc:
|
49
|
+
class Rainbows::Sendfile < Struct.new(:app)
|
56
50
|
|
57
51
|
# Body wrapper, this allows us to fall back gracefully to
|
58
52
|
# +each+ in case a given concurrency model does not optimize
|
59
53
|
# +to_path+ calls.
|
60
54
|
class Body < Struct.new(:to_path) # :nodoc: all
|
55
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
61
56
|
|
62
57
|
def self.new(path, headers)
|
63
|
-
unless headers[
|
58
|
+
unless headers[CONTENT_LENGTH]
|
64
59
|
stat = File.stat(path)
|
65
|
-
headers[
|
60
|
+
headers[CONTENT_LENGTH] = stat.size.to_s if stat.file?
|
66
61
|
end
|
67
62
|
super(path)
|
68
63
|
end
|
69
64
|
|
70
65
|
# fallback in case our +to_path+ doesn't get handled for whatever reason
|
71
66
|
def each(&block)
|
72
|
-
|
73
|
-
|
67
|
+
buf = ''
|
68
|
+
File.open(to_path) do |fp|
|
74
69
|
yield buf while fp.read(0x4000, buf)
|
75
70
|
end
|
76
71
|
end
|
77
72
|
end
|
78
73
|
|
74
|
+
# :stopdoc:
|
75
|
+
HH = Rack::Utils::HeaderHash
|
76
|
+
X_SENDFILE = 'X-Sendfile'
|
77
|
+
# :startdoc:
|
78
|
+
|
79
79
|
def call(env) # :nodoc:
|
80
80
|
status, headers, body = app.call(env)
|
81
81
|
headers = HH.new(headers)
|
82
|
-
if path = headers.delete(
|
82
|
+
if path = headers.delete(X_SENDFILE)
|
83
83
|
body = Body.new(path, headers) unless body.respond_to?(:to_path)
|
84
84
|
end
|
85
85
|
[ status, headers, body ]
|
86
86
|
end
|
87
87
|
end
|
88
|
-
|
89
|
-
end
|
data/lib/rainbows/stream_file.rb
CHANGED
@@ -5,10 +5,10 @@
|
|
5
5
|
# models. We always maintain our own file offsets in userspace because
|
6
6
|
# because sendfile() implementations offer pread()-like idempotency for
|
7
7
|
# concurrency (multiple clients can read the same underlying file handle).
|
8
|
-
class Rainbows::StreamFile < Struct.new(:offset, :to_io)
|
9
|
-
|
8
|
+
class Rainbows::StreamFile < Struct.new(:offset, :count, :to_io, :body)
|
10
9
|
def close
|
11
|
-
|
10
|
+
body.close if body.respond_to?(:close)
|
11
|
+
to_io.close unless to_io.closed?
|
12
12
|
self.to_io = nil
|
13
13
|
end
|
14
14
|
end
|
@@ -46,33 +46,33 @@ module Rainbows
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
module Response # :nodoc:
|
50
|
-
def write_body(qclient, body)
|
51
|
-
qclient.q << [ qclient.to_io, :body, body ]
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
49
|
@@nr = 0
|
56
50
|
@@q = nil
|
57
51
|
|
52
|
+
def async_write_body(qclient, body, range)
|
53
|
+
qclient.q << [ qclient.to_io, :body, body, range ]
|
54
|
+
end
|
55
|
+
|
58
56
|
def process_client(client) # :nodoc:
|
59
57
|
@@nr += 1
|
60
|
-
super(QueueSocket
|
58
|
+
super(QueueSocket.new(client, @@q[@@nr %= @@q.size]))
|
61
59
|
end
|
62
60
|
|
63
|
-
def
|
64
|
-
|
61
|
+
def init_worker_process(worker)
|
62
|
+
super
|
65
63
|
self.class.__send__(:alias_method, :sync_write_body, :write_body)
|
66
|
-
|
64
|
+
WriterThreadPool.__send__(:alias_method, :write_body, :async_write_body)
|
65
|
+
end
|
67
66
|
|
67
|
+
def worker_loop(worker) # :nodoc:
|
68
68
|
# we have multiple, single-thread queues since we don't want to
|
69
69
|
# interleave writes from the same client
|
70
70
|
qp = (1..worker_connections).map do |n|
|
71
71
|
QueuePool.new(1) do |response|
|
72
72
|
begin
|
73
|
-
io, arg1, arg2 = response
|
73
|
+
io, arg1, arg2, arg3 = response
|
74
74
|
case arg1
|
75
|
-
when :body then sync_write_body(io, arg2)
|
75
|
+
when :body then sync_write_body(io, arg2, arg3)
|
76
76
|
when :close then io.close unless io.closed?
|
77
77
|
else
|
78
78
|
io.write(arg1)
|
@@ -51,9 +51,9 @@ module Rainbows
|
|
51
51
|
self.thr = Thread.new(to_io, q) do |io, q|
|
52
52
|
while response = q.shift
|
53
53
|
begin
|
54
|
-
arg1, arg2 = response
|
54
|
+
arg1, arg2, arg3 = response
|
55
55
|
case arg1
|
56
|
-
when :body then write_body(io, arg2)
|
56
|
+
when :body then write_body(io, arg2, arg3)
|
57
57
|
when :close
|
58
58
|
io.close unless io.closed?
|
59
59
|
break
|
@@ -73,8 +73,8 @@ module Rainbows
|
|
73
73
|
(self.q ||= queue_writer) << buf
|
74
74
|
end
|
75
75
|
|
76
|
-
def queue_body(body)
|
77
|
-
(self.q ||= queue_writer) << [ :body, body ]
|
76
|
+
def queue_body(body, range)
|
77
|
+
(self.q ||= queue_writer) << [ :body, body, range ]
|
78
78
|
end
|
79
79
|
|
80
80
|
def close
|
@@ -90,8 +90,8 @@ module Rainbows
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
def write_body(my_sock, body) # :nodoc:
|
94
|
-
my_sock.queue_body(body)
|
93
|
+
def write_body(my_sock, body, range) # :nodoc:
|
94
|
+
my_sock.queue_body(body, range)
|
95
95
|
end
|
96
96
|
|
97
97
|
def process_client(client) # :nodoc:
|
@@ -100,7 +100,6 @@ module Rainbows
|
|
100
100
|
|
101
101
|
def worker_loop(worker) # :nodoc:
|
102
102
|
MySocket.const_set(:MAX, worker_connections)
|
103
|
-
Rainbows::Response.setup(MySocket)
|
104
103
|
super(worker) # accept loop from Unicorn
|
105
104
|
CUR.delete_if do |t,q|
|
106
105
|
q << nil
|