rainbows 0.95.1 → 0.96.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/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
|