rainbows 0.96.0 → 0.97.0
Sign up to get free protection for your applications and to get access to all the features.
- data/FAQ +12 -7
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +1 -1
- data/README +3 -3
- data/lib/rainbows.rb +8 -16
- data/lib/rainbows/acceptor.rb +26 -0
- data/lib/rainbows/base.rb +1 -1
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/dev_fd_response.rb +4 -2
- data/lib/rainbows/event_machine.rb +4 -2
- data/lib/rainbows/fiber/rev.rb +2 -1
- data/lib/rainbows/fiber_pool.rb +2 -1
- data/lib/rainbows/fiber_spawn.rb +2 -1
- data/lib/rainbows/response.rb +1 -0
- data/lib/rainbows/response/body.rb +7 -1
- data/lib/rainbows/rev/client.rb +2 -1
- data/lib/rainbows/rev/core.rb +2 -2
- data/lib/rainbows/rev/heartbeat.rb +1 -1
- data/lib/rainbows/rev/master.rb +12 -17
- data/lib/rainbows/revactor.rb +1 -1
- data/lib/rainbows/thread_pool.rb +3 -2
- data/lib/rainbows/thread_spawn.rb +2 -1
- data/lib/rainbows/thread_timeout.rb +94 -0
- data/rainbows.gemspec +1 -1
- data/t/close-pipe-to_path-response.ru +30 -0
- data/t/t0016-onenine-encoding-is-tricky.sh +1 -1
- data/t/t0017-keepalive-timeout-zero.sh +43 -0
- data/t/t0032-close-pipe-to_path-response.sh +101 -0
- data/t/t9100-thread-timeout.sh +36 -0
- data/t/t9100.ru +9 -0
- data/t/t9101-thread-timeout-threshold.sh +62 -0
- data/t/t9101.ru +9 -0
- data/t/test_isolate.rb +1 -1
- metadata +18 -7
data/FAQ
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
=== Why is \Rainbows! a separate project from Unicorn?
|
4
4
|
|
5
|
-
\Rainbows is for the odd, corner-case requests that Unicorn is poorly
|
5
|
+
\Rainbows! is for the odd, corner-case requests that Unicorn is poorly
|
6
6
|
suited for. More scalable concurrency models introduce additional
|
7
7
|
complexity that Unicorn users and developers are uncomfortable with for
|
8
8
|
the common cases.
|
@@ -52,9 +52,14 @@ solution even if nginx will always outperform it in raw throughput.
|
|
52
52
|
|
53
53
|
=== How do I support SSL?
|
54
54
|
|
55
|
-
If you need
|
56
|
-
Rack application, then
|
57
|
-
|
55
|
+
If you need streaming "rack.input" to do on-the-fly upload processing
|
56
|
+
within your Rack application, then using an SSL proxy such as
|
57
|
+
{Pound}[http://www.apsis.ch/pound/] or {Stunnel}[http://stunnel.org/] is
|
58
|
+
required. Pound has built-in X-Forwarded-For support while Stunnel
|
59
|
+
requires a extra {patch}[http://haproxy.1wt.eu/download/patches/].
|
60
|
+
|
61
|
+
If you don't need streaming "rack.input", then nginx is a great HTTPS
|
62
|
+
reverse proxy.
|
58
63
|
|
59
64
|
Refer to the {Unicorn FAQ}[http://unicorn.bogomips.org/FAQ.html] on how
|
60
65
|
to ensure redirects go to "https://" URLs.
|
@@ -77,15 +82,15 @@ to set RAILS_ENV.
|
|
77
82
|
For Rails 2.3.x, the following config.ru will work for you:
|
78
83
|
|
79
84
|
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
|
80
|
-
require "config/environment"
|
85
|
+
require "#{::File.expand_path('config/environment')}"
|
81
86
|
use Rails::Rack::Static
|
82
87
|
run ActionController::Dispatcher.new
|
83
88
|
|
84
89
|
For older versions of Rails, the following config.ru will work:
|
85
90
|
|
86
91
|
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
|
87
|
-
require 'config/boot'
|
88
|
-
require 'config/environment'
|
92
|
+
require "#{::File.expand_path('config/boot')}"
|
93
|
+
require "#{::File.expand_path('config/environment')}"
|
89
94
|
require 'unicorn/app/old_rails'
|
90
95
|
require 'unicorn/app/old_rails/static' # not needed with Unicorn 0.95+
|
91
96
|
use Unicorn::App::OldRails::Static
|
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
data/README
CHANGED
@@ -67,7 +67,7 @@ network concurrency.
|
|
67
67
|
|
68
68
|
== Applications
|
69
69
|
|
70
|
-
\Rainbows is mainly designed for the odd things Unicorn sucks at:
|
70
|
+
\Rainbows! is mainly designed for the odd things Unicorn sucks at:
|
71
71
|
|
72
72
|
* Web Sockets (via {Sunshowers}[http://rainbows.rubyforge.org/sunshowers/])
|
73
73
|
* 3rd-party APIs (to services outside your control/LAN)
|
@@ -81,7 +81,7 @@ network concurrency.
|
|
81
81
|
* Reverse AJAX
|
82
82
|
* real-time upload processing (via {upr}[http://upr.bogomips.org/])
|
83
83
|
|
84
|
-
\Rainbows can also be used to service slow clients directly even with
|
84
|
+
\Rainbows! can also be used to service slow clients directly even with
|
85
85
|
fast applications.
|
86
86
|
|
87
87
|
== License
|
@@ -97,7 +97,7 @@ details.
|
|
97
97
|
|
98
98
|
== Install
|
99
99
|
|
100
|
-
You may download the tarball from the Rainbows project page on Rubyforge
|
100
|
+
You may download the tarball from the \Rainbows! project page on Rubyforge
|
101
101
|
and run setup.rb after unpacking it:
|
102
102
|
|
103
103
|
http://rubyforge.org/frs/?group_id=8977
|
data/lib/rainbows.rb
CHANGED
@@ -30,6 +30,12 @@ module Rainbows
|
|
30
30
|
G = State.new(true, 0, 0, 5)
|
31
31
|
O = {}
|
32
32
|
class Response416 < RangeError; end
|
33
|
+
|
34
|
+
# map of numeric file descriptors to IO objects to avoid using IO.new
|
35
|
+
# and potentially causing race conditions when using /dev/fd/
|
36
|
+
FD_MAP = {}
|
37
|
+
FD_MAP.compare_by_identity if FD_MAP.respond_to?(:compare_by_identity)
|
38
|
+
|
33
39
|
# :startdoc:
|
34
40
|
|
35
41
|
require 'rainbows/const'
|
@@ -69,22 +75,6 @@ module Rainbows
|
|
69
75
|
HttpServer.new(app, options).start.join
|
70
76
|
end
|
71
77
|
|
72
|
-
# returns nil if accept fails
|
73
|
-
def sync_accept(sock) # :nodoc:
|
74
|
-
rv = sock.accept
|
75
|
-
rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
76
|
-
rv
|
77
|
-
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EINTR
|
78
|
-
end
|
79
|
-
|
80
|
-
# returns nil if accept fails
|
81
|
-
def accept(sock) # :nodoc:
|
82
|
-
rv = sock.accept_nonblock
|
83
|
-
rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
84
|
-
rv
|
85
|
-
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
86
|
-
end
|
87
|
-
|
88
78
|
# returns a string representing the address of the given client +io+
|
89
79
|
# For local UNIX domain sockets, this will return a string referred
|
90
80
|
# to by the (non-frozen) Unicorn::HttpRequest::LOCALHOST constant.
|
@@ -131,4 +121,6 @@ module Rainbows
|
|
131
121
|
autoload :ByteSlice, 'rainbows/byte_slice'
|
132
122
|
autoload :StreamFile, 'rainbows/stream_file'
|
133
123
|
autoload :HttpResponse, 'rainbows/http_response' # deprecated
|
124
|
+
autoload :ThreadTimeout, 'rainbows/thread_timeout'
|
134
125
|
end
|
126
|
+
require 'rainbows/acceptor'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# :enddoc:
|
4
|
+
require 'fcntl'
|
5
|
+
|
6
|
+
# this should make life easier for Zbatery if compatibility with
|
7
|
+
# fcntl-crippled platforms is required (or if FD_CLOEXEC is inherited)
|
8
|
+
# and we want to microptimize away fcntl(2) syscalls.
|
9
|
+
module Rainbows::Acceptor
|
10
|
+
|
11
|
+
# returns nil if accept fails
|
12
|
+
def sync_accept(sock)
|
13
|
+
rv = sock.accept
|
14
|
+
rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
15
|
+
rv
|
16
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EINTR
|
17
|
+
end
|
18
|
+
|
19
|
+
# returns nil if accept fails
|
20
|
+
def accept(sock)
|
21
|
+
rv = sock.accept_nonblock
|
22
|
+
rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
23
|
+
rv
|
24
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
25
|
+
end
|
26
|
+
end
|
data/lib/rainbows/base.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
# base class for Rainbows concurrency models, this is currently used by
|
3
|
+
# base class for \Rainbows! concurrency models, this is currently used by
|
4
4
|
# ThreadSpawn and ThreadPool models. Base is also its own
|
5
5
|
# (non-)concurrency model which is basically Unicorn-with-keepalive, and
|
6
6
|
# not intended for production use, as keepalive with a pure prefork
|
data/lib/rainbows/const.rb
CHANGED
@@ -14,7 +14,8 @@
|
|
14
14
|
class Rainbows::DevFdResponse < Struct.new(:app)
|
15
15
|
|
16
16
|
# :stopdoc:
|
17
|
-
|
17
|
+
FD_MAP = Rainbows::FD_MAP
|
18
|
+
|
18
19
|
# make this a no-op under Rubinius, it's pointless anyways
|
19
20
|
# since Rubinius doesn't have IO.copy_stream
|
20
21
|
def self.new(app)
|
@@ -37,6 +38,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
37
38
|
headers = HeaderHash.new(headers)
|
38
39
|
st = io.stat
|
39
40
|
fileno = io.fileno
|
41
|
+
FD_MAP[fileno] = io
|
40
42
|
if st.file?
|
41
43
|
headers['Content-Length'] ||= st.size.to_s
|
42
44
|
headers.delete('Transfer-Encoding')
|
@@ -70,7 +72,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
70
72
|
end
|
71
73
|
|
72
74
|
# remain Rack::Lint-compatible for people with wonky systems :P
|
73
|
-
unless File.
|
75
|
+
unless File.directory?("/dev/fd")
|
74
76
|
alias to_path_orig to_path
|
75
77
|
undef_method :to_path
|
76
78
|
end
|
@@ -95,7 +95,8 @@ module Rainbows
|
|
95
95
|
# long-running async response
|
96
96
|
(response.nil? || -1 == response[0]) and return @state = :close
|
97
97
|
|
98
|
-
|
98
|
+
alive = @hp.keepalive? && G.alive && G.kato > 0
|
99
|
+
em_write_response(response, alive)
|
99
100
|
if alive
|
100
101
|
@env.clear
|
101
102
|
@hp.reset
|
@@ -169,6 +170,7 @@ module Rainbows
|
|
169
170
|
end
|
170
171
|
|
171
172
|
module Server # :nodoc: all
|
173
|
+
include Rainbows::Acceptor
|
172
174
|
|
173
175
|
def close
|
174
176
|
detach
|
@@ -177,7 +179,7 @@ module Rainbows
|
|
177
179
|
|
178
180
|
def notify_readable
|
179
181
|
return if CUR.size >= MAX
|
180
|
-
io =
|
182
|
+
io = accept(@io) or return
|
181
183
|
sig = EM.attach_fd(io.fileno, false)
|
182
184
|
CUR[sig] = CL.new(sig, io)
|
183
185
|
end
|
data/lib/rainbows/fiber/rev.rb
CHANGED
@@ -54,6 +54,7 @@ module Rainbows::Fiber
|
|
54
54
|
include Rainbows
|
55
55
|
include Rainbows::Const
|
56
56
|
include Rainbows::Response
|
57
|
+
include Rainbows::Acceptor
|
57
58
|
FIO = Rainbows::Fiber::IO
|
58
59
|
|
59
60
|
def to_io
|
@@ -72,7 +73,7 @@ module Rainbows::Fiber
|
|
72
73
|
|
73
74
|
def on_readable
|
74
75
|
return if G.cur >= MAX
|
75
|
-
c =
|
76
|
+
c = accept(@io) and ::Fiber.new { process(c) }.resume
|
76
77
|
end
|
77
78
|
|
78
79
|
def process(io)
|
data/lib/rainbows/fiber_pool.rb
CHANGED
@@ -15,6 +15,7 @@ module Rainbows
|
|
15
15
|
|
16
16
|
module FiberPool
|
17
17
|
include Fiber::Base
|
18
|
+
include Rainbows::Acceptor
|
18
19
|
|
19
20
|
def worker_loop(worker) # :nodoc:
|
20
21
|
init_worker_process(worker)
|
@@ -29,7 +30,7 @@ module Rainbows
|
|
29
30
|
begin
|
30
31
|
schedule do |l|
|
31
32
|
fib = pool.shift or break # let another worker process take it
|
32
|
-
if io =
|
33
|
+
if io = accept(l)
|
33
34
|
fib.resume(Fiber::IO.new(io, fib))
|
34
35
|
else
|
35
36
|
pool << fib
|
data/lib/rainbows/fiber_spawn.rb
CHANGED
@@ -12,6 +12,7 @@ module Rainbows
|
|
12
12
|
|
13
13
|
module FiberSpawn
|
14
14
|
include Fiber::Base
|
15
|
+
include Rainbows::Acceptor
|
15
16
|
|
16
17
|
def worker_loop(worker) # :nodoc:
|
17
18
|
init_worker_process(worker)
|
@@ -22,7 +23,7 @@ module Rainbows
|
|
22
23
|
begin
|
23
24
|
schedule do |l|
|
24
25
|
break if G.cur >= limit
|
25
|
-
io =
|
26
|
+
io = accept(l) or next
|
26
27
|
::Fiber.new { process_client(fio.new(io, ::Fiber.current)) }.resume
|
27
28
|
end
|
28
29
|
rescue => e
|
data/lib/rainbows/response.rb
CHANGED
@@ -30,6 +30,12 @@
|
|
30
30
|
module Rainbows::Response::Body # :nodoc:
|
31
31
|
ALIASES = {}
|
32
32
|
|
33
|
+
FD_MAP = Rainbows::FD_MAP
|
34
|
+
|
35
|
+
def io_for_fd(fd)
|
36
|
+
FD_MAP.delete(fd) || IO.new(fd)
|
37
|
+
end
|
38
|
+
|
33
39
|
# to_io is not part of the Rack spec, but make an exception here
|
34
40
|
# since we can conserve path lookups and file descriptors.
|
35
41
|
# \Rainbows! will never get here without checking for the existence
|
@@ -41,7 +47,7 @@ module Rainbows::Response::Body # :nodoc:
|
|
41
47
|
# try to take advantage of Rainbows::DevFdResponse, calling File.open
|
42
48
|
# is a last resort
|
43
49
|
path = body.to_path
|
44
|
-
path =~ %r{\A/dev/fd/(\d+)\z} ?
|
50
|
+
path =~ %r{\A/dev/fd/(\d+)\z} ? io_for_fd($1.to_i) : File.open(path)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
data/lib/rainbows/rev/client.rb
CHANGED
@@ -58,7 +58,7 @@ module Rainbows
|
|
58
58
|
|
59
59
|
def next!
|
60
60
|
@deferred = nil
|
61
|
-
|
61
|
+
enable_write_watcher
|
62
62
|
end
|
63
63
|
|
64
64
|
def timeout?
|
@@ -170,6 +170,7 @@ module Rainbows
|
|
170
170
|
def on_close
|
171
171
|
close_deferred
|
172
172
|
CONN.delete(self)
|
173
|
+
KATO.delete(self)
|
173
174
|
end
|
174
175
|
|
175
176
|
end # module Client
|
data/lib/rainbows/rev/core.rb
CHANGED
@@ -7,12 +7,12 @@ require 'rainbows/rev/heartbeat'
|
|
7
7
|
module Rainbows
|
8
8
|
module Rev
|
9
9
|
class Server < ::Rev::IO
|
10
|
-
|
10
|
+
include Rainbows::Acceptor
|
11
11
|
# CL and MAX will be defined in the corresponding worker loop
|
12
12
|
|
13
13
|
def on_readable
|
14
14
|
return if CONN.size >= MAX
|
15
|
-
io =
|
15
|
+
io = accept(@_io) and CL.new(io).attach(LOOP)
|
16
16
|
end
|
17
17
|
end # class Server
|
18
18
|
|
data/lib/rainbows/rev/master.rb
CHANGED
@@ -2,25 +2,20 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
require 'rainbows/rev'
|
4
4
|
|
5
|
-
|
5
|
+
class Rainbows::Rev::Master < Rev::AsyncWatcher
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
super()
|
12
|
-
@queue = queue
|
13
|
-
end
|
7
|
+
def initialize(queue)
|
8
|
+
super()
|
9
|
+
@queue = queue
|
10
|
+
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
def <<(output)
|
13
|
+
@queue << output
|
14
|
+
signal
|
15
|
+
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
17
|
+
def on_signal
|
18
|
+
client, response = @queue.pop
|
19
|
+
client.response_write(response)
|
25
20
|
end
|
26
21
|
end
|
data/lib/rainbows/revactor.rb
CHANGED
@@ -62,7 +62,7 @@ module Rainbows::Revactor
|
|
62
62
|
if hp.headers?
|
63
63
|
headers = HH.new(headers)
|
64
64
|
range = make_range!(env, status, headers) and status = range.shift
|
65
|
-
env = false unless hp.keepalive? && G.alive
|
65
|
+
env = false unless hp.keepalive? && G.alive && G.kato > 0
|
66
66
|
headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
|
67
67
|
client.write(response_header(status, headers))
|
68
68
|
end
|
data/lib/rainbows/thread_pool.rb
CHANGED
@@ -24,6 +24,7 @@ module Rainbows
|
|
24
24
|
module ThreadPool
|
25
25
|
|
26
26
|
include Base
|
27
|
+
include Rainbows::Acceptor
|
27
28
|
|
28
29
|
def worker_loop(worker) # :nodoc:
|
29
30
|
init_worker_process(worker)
|
@@ -44,7 +45,7 @@ module Rainbows
|
|
44
45
|
def sync_worker # :nodoc:
|
45
46
|
s = LISTENERS[0]
|
46
47
|
begin
|
47
|
-
c =
|
48
|
+
c = sync_accept(s) and process_client(c)
|
48
49
|
rescue => e
|
49
50
|
Error.listen_loop(e)
|
50
51
|
end while G.alive
|
@@ -58,7 +59,7 @@ module Rainbows
|
|
58
59
|
# problem. On the other hand, a thundering herd may not
|
59
60
|
# even incur as much overhead as an extra Mutex#synchronize
|
60
61
|
ret = IO.select(LISTENERS, nil, nil, 1) and ret[0].each do |s|
|
61
|
-
s =
|
62
|
+
s = accept(s) and process_client(s)
|
62
63
|
end
|
63
64
|
rescue Errno::EINTR
|
64
65
|
rescue => e
|
@@ -18,6 +18,7 @@ module Rainbows
|
|
18
18
|
|
19
19
|
module ThreadSpawn
|
20
20
|
include Base
|
21
|
+
include Rainbows::Acceptor
|
21
22
|
|
22
23
|
def accept_loop(klass) #:nodoc:
|
23
24
|
lock = Mutex.new
|
@@ -36,7 +37,7 @@ module Rainbows
|
|
36
37
|
# CPU during I/O wait, CPU cycles that can be better used
|
37
38
|
# by other worker _processes_.
|
38
39
|
sleep(0.01)
|
39
|
-
elsif c =
|
40
|
+
elsif c = sync_accept(l)
|
40
41
|
klass.new(c) do |c|
|
41
42
|
begin
|
42
43
|
lock.synchronize { G.cur += 1 }
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
# Soft timeout middleware for thread-based concurrency models in \Rainbows!
|
5
|
+
# This timeout only includes application dispatch, and will not take into
|
6
|
+
# account the (rare) response bodies that are dynamically generated while
|
7
|
+
# they are being written out to the client.
|
8
|
+
#
|
9
|
+
# In your rackup config file (config.ru), the following line will
|
10
|
+
# cause execution to timeout in 1.5 seconds.
|
11
|
+
#
|
12
|
+
# use Rainbows::ThreadTimeout, :timeout => 1.5
|
13
|
+
# run MyApplication.new
|
14
|
+
#
|
15
|
+
# You may also specify a threshold, so the timeout does not take
|
16
|
+
# effect until there are enough active clients. It does not make
|
17
|
+
# sense to set a +:threshold+ higher or equal to the
|
18
|
+
# +worker_connections+ \Rainbows! configuration parameter.
|
19
|
+
# You may specify a negative threshold to be an absolute
|
20
|
+
# value relative to the +worker_connections+ parameter, thus
|
21
|
+
# if you specify a threshold of -1, and have 100 worker_connections,
|
22
|
+
# ThreadTimeout will only activate when there are 99 active requests.
|
23
|
+
#
|
24
|
+
# use Rainbows::ThreadTimeout, :timeout => 1.5, :threshold => -1
|
25
|
+
# run MyApplication.new
|
26
|
+
#
|
27
|
+
# This middleware only affects elements below it in the stack, so
|
28
|
+
# it can be configured to ignore certain endpoints or middlewares.
|
29
|
+
#
|
30
|
+
# Timed-out requests will cause this middleware to return with a
|
31
|
+
# "408 Request Timeout" response.
|
32
|
+
|
33
|
+
class Rainbows::ThreadTimeout < Struct.new(:app, :timeout,
|
34
|
+
:threshold, :watchdog,
|
35
|
+
:active, :lock)
|
36
|
+
|
37
|
+
# :stopdoc:
|
38
|
+
class ExecutionExpired < ::Exception
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(app, opts)
|
42
|
+
timeout = opts[:timeout]
|
43
|
+
Numeric === timeout or
|
44
|
+
raise TypeError, "timeout=#{timeout.inspect} is not numeric"
|
45
|
+
|
46
|
+
if threshold = opts[:threshold]
|
47
|
+
Integer === threshold or
|
48
|
+
raise TypeError, "threshold=#{threshold.inspect} is not an integer"
|
49
|
+
threshold == 0 and
|
50
|
+
raise ArgumentError, "threshold=0 does not make sense"
|
51
|
+
threshold < 0 and
|
52
|
+
threshold += Rainbows::G.server.worker_connections
|
53
|
+
end
|
54
|
+
super(app, timeout, threshold, nil, {}, Mutex.new)
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(env)
|
58
|
+
lock.synchronize do
|
59
|
+
start_watchdog unless watchdog
|
60
|
+
active[Thread.current] = Time.now + timeout
|
61
|
+
end
|
62
|
+
begin
|
63
|
+
app.call(env)
|
64
|
+
ensure
|
65
|
+
lock.synchronize { active.delete(Thread.current) }
|
66
|
+
end
|
67
|
+
rescue ExecutionExpired
|
68
|
+
[ 408, { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }, [] ]
|
69
|
+
end
|
70
|
+
|
71
|
+
def start_watchdog
|
72
|
+
self.watchdog = Thread.new do
|
73
|
+
begin
|
74
|
+
if next_wake = lock.synchronize { active.values }.min
|
75
|
+
next_wake -= Time.now
|
76
|
+
sleep(next_wake) if next_wake > 0
|
77
|
+
else
|
78
|
+
sleep(timeout)
|
79
|
+
end
|
80
|
+
|
81
|
+
# "active.size" is atomic in MRI 1.8 and 1.9
|
82
|
+
next if threshold && active.size < threshold
|
83
|
+
|
84
|
+
now = Time.now
|
85
|
+
lock.synchronize do
|
86
|
+
active.delete_if do |thread, time|
|
87
|
+
time >= now and thread.raise(ExecutionExpired).nil?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end while true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# :startdoc:
|
94
|
+
end
|
data/rainbows.gemspec
CHANGED
@@ -46,7 +46,7 @@ Gem::Specification.new do |s|
|
|
46
46
|
# we need Unicorn for the HTTP parser and process management
|
47
47
|
# Unicorn 0.991.0 handles config.ru when started outside of
|
48
48
|
# the prespecified working_directory
|
49
|
-
s.add_dependency(%q<unicorn>, [">= 1.1.
|
49
|
+
s.add_dependency(%q<unicorn>, [">= 1.1.3", "< 2.0.0"])
|
50
50
|
s.add_development_dependency(%q<isolate>, "~> 2.1.0")
|
51
51
|
|
52
52
|
# optional runtime dependencies depending on configuration
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# must be run without Rack::Lint since that clobbers to_path
|
2
|
+
class MyMiddleware < Struct.new(:app)
|
3
|
+
class Body < Struct.new(:body, :to_path)
|
4
|
+
def each(&block); body.each(&block); end
|
5
|
+
def close
|
6
|
+
c = body.respond_to?(:close)
|
7
|
+
::File.open(ENV['fifo'], 'wb') do |fp|
|
8
|
+
fp.syswrite("CLOSING #{body.inspect} #{to_path} (#{c})\n")
|
9
|
+
end
|
10
|
+
body.close if c
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
status, headers, body = app.call(env)
|
16
|
+
body.respond_to?(:to_path) and body = Body.new(body, body.to_path)
|
17
|
+
[ status, headers, body ]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
use MyMiddleware
|
21
|
+
use Rainbows::DevFdResponse
|
22
|
+
run(lambda { |env|
|
23
|
+
io = IO.popen('cat random_blob', 'rb')
|
24
|
+
[ 200,
|
25
|
+
{
|
26
|
+
'Content-Length' => ::File.stat('random_blob').size.to_s,
|
27
|
+
'Content-Type' => 'application/octet-stream',
|
28
|
+
},
|
29
|
+
io ]
|
30
|
+
})
|
@@ -4,7 +4,7 @@ t_plan 4 "proper handling of onenine encoding for $model"
|
|
4
4
|
|
5
5
|
t_begin "setup and startup" && {
|
6
6
|
rainbows_setup $model
|
7
|
-
rainbows -D ./t0016.rb -c $unicorn_config
|
7
|
+
rainbows -E none -D ./t0016.rb -c $unicorn_config
|
8
8
|
rainbows_wait_start
|
9
9
|
expect_sha1=8ff79d8115f9fe38d18be858c66aa08a1cc27a66
|
10
10
|
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
t_plan 6 "keepalive_timeout 0 tests for $model"
|
4
|
+
|
5
|
+
t_begin "setup and start" && {
|
6
|
+
rainbows_setup $model 2 0
|
7
|
+
grep 'keepalive_timeout 0' $unicorn_config
|
8
|
+
rainbows -D env.ru -c $unicorn_config
|
9
|
+
rainbows_wait_start
|
10
|
+
}
|
11
|
+
|
12
|
+
t_begin 'check server responds with Connection: close' && {
|
13
|
+
curl -sSfi http://$listen/ | grep 'Connection: close'
|
14
|
+
}
|
15
|
+
|
16
|
+
t_begin "send keepalive response that does not expect close" && {
|
17
|
+
req='GET / HTTP/1.1\r\nHost: example.com\r\n\r\n'
|
18
|
+
t0=$(date +%s)
|
19
|
+
(
|
20
|
+
cat $fifo > $tmp &
|
21
|
+
printf "$req"
|
22
|
+
wait
|
23
|
+
date +%s > $ok
|
24
|
+
) | socat - TCP:$listen > $fifo
|
25
|
+
now="$(cat $ok)"
|
26
|
+
elapsed=$(( $now - $t0 ))
|
27
|
+
t_info "elapsed=$elapsed (expecting <=3)"
|
28
|
+
test $elapsed -le 3
|
29
|
+
}
|
30
|
+
|
31
|
+
t_begin "'Connection: close' header set" && {
|
32
|
+
grep 'Connection: close' $tmp
|
33
|
+
}
|
34
|
+
|
35
|
+
t_begin "killing succeeds" && {
|
36
|
+
kill $rainbows_pid
|
37
|
+
}
|
38
|
+
|
39
|
+
t_begin "check stderr" && {
|
40
|
+
check_stderr
|
41
|
+
}
|
42
|
+
|
43
|
+
t_done
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
if ! test -d /dev/fd
|
4
|
+
then
|
5
|
+
t_info "skipping $T since /dev/fd is required"
|
6
|
+
exit 0
|
7
|
+
fi
|
8
|
+
|
9
|
+
t_plan 16 "close pipe to_path response for $model"
|
10
|
+
|
11
|
+
t_begin "setup and startup" && {
|
12
|
+
rtmpfiles err out http_fifo sub_ok
|
13
|
+
rainbows_setup $model
|
14
|
+
export fifo
|
15
|
+
rainbows -E none -D close-pipe-to_path-response.ru -c $unicorn_config
|
16
|
+
rainbows_wait_start
|
17
|
+
}
|
18
|
+
|
19
|
+
t_begin "read random blob sha1" && {
|
20
|
+
random_blob_sha1=$(rsha1 < random_blob)
|
21
|
+
}
|
22
|
+
|
23
|
+
t_begin "start FIFO reader" && {
|
24
|
+
cat $fifo > $out &
|
25
|
+
}
|
26
|
+
|
27
|
+
t_begin "single request matches" && {
|
28
|
+
sha1=$(curl -sSfv 2> $err http://$listen/ | rsha1)
|
29
|
+
test -n "$sha1"
|
30
|
+
test x"$sha1" = x"$random_blob_sha1"
|
31
|
+
}
|
32
|
+
|
33
|
+
t_begin "body.close called" && {
|
34
|
+
wait # for cat $fifo
|
35
|
+
grep CLOSING $out || die "body.close not logged"
|
36
|
+
}
|
37
|
+
|
38
|
+
t_begin "start FIFO reader for abortive HTTP/1.1 request" && {
|
39
|
+
cat $fifo > $out &
|
40
|
+
}
|
41
|
+
|
42
|
+
t_begin "send abortive HTTP/1.1 request" && {
|
43
|
+
rm -f $ok
|
44
|
+
(
|
45
|
+
printf 'GET /random_blob HTTP/1.1\r\nHost: example.com\r\n\r\n'
|
46
|
+
dd bs=4096 count=1 < $http_fifo >/dev/null
|
47
|
+
echo ok > $ok
|
48
|
+
) | socat - TCP:$listen > $http_fifo || :
|
49
|
+
test xok = x$(cat $ok)
|
50
|
+
}
|
51
|
+
|
52
|
+
t_begin "body.close called for aborted HTTP/1.1 request" && {
|
53
|
+
wait # for cat $fifo
|
54
|
+
grep CLOSING $out || die "body.close not logged"
|
55
|
+
}
|
56
|
+
|
57
|
+
t_begin "start FIFO reader for abortive HTTP/1.0 request" && {
|
58
|
+
cat $fifo > $out &
|
59
|
+
}
|
60
|
+
|
61
|
+
t_begin "send abortive HTTP/1.0 request" && {
|
62
|
+
rm -f $ok
|
63
|
+
(
|
64
|
+
printf 'GET /random_blob HTTP/1.0\r\n\r\n'
|
65
|
+
dd bs=4096 count=1 < $http_fifo >/dev/null
|
66
|
+
echo ok > $ok
|
67
|
+
) | socat - TCP:$listen > $http_fifo || :
|
68
|
+
test xok = x$(cat $ok)
|
69
|
+
}
|
70
|
+
|
71
|
+
t_begin "body.close called for aborted HTTP/1.0 request" && {
|
72
|
+
wait # for cat $fifo
|
73
|
+
grep CLOSING $out || die "body.close not logged"
|
74
|
+
}
|
75
|
+
|
76
|
+
t_begin "start FIFO reader for abortive HTTP/0.9 request" && {
|
77
|
+
cat $fifo > $out &
|
78
|
+
}
|
79
|
+
|
80
|
+
t_begin "send abortive HTTP/0.9 request" && {
|
81
|
+
rm -f $ok
|
82
|
+
(
|
83
|
+
printf 'GET /random_blob\r\n'
|
84
|
+
dd bs=4096 count=1 < $http_fifo >/dev/null
|
85
|
+
echo ok > $ok
|
86
|
+
) | socat - TCP:$listen > $http_fifo || :
|
87
|
+
test xok = x$(cat $ok)
|
88
|
+
}
|
89
|
+
|
90
|
+
t_begin "body.close called for aborted HTTP/0.9 request" && {
|
91
|
+
wait # for cat $fifo
|
92
|
+
grep CLOSING $out || die "body.close not logged"
|
93
|
+
}
|
94
|
+
|
95
|
+
t_begin "shutdown server" && {
|
96
|
+
kill -QUIT $rainbows_pid
|
97
|
+
}
|
98
|
+
|
99
|
+
t_begin "check stderr" && check_stderr
|
100
|
+
|
101
|
+
t_done
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
case $model in
|
4
|
+
ThreadSpawn|ThreadPool|RevThreadSpawn|RevThreadPool) ;;
|
5
|
+
*) t_info "$0 is only compatible with Thread*"; exit 0 ;;
|
6
|
+
esac
|
7
|
+
|
8
|
+
t_plan 5 "ThreadTimeout Rack middleware test for $model"
|
9
|
+
|
10
|
+
t_begin "configure and start" && {
|
11
|
+
rtmpfiles curl_err
|
12
|
+
rainbows_setup
|
13
|
+
rainbows -D t9100.ru -c $unicorn_config
|
14
|
+
rainbows_wait_start
|
15
|
+
}
|
16
|
+
|
17
|
+
t_begin "normal request should not timeout" && {
|
18
|
+
test x"HI" = x"$(curl -sSf http://$listen/ 2>> $curl_err)"
|
19
|
+
}
|
20
|
+
|
21
|
+
t_begin "sleepy request times out with 408" && {
|
22
|
+
rm -f $ok
|
23
|
+
curl -sSf http://$listen/2 2>> $curl_err || > $ok
|
24
|
+
test -e $ok
|
25
|
+
grep 408 $curl_err
|
26
|
+
}
|
27
|
+
|
28
|
+
t_begin "kill server" && {
|
29
|
+
kill $rainbows_pid
|
30
|
+
}
|
31
|
+
|
32
|
+
t_begin "no errors in Rainbows! stderr" && {
|
33
|
+
check_stderr
|
34
|
+
}
|
35
|
+
|
36
|
+
t_done
|
data/t/t9100.ru
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
case $model in
|
4
|
+
ThreadSpawn|ThreadPool|RevThreadSpawn|RevThreadPool) ;;
|
5
|
+
*) t_info "$0 is only compatible with Thread*"; exit 0 ;;
|
6
|
+
esac
|
7
|
+
|
8
|
+
t_plan 6 "ThreadTimeout Rack middleware test for $model"
|
9
|
+
|
10
|
+
t_begin "configure and start" && {
|
11
|
+
rtmpfiles curl_err curl_out
|
12
|
+
rainbows_setup $model 10
|
13
|
+
rainbows -D t9101.ru -c $unicorn_config
|
14
|
+
rainbows_wait_start
|
15
|
+
}
|
16
|
+
|
17
|
+
t_begin "normal request should not timeout" && {
|
18
|
+
test x"HI" = x"$(curl -sSf http://$listen/ 2>> $curl_err)"
|
19
|
+
}
|
20
|
+
|
21
|
+
t_begin "8 sleepy requests do not time out" && {
|
22
|
+
> $curl_err
|
23
|
+
for i in 1 2 3 4 5 6 7 8
|
24
|
+
do
|
25
|
+
curl --no-buffer -sSf http://$listen/3 \
|
26
|
+
2>> $curl_err >> $curl_out &
|
27
|
+
done
|
28
|
+
wait
|
29
|
+
test 8 -eq "$(wc -l < $curl_out)"
|
30
|
+
test xHI = x"$(sort < $curl_out | uniq)"
|
31
|
+
}
|
32
|
+
|
33
|
+
t_begin "9 sleepy requests do time out" && {
|
34
|
+
> $curl_err
|
35
|
+
> $curl_out
|
36
|
+
for i in 1 2 3 4 5 6 7 8 9
|
37
|
+
do
|
38
|
+
rtmpfiles curl_err_$i
|
39
|
+
curl -sSf --no-buffer \
|
40
|
+
http://$listen/3 2>> ${curl_err}_${i} >> $curl_out &
|
41
|
+
done
|
42
|
+
wait
|
43
|
+
if test -s $curl_out
|
44
|
+
then
|
45
|
+
dbgcat curl_out
|
46
|
+
die "$curl_out should be empty"
|
47
|
+
fi
|
48
|
+
for i in 1 2 3 4 5 6 7 8 9
|
49
|
+
do
|
50
|
+
grep 408 ${curl_err}_${i}
|
51
|
+
done
|
52
|
+
}
|
53
|
+
|
54
|
+
t_begin "kill server" && {
|
55
|
+
kill $rainbows_pid
|
56
|
+
}
|
57
|
+
|
58
|
+
t_begin "no errors in Rainbows! stderr" && {
|
59
|
+
check_stderr
|
60
|
+
}
|
61
|
+
|
62
|
+
t_done
|
data/t/t9101.ru
ADDED
data/t/test_isolate.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rainbows
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 411
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 97
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.97.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Rainbows! hackers
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-08-
|
18
|
+
date: 2010-08-28 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -41,12 +41,12 @@ dependencies:
|
|
41
41
|
requirements:
|
42
42
|
- - ">="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
hash:
|
44
|
+
hash: 21
|
45
45
|
segments:
|
46
46
|
- 1
|
47
47
|
- 1
|
48
|
-
-
|
49
|
-
version: 1.1.
|
48
|
+
- 3
|
49
|
+
version: 1.1.3
|
50
50
|
- - <
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
hash: 15
|
@@ -87,6 +87,7 @@ extra_rdoc_files:
|
|
87
87
|
- DEPLOY
|
88
88
|
- FAQ
|
89
89
|
- lib/rainbows.rb
|
90
|
+
- lib/rainbows/acceptor.rb
|
90
91
|
- lib/rainbows/actor_spawn.rb
|
91
92
|
- lib/rainbows/app_pool.rb
|
92
93
|
- lib/rainbows/base.rb
|
@@ -138,6 +139,7 @@ extra_rdoc_files:
|
|
138
139
|
- lib/rainbows/tee_input.rb
|
139
140
|
- lib/rainbows/thread_pool.rb
|
140
141
|
- lib/rainbows/thread_spawn.rb
|
142
|
+
- lib/rainbows/thread_timeout.rb
|
141
143
|
- lib/rainbows/writer_thread_pool.rb
|
142
144
|
- lib/rainbows/writer_thread_spawn.rb
|
143
145
|
- LICENSE
|
@@ -178,6 +180,7 @@ files:
|
|
178
180
|
- Test_Suite
|
179
181
|
- bin/rainbows
|
180
182
|
- lib/rainbows.rb
|
183
|
+
- lib/rainbows/acceptor.rb
|
181
184
|
- lib/rainbows/actor_spawn.rb
|
182
185
|
- lib/rainbows/app_pool.rb
|
183
186
|
- lib/rainbows/base.rb
|
@@ -229,6 +232,7 @@ files:
|
|
229
232
|
- lib/rainbows/tee_input.rb
|
230
233
|
- lib/rainbows/thread_pool.rb
|
231
234
|
- lib/rainbows/thread_spawn.rb
|
235
|
+
- lib/rainbows/thread_timeout.rb
|
232
236
|
- lib/rainbows/writer_thread_pool.rb
|
233
237
|
- lib/rainbows/writer_thread_spawn.rb
|
234
238
|
- local.mk.sample
|
@@ -250,6 +254,7 @@ files:
|
|
250
254
|
- t/bin/unused_listen
|
251
255
|
- t/bin/utee
|
252
256
|
- t/close-pipe-response.ru
|
257
|
+
- t/close-pipe-to_path-response.ru
|
253
258
|
- t/content-md5.ru
|
254
259
|
- t/cramp/README
|
255
260
|
- t/cramp/rainsocket.ru
|
@@ -302,6 +307,7 @@ files:
|
|
302
307
|
- t/t0015-working_directory.sh
|
303
308
|
- t/t0016-onenine-encoding-is-tricky.sh
|
304
309
|
- t/t0016.rb
|
310
|
+
- t/t0017-keepalive-timeout-zero.sh
|
305
311
|
- t/t0020-large-sendfile-response.sh
|
306
312
|
- t/t0021-sendfile-wrap-to_path.sh
|
307
313
|
- t/t0022-copy_stream-byte-range.sh
|
@@ -309,6 +315,7 @@ files:
|
|
309
315
|
- t/t0024-pipelined-sendfile-response.sh
|
310
316
|
- t/t0030-fast-pipe-response.sh
|
311
317
|
- t/t0031-close-pipe-response.sh
|
318
|
+
- t/t0032-close-pipe-to_path-response.sh
|
312
319
|
- t/t0034-pipelined-pipe-response.sh
|
313
320
|
- t/t0100-rack-input-hammer-chunked.sh
|
314
321
|
- t/t0100-rack-input-hammer-content-length.sh
|
@@ -332,6 +339,10 @@ files:
|
|
332
339
|
- t/t9001.ru
|
333
340
|
- t/t9002-server-token.sh
|
334
341
|
- t/t9002.ru
|
342
|
+
- t/t9100-thread-timeout.sh
|
343
|
+
- t/t9100.ru
|
344
|
+
- t/t9101-thread-timeout-threshold.sh
|
345
|
+
- t/t9101.ru
|
335
346
|
- t/test-lib.sh
|
336
347
|
- t/test_isolate.rb
|
337
348
|
- t/worker-follows-master-to-death.ru
|