rainbows 0.96.0 → 0.97.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/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
|