rainbows 0.92.0 → 0.93.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 +45 -6
- data/FAQ +10 -4
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +3 -3
- data/README +2 -0
- data/Rakefile +16 -2
- data/config/isolate.rb +2 -2
- data/lib/rainbows.rb +3 -0
- data/lib/rainbows/base.rb +2 -0
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/queue_pool.rb +32 -0
- data/lib/rainbows/thread_spawn.rb +3 -0
- data/lib/rainbows/writer_thread_pool.rb +90 -0
- data/lib/rainbows/writer_thread_spawn.rb +113 -0
- data/local.mk.sample +1 -1
- data/rainbows.gemspec +1 -1
- data/t/.gitignore +1 -0
- data/t/GNUmakefile +2 -0
- data/t/simple-http_WriterThreadPool.ru +9 -0
- data/t/simple-http_WriterThreadSpawn.ru +9 -0
- data/t/t0004-heartbeat-timeout.sh +16 -5
- data/t/t0005-large-file-response.sh +8 -4
- data/t/t0007-worker-follows-master-to-death.sh +6 -1
- data/t/t0102-rack-input-short.sh +1 -1
- data/t/t0103-rack-input-limit.sh +1 -0
- data/t/t0104-rack-input-limit-tiny.sh +1 -0
- data/t/t0105-rack-input-limit-bigger.sh +2 -0
- data/t/t0200-async-response.sh +2 -0
- data/t/test-lib.sh +29 -1
- metadata +11 -3
@@ -99,6 +99,20 @@
|
|
99
99
|
%td.r19 Yes
|
100
100
|
%td.rbx No
|
101
101
|
%td.slow Yes
|
102
|
+
%tr.comp_row
|
103
|
+
%td.mod WriterThreadPool
|
104
|
+
%td.tee Yes
|
105
|
+
%td.r18 Yes
|
106
|
+
%td.r19 Yes
|
107
|
+
%td.rbx Yes
|
108
|
+
%td.slow no
|
109
|
+
%tr.comp_row
|
110
|
+
%td.mod WriterThreadSpawn
|
111
|
+
%td.tee Yes
|
112
|
+
%td.r18 Yes
|
113
|
+
%td.r19 Yes
|
114
|
+
%td.rbx Yes
|
115
|
+
%td.slow no
|
102
116
|
%ul
|
103
117
|
%li
|
104
118
|
RevThread* + 1.8 requires Rev >= 0.3.2 for reasonable performance
|
@@ -203,7 +217,16 @@
|
|
203
217
|
%a(href="Rainbows/Fiber/IO.html") Rainbows::Fiber::IO
|
204
218
|
%td.thr No
|
205
219
|
%td.reent Yes
|
206
|
-
|
220
|
+
%tr.comp_base
|
221
|
+
%td.mod WriterThreadPool
|
222
|
+
%td.slowio avoid
|
223
|
+
%td.thr Maybe
|
224
|
+
%td.reent Maybe
|
225
|
+
%tr.comp_base
|
226
|
+
%td.mod WriterThreadSpawn
|
227
|
+
%td.slowio avoid
|
228
|
+
%td.thr Maybe
|
229
|
+
%td.reent Maybe
|
207
230
|
%ul
|
208
231
|
%li
|
209
232
|
Requirements for single thread reentrancy are loose in that there is
|
@@ -219,6 +242,9 @@
|
|
219
242
|
on UNIX domain sockets and named pipes. Nearly all other operations
|
220
243
|
on POSIX filesystems can be considered "fast", or at least
|
221
244
|
uninterruptible.
|
245
|
+
%li
|
246
|
+
WriterThread{Pool,Spawn} will require thread safety if your response
|
247
|
+
body is dynamically generated during the body#each call.
|
222
248
|
|
223
249
|
%h2 middlewares and frameworks
|
224
250
|
%br
|
@@ -250,7 +276,7 @@
|
|
250
276
|
%td.ws Sunshowers
|
251
277
|
%tr.comp_row
|
252
278
|
%td.mod ThreadPool
|
253
|
-
%td.devfd
|
279
|
+
%td.devfd Yes
|
254
280
|
%td.app_pool Yes
|
255
281
|
%td.lock Yes
|
256
282
|
%td.async standard Ruby
|
@@ -264,7 +290,7 @@
|
|
264
290
|
%td.ws no
|
265
291
|
%tr.comp_row
|
266
292
|
%td.mod ThreadSpawn
|
267
|
-
%td.devfd
|
293
|
+
%td.devfd Yes
|
268
294
|
%td.app_pool Yes
|
269
295
|
%td.lock Yes
|
270
296
|
%td.async standard Ruby
|
@@ -316,15 +342,28 @@
|
|
316
342
|
%td.devfd Yes
|
317
343
|
%td.app_pool Yes
|
318
344
|
%td.lock Dumb
|
319
|
-
%td.async standard Ruby
|
345
|
+
%td.async Rev, standard Ruby
|
320
346
|
%td.ws no
|
321
347
|
%tr.comp_row
|
322
348
|
%td.mod RevFiberSpawn
|
323
349
|
%td.devfd Yes
|
324
350
|
%td.app_pool Yes
|
325
351
|
%td.lock No!
|
326
|
-
%td.async Rainbows::Fiber::IO, Rainbows.sleep
|
327
|
-
|
352
|
+
%td.async Rev, Rainbows::Fiber::IO, Rainbows.sleep
|
353
|
+
%tr.comp_row
|
354
|
+
%td.mod WriterThreadPool
|
355
|
+
%td.devfd Yes
|
356
|
+
%td.app_pool no-op
|
357
|
+
%td.lock no-op
|
358
|
+
%td.async Standard Ruby in response body only
|
359
|
+
%td.ws response body only
|
360
|
+
%tr.comp_row
|
361
|
+
%td.mod WriterThreadSpawn
|
362
|
+
%td.devfd Yes
|
363
|
+
%td.app_pool no-op
|
364
|
+
%td.lock no-op
|
365
|
+
%td.async Standard Ruby in response body only
|
366
|
+
%td.ws response body only
|
328
367
|
%ul
|
329
368
|
%li
|
330
369
|
"No!" means it's fundamentally incompatible, use an
|
data/FAQ
CHANGED
@@ -62,13 +62,19 @@ to ensure redirects go to "https://" URLs.
|
|
62
62
|
|
63
63
|
=== Is there a "rainbows_rails" command like there is "unicorn_rails"?
|
64
64
|
|
65
|
-
|
65
|
+
No.
|
66
66
|
|
67
67
|
"unicorn_rails" was written primarily to support older versions of
|
68
|
-
Rails. Since \Rainbows! is designed for newer
|
69
|
-
a "config.ru" file like other Rack frameworks and
|
68
|
+
Rails. Since \Rainbows! is designed for newer applications based on
|
69
|
+
Rack, it can just use a "config.ru" file like other Rack frameworks and
|
70
|
+
applications.
|
70
71
|
|
71
|
-
For Rails
|
72
|
+
For Rails 3.x, you should already have a config.ru file and
|
73
|
+
"rainbows(1)" will work out-of-the-box like "rackup(1)". Rails 3
|
74
|
+
will support RACK_ENV as set by "rainbows(1)", so you won't need
|
75
|
+
to set RAILS_ENV.
|
76
|
+
|
77
|
+
For Rails 2.3.x, the following config.ru will work for you:
|
72
78
|
|
73
79
|
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
|
74
80
|
require "config/environment"
|
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -17,8 +17,8 @@ ifeq ($(RUBY_VERSION),)
|
|
17
17
|
endif
|
18
18
|
|
19
19
|
# rake takes forever to start
|
20
|
-
isolate: tmp/
|
21
|
-
tmp/
|
20
|
+
isolate: tmp/isolate/ruby-$(RUBY_VERSION)/.isolate
|
21
|
+
tmp/isolate/ruby-$(RUBY_VERSION)/.isolate: $(ISOLATE_CONFIG)
|
22
22
|
ISOLATE_CONFIG=$(ISOLATE_CONFIG) $(RAKE) isolate
|
23
23
|
> $@
|
24
24
|
|
@@ -66,7 +66,7 @@ NEWS: GIT-VERSION-FILE
|
|
66
66
|
$(RAKE) -s news_rdoc > $@+
|
67
67
|
mv $@+ $@
|
68
68
|
|
69
|
-
SINCE = 0.
|
69
|
+
SINCE = 0.92.0
|
70
70
|
ChangeLog: LOG_VERSION = \
|
71
71
|
$(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
|
72
72
|
echo $(GIT_VERSION) || git describe)
|
data/README
CHANGED
@@ -24,6 +24,8 @@ For network concurrency, models we currently support are:
|
|
24
24
|
* {FiberPool}[link:Rainbows/FiberPool.html]
|
25
25
|
* {NeverBlock}[link:Rainbows/NeverBlock.html]
|
26
26
|
* {RevThreadPool}[link:Rainbows/RevThreadPool.html]
|
27
|
+
* {WriterThreadPool}[link:Rainbows/WriterThreadPool.html]
|
28
|
+
* {WriterThreadSpawn}[link:Rainbows/WriterThreadSpawn.html]
|
27
29
|
|
28
30
|
We have {many more on the way}[link:TODO.html] for handling network
|
29
31
|
concurrency. Additionally, we also use multiple processes (managed by
|
data/Rakefile
CHANGED
@@ -187,6 +187,20 @@ end
|
|
187
187
|
desc 'isolate gems for development'
|
188
188
|
task :isolate do
|
189
189
|
require 'isolate'
|
190
|
-
|
191
|
-
|
190
|
+
require 'rbconfig'
|
191
|
+
|
192
|
+
Isolate.now! :file => ENV['ISOLATE_CONFIG'], :system => false
|
193
|
+
|
194
|
+
# for Ruby 1.8 isolate uses "1.8" instead of "1.8.7" for paths,
|
195
|
+
# but we'll still try to support 1.8.6 for now even though isolate
|
196
|
+
# does not.
|
197
|
+
if Gem.ruby_engine == "ruby" &&
|
198
|
+
RbConfig::CONFIG["ruby_version"] != RUBY_VERSION
|
199
|
+
require 'fileutils'
|
200
|
+
Dir.chdir('tmp/isolate') do
|
201
|
+
FileUtils.rm_rf("ruby-#{RUBY_VERSION}")
|
202
|
+
File.symlink "ruby-#{RbConfig::CONFIG["ruby_version"]}",
|
203
|
+
"ruby-#{RUBY_VERSION}"
|
204
|
+
end
|
205
|
+
end
|
192
206
|
end
|
data/config/isolate.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
# `rake isolate' or (faster in the unmodified case, `make isolate')
|
5
5
|
|
6
6
|
gem 'rack', '1.1.0'
|
7
|
-
gem 'unicorn', '0.
|
7
|
+
gem 'unicorn', '0.99.0'
|
8
8
|
|
9
9
|
gem 'iobuffer', '0.1.3'
|
10
10
|
gem 'rev', '0.3.2'
|
@@ -22,4 +22,4 @@ if defined?(::Fiber)
|
|
22
22
|
gem 'rack-fiber_pool', '0.9.0'
|
23
23
|
end
|
24
24
|
|
25
|
-
gem 'cramp', '0.
|
25
|
+
gem 'cramp', '0.11'
|
data/lib/rainbows.rb
CHANGED
@@ -35,6 +35,7 @@ module Rainbows
|
|
35
35
|
autoload :AppPool, 'rainbows/app_pool'
|
36
36
|
autoload :DevFdResponse, 'rainbows/dev_fd_response'
|
37
37
|
autoload :MaxBody, 'rainbows/max_body'
|
38
|
+
autoload :QueuePool, 'rainbows/queue_pool'
|
38
39
|
|
39
40
|
class << self
|
40
41
|
|
@@ -133,6 +134,8 @@ module Rainbows
|
|
133
134
|
# highly recommended
|
134
135
|
MODEL_WORKER_CONNECTIONS = {
|
135
136
|
:Base => 1, # this one can't change
|
137
|
+
:WriterThreadPool => 20,
|
138
|
+
:WriterThreadSpawn => 20,
|
136
139
|
:Revactor => 50,
|
137
140
|
:ThreadSpawn => 30,
|
138
141
|
:ThreadPool => 20,
|
data/lib/rainbows/base.rb
CHANGED
data/lib/rainbows/const.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Rainbows
|
5
|
+
|
6
|
+
# Thread pool class based on pulling off a single Ruby Queue.
|
7
|
+
# This is NOT used for the ThreadPool class, since that class does not
|
8
|
+
# need a userspace Queue.
|
9
|
+
class QueuePool < Struct.new(:queue, :threads)
|
10
|
+
G = Rainbows::G
|
11
|
+
|
12
|
+
def initialize(size = 20, &block)
|
13
|
+
q = Queue.new
|
14
|
+
self.threads = (1..size).map do
|
15
|
+
Thread.new do
|
16
|
+
while job = q.shift
|
17
|
+
block.call(job)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self.queue = q
|
22
|
+
end
|
23
|
+
|
24
|
+
def quit!
|
25
|
+
threads.each { |_| queue << nil }
|
26
|
+
threads.delete_if do |t|
|
27
|
+
G.tick
|
28
|
+
t.alive? ? t.join(0.01) : true
|
29
|
+
end until threads.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -33,6 +33,9 @@ module Rainbows
|
|
33
33
|
# synchronization primitives for _every_ case, not just this
|
34
34
|
# unlikely one. Since this case is (or should be) uncommon,
|
35
35
|
# just busy wait when we have to.
|
36
|
+
# We don't use Thread.pass because it needlessly spins the
|
37
|
+
# CPU during I/O wait, CPU cycles that can be better used
|
38
|
+
# by other worker _processes_.
|
36
39
|
sleep(0.01)
|
37
40
|
elsif c = Rainbows.sync_accept(l)
|
38
41
|
klass.new(c) do |c|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
# This concurrency model implements a single-threaded app dispatch
|
6
|
+
# with a separate thread pool for writing responses.
|
7
|
+
#
|
8
|
+
# Unlike most \Rainbows! concurrency models, WriterThreadPool is
|
9
|
+
# designed to run behind nginx just like Unicorn is. This concurrency
|
10
|
+
# model may be useful for existing Unicorn users looking for more
|
11
|
+
# output concurrency than socket buffers can provide while still
|
12
|
+
# maintaining a single-threaded application dispatch (though if the
|
13
|
+
# response body is dynamically generated, it must be thread safe).
|
14
|
+
#
|
15
|
+
# For serving large or streaming responses, using more threads (via
|
16
|
+
# the +worker_connections+ setting) and setting "proxy_buffering off"
|
17
|
+
# in nginx is recommended. If your application does not handle
|
18
|
+
# uploads, then using any HTTP-aware proxy like haproxy is fine.
|
19
|
+
# Using a non-HTTP-aware proxy will leave you vulnerable to
|
20
|
+
# slow client denial-of-service attacks.
|
21
|
+
|
22
|
+
module WriterThreadPool
|
23
|
+
include Base
|
24
|
+
|
25
|
+
# used to wrap a BasicSocket to use with +q+ for all writes
|
26
|
+
# this is compatible with IO.select
|
27
|
+
class QueueSocket < Struct.new(:to_io, :q)
|
28
|
+
def readpartial(size, buf = "")
|
29
|
+
to_io.readpartial(size, buf)
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_nonblock(buf)
|
33
|
+
to_io.write_nonblock(buf)
|
34
|
+
end
|
35
|
+
|
36
|
+
def write(buf)
|
37
|
+
q << [ to_io, buf ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def close
|
41
|
+
q << [ to_io, :close ]
|
42
|
+
end
|
43
|
+
|
44
|
+
def closed?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
alias base_write_body write_body
|
50
|
+
if IO.respond_to?(:copy_stream)
|
51
|
+
undef_method :write_body
|
52
|
+
|
53
|
+
def write_body(qclient, body)
|
54
|
+
qclient.q << [ qclient.to_io, :body, body ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@@nr = 0
|
59
|
+
@@q = nil
|
60
|
+
|
61
|
+
def process_client(client)
|
62
|
+
@@nr += 1
|
63
|
+
super(QueueSocket[client, @@q[@@nr %= @@q.size]])
|
64
|
+
end
|
65
|
+
|
66
|
+
def worker_loop(worker)
|
67
|
+
# we have multiple, single-thread queues since we don't want to
|
68
|
+
# interleave writes from the same client
|
69
|
+
qp = (1..worker_connections).map do |n|
|
70
|
+
QueuePool.new(1) do |response|
|
71
|
+
begin
|
72
|
+
io, arg1, arg2 = response
|
73
|
+
case arg1
|
74
|
+
when :body then base_write_body(io, arg2)
|
75
|
+
when :close then io.close unless io.closed?
|
76
|
+
else
|
77
|
+
io.write(arg1)
|
78
|
+
end
|
79
|
+
rescue => err
|
80
|
+
Error.app(err)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@@q = qp.map { |q| q.queue }
|
86
|
+
super(worker) # accept loop from Unicorn
|
87
|
+
qp.map { |q| q.quit! }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'thread'
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
# This concurrency model implements a single-threaded app dispatch and
|
6
|
+
# spawns a new thread for writing responses. This concurrency model
|
7
|
+
# should be ideal for apps that serve large responses or stream
|
8
|
+
# responses slowly.
|
9
|
+
#
|
10
|
+
# Unlike most \Rainbows! concurrency models, WriterThreadSpawn is
|
11
|
+
# designed to run behind nginx just like Unicorn is. This concurrency
|
12
|
+
# model may be useful for existing Unicorn users looking for more
|
13
|
+
# output concurrency than socket buffers can provide while still
|
14
|
+
# maintaining a single-threaded application dispatch (though if the
|
15
|
+
# response body is generated on-the-fly, it must be thread safe).
|
16
|
+
#
|
17
|
+
# For serving large or streaming responses, setting
|
18
|
+
# "proxy_buffering off" in nginx is recommended. If your application
|
19
|
+
# does not handle uploads, then using any HTTP-aware proxy like
|
20
|
+
# haproxy is fine. Using a non-HTTP-aware proxy will leave you
|
21
|
+
# vulnerable to slow client denial-of-service attacks.
|
22
|
+
|
23
|
+
module WriterThreadSpawn
|
24
|
+
include Base
|
25
|
+
|
26
|
+
CUR = {}
|
27
|
+
|
28
|
+
# used to wrap a BasicSocket to use with +q+ for all writes
|
29
|
+
# this is compatible with IO.select
|
30
|
+
class MySocket < Struct.new(:to_io, :q, :thr)
|
31
|
+
def readpartial(size, buf = "")
|
32
|
+
to_io.readpartial(size, buf)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_nonblock(buf)
|
36
|
+
to_io.write_nonblock(buf)
|
37
|
+
end
|
38
|
+
|
39
|
+
def queue_writer
|
40
|
+
# not using Thread.pass here because that spins the CPU during
|
41
|
+
# I/O wait and will eat cycles from other worker processes.
|
42
|
+
until CUR.size < MAX
|
43
|
+
CUR.delete_if { |t,_|
|
44
|
+
t.alive? ? t.join(0) : true
|
45
|
+
}.size >= MAX and sleep(0.01)
|
46
|
+
end
|
47
|
+
|
48
|
+
q = Queue.new
|
49
|
+
self.thr = Thread.new(to_io, q) do |io, q|
|
50
|
+
while response = q.shift
|
51
|
+
begin
|
52
|
+
arg1, arg2 = response
|
53
|
+
case arg1
|
54
|
+
when :body then Base.write_body(io, arg2)
|
55
|
+
when :close
|
56
|
+
io.close unless io.closed?
|
57
|
+
break
|
58
|
+
else
|
59
|
+
io.write(arg1)
|
60
|
+
end
|
61
|
+
rescue => e
|
62
|
+
Error.app(e)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
CUR.delete(Thread.current)
|
66
|
+
end
|
67
|
+
CUR[thr] = q
|
68
|
+
end
|
69
|
+
|
70
|
+
def write(buf)
|
71
|
+
(self.q ||= queue_writer) << buf
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_body(body)
|
75
|
+
(self.q ||= queue_writer) << [ :body, body ]
|
76
|
+
end
|
77
|
+
|
78
|
+
def close
|
79
|
+
if q
|
80
|
+
q << :close
|
81
|
+
else
|
82
|
+
to_io.close
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def closed?
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if IO.respond_to?(:copy_stream)
|
92
|
+
undef_method :write_body
|
93
|
+
|
94
|
+
def write_body(my_sock, body)
|
95
|
+
my_sock.write_body(body)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def process_client(client)
|
100
|
+
super(MySocket[client])
|
101
|
+
end
|
102
|
+
|
103
|
+
def worker_loop(worker)
|
104
|
+
MySocket.const_set(:MAX, worker_connections)
|
105
|
+
super(worker) # accept loop from Unicorn
|
106
|
+
CUR.delete_if do |t,q|
|
107
|
+
q << nil
|
108
|
+
G.tick
|
109
|
+
t.alive? ? t.join(0.01) : true
|
110
|
+
end until CUR.empty?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/local.mk.sample
CHANGED
@@ -27,7 +27,7 @@ endif
|
|
27
27
|
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
28
28
|
|
29
29
|
updir := $(shell git rev-parse --show-cdup)
|
30
|
-
gem_paths := $(wildcard $(updir)tmp/
|
30
|
+
gem_paths := $(wildcard $(updir)tmp/isolate/ruby-$(RUBY_VERSION)/gems/*-*)
|
31
31
|
|
32
32
|
ifdef gem_paths
|
33
33
|
sp :=
|
data/rainbows.gemspec
CHANGED
@@ -43,7 +43,7 @@ Gem::Specification.new do |s|
|
|
43
43
|
# we need Unicorn for the HTTP parser and process management
|
44
44
|
# The HTTP parser in Unicorn <= 0.97.0 was vulnerable to a remote DoS
|
45
45
|
# when exposed directly to untrusted clients.
|
46
|
-
s.add_dependency(%q<unicorn>, [">= 0.97.1", "<
|
46
|
+
s.add_dependency(%q<unicorn>, [">= 0.97.1", "< 2.0.0"])
|
47
47
|
|
48
48
|
# Unicorn already depends on Rack
|
49
49
|
# s.add_dependency(%q<rack>)
|
data/t/.gitignore
CHANGED
data/t/GNUmakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
. ./test-lib.sh
|
3
3
|
|
4
|
-
t_plan
|
4
|
+
t_plan 12 "heartbeat/timeout test for $model"
|
5
5
|
|
6
6
|
t_begin "setup and startup" && {
|
7
7
|
rainbows_setup $model
|
@@ -22,8 +22,9 @@ t_begin "sleep for a bit, ensure worker PID does not change" && {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
t_begin "block the worker process to force it to die" && {
|
25
|
+
rm $ok
|
25
26
|
t0=$(date +%s)
|
26
|
-
err="$(curl -sSf http://$listen/block-forever 2>&1 ||
|
27
|
+
err="$(curl -sSf http://$listen/block-forever 2>&1 || > $ok)"
|
27
28
|
t1=$(date +%s)
|
28
29
|
elapsed=$(($t1 - $t0))
|
29
30
|
t_info "elapsed=$elapsed err=$err"
|
@@ -31,9 +32,13 @@ t_begin "block the worker process to force it to die" && {
|
|
31
32
|
test x"$err" != x"$worker_pid"
|
32
33
|
}
|
33
34
|
|
34
|
-
t_begin "ensure
|
35
|
+
t_begin "ensure worker was killed" && {
|
36
|
+
test -e $ok
|
37
|
+
test 1 -eq $(grep timeout $r_err | grep killing | wc -l)
|
38
|
+
}
|
39
|
+
|
40
|
+
t_begin "ensure timeout took at least 3 seconds" && {
|
35
41
|
test $elapsed -ge 3
|
36
|
-
test $elapsed -le 6 # give it some slack in case box is bogged down
|
37
42
|
}
|
38
43
|
|
39
44
|
t_begin "wait for new worker to start up" && {
|
@@ -45,6 +50,10 @@ t_begin "we get a fresh new worker process" && {
|
|
45
50
|
test $new_worker_pid -ne $worker_pid
|
46
51
|
}
|
47
52
|
|
53
|
+
t_begin "truncate the server error log" && {
|
54
|
+
> $r_err
|
55
|
+
}
|
56
|
+
|
48
57
|
t_begin "SIGSTOP and SIGCONT on rainbows master does not kill worker" && {
|
49
58
|
kill -STOP $rainbows_pid
|
50
59
|
sleep 4
|
@@ -54,9 +63,11 @@ t_begin "SIGSTOP and SIGCONT on rainbows master does not kill worker" && {
|
|
54
63
|
}
|
55
64
|
|
56
65
|
t_begin "stop server" && {
|
57
|
-
kill $rainbows_pid
|
66
|
+
kill -QUIT $rainbows_pid
|
58
67
|
}
|
59
68
|
|
69
|
+
t_begin "check stderr" && check_stderr
|
70
|
+
|
60
71
|
dbgcat r_err
|
61
72
|
|
62
73
|
t_done
|
@@ -12,7 +12,7 @@ t_plan 10 "large file response slurp avoidance for $model"
|
|
12
12
|
|
13
13
|
t_begin "setup and startup" && {
|
14
14
|
rtmpfiles curl_out
|
15
|
-
rainbows_setup $model
|
15
|
+
rainbows_setup $model 1
|
16
16
|
# can't load Rack::Lint here since it'll cause Rev to slurp
|
17
17
|
rainbows -E none -D large-file-response.ru -c $unicorn_config
|
18
18
|
rainbows_wait_start
|
@@ -50,11 +50,11 @@ t_begin "HTTP/1.0 test" && {
|
|
50
50
|
t_begin "HTTP/0.9 test" && {
|
51
51
|
(
|
52
52
|
printf 'GET /random_blob\r\n'
|
53
|
-
|
53
|
+
rsha1 < $fifo > $tmp &
|
54
54
|
wait
|
55
55
|
echo ok > $ok
|
56
56
|
) | socat - TCP:$listen > $fifo
|
57
|
-
|
57
|
+
test $(cat $tmp) = $(rsha1 < random_blob)
|
58
58
|
test xok = x$(cat $ok)
|
59
59
|
}
|
60
60
|
|
@@ -72,8 +72,12 @@ t_begin "shutdown server" && {
|
|
72
72
|
|
73
73
|
t_begin "compare RSS before and after" && {
|
74
74
|
diff=$(( $rss_after - $rss_before ))
|
75
|
+
|
76
|
+
# default GC malloc limit in MRI:
|
77
|
+
fudge=$(( 8 * 1024 * 1024 ))
|
78
|
+
|
75
79
|
t_info "test diff=$diff < orig=$random_blob_size"
|
76
|
-
test $diff -le $random_blob_size
|
80
|
+
test $diff -le $(( $random_blob_size + $fudge ))
|
77
81
|
}
|
78
82
|
|
79
83
|
dbgcat r_err
|
@@ -32,7 +32,12 @@ t_begin "nuke the master once we're connected" && {
|
|
32
32
|
}
|
33
33
|
|
34
34
|
t_begin "worker is no longer running" && {
|
35
|
-
|
35
|
+
nr=30
|
36
|
+
while kill -0 $worker_pid 2>/dev/null && test $nr -gt 0
|
37
|
+
do
|
38
|
+
nr=$(( $nr - 1))
|
39
|
+
sleep 1
|
40
|
+
done
|
36
41
|
kill -0 $worker_pid 2> $tmp && false
|
37
42
|
test -s $tmp
|
38
43
|
}
|
data/t/t0102-rack-input-short.sh
CHANGED
@@ -8,7 +8,7 @@ t_begin "setup and startup" && {
|
|
8
8
|
rtmpfiles curl_out curl_err
|
9
9
|
rainbows_setup $model
|
10
10
|
rainbows -D sha1-random-size.ru -c $unicorn_config
|
11
|
-
blob_sha1=$(rsha1 random_blob)
|
11
|
+
blob_sha1=$(rsha1 < random_blob)
|
12
12
|
t_info "blob_sha1=$blob_sha1"
|
13
13
|
rainbows_wait_start
|
14
14
|
}
|
data/t/t0103-rack-input-limit.sh
CHANGED
data/t/t0200-async-response.sh
CHANGED
data/t/test-lib.sh
CHANGED
@@ -42,6 +42,19 @@ require_check () {
|
|
42
42
|
fi
|
43
43
|
}
|
44
44
|
|
45
|
+
skip_models () {
|
46
|
+
for i in "$@"
|
47
|
+
do
|
48
|
+
if test x"$model" != x"$i"
|
49
|
+
then
|
50
|
+
continue
|
51
|
+
fi
|
52
|
+
t_info "skipping $T since it is not compatible with $model"
|
53
|
+
exit 0
|
54
|
+
done
|
55
|
+
}
|
56
|
+
|
57
|
+
|
45
58
|
# given a list of variable names, create temporary files and assign
|
46
59
|
# the pathnames to those variables
|
47
60
|
rtmpfiles () {
|
@@ -146,7 +159,22 @@ rsha1 () {
|
|
146
159
|
|
147
160
|
# last resort, see comments in sha1sum.rb for reasoning
|
148
161
|
test -n "$_cmd" || _cmd=sha1sum.rb
|
149
|
-
expr "$($_cmd
|
162
|
+
expr "$($_cmd)" : '\([a-f0-9]\{40\}\)'
|
163
|
+
}
|
164
|
+
|
165
|
+
req_curl_chunked_upload_err_check () {
|
166
|
+
set +e
|
167
|
+
curl --version 2>/dev/null | awk '$1 == "curl" {
|
168
|
+
split($2, v, /\./)
|
169
|
+
if ((v[1] < 7) || (v[1] == 7 && v[2] < 18))
|
170
|
+
code = 1
|
171
|
+
}
|
172
|
+
END { exit(code) }'
|
173
|
+
if test $? -ne 0
|
174
|
+
then
|
175
|
+
t_info "curl >= 7.18.0 required for $T"
|
176
|
+
exit 0
|
177
|
+
fi
|
150
178
|
}
|
151
179
|
|
152
180
|
case $model in
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rainbows
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.93.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rainbows! hackers
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-05-
|
12
|
+
date: 2010-05-29 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -23,7 +23,7 @@ dependencies:
|
|
23
23
|
version: 0.97.1
|
24
24
|
- - <
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.0.0
|
27
27
|
version:
|
28
28
|
description: |-
|
29
29
|
\Rainbows! is an HTTP server for sleepy Rack applications. It is based on
|
@@ -61,6 +61,7 @@ extra_rdoc_files:
|
|
61
61
|
- lib/rainbows/max_body.rb
|
62
62
|
- lib/rainbows/never_block.rb
|
63
63
|
- lib/rainbows/never_block/event_machine.rb
|
64
|
+
- lib/rainbows/queue_pool.rb
|
64
65
|
- lib/rainbows/rev.rb
|
65
66
|
- lib/rainbows/rev/client.rb
|
66
67
|
- lib/rainbows/rev/core.rb
|
@@ -75,6 +76,8 @@ extra_rdoc_files:
|
|
75
76
|
- lib/rainbows/tee_input.rb
|
76
77
|
- lib/rainbows/thread_pool.rb
|
77
78
|
- lib/rainbows/thread_spawn.rb
|
79
|
+
- lib/rainbows/writer_thread_pool.rb
|
80
|
+
- lib/rainbows/writer_thread_spawn.rb
|
78
81
|
- LICENSE
|
79
82
|
- NEWS
|
80
83
|
- README
|
@@ -131,6 +134,7 @@ files:
|
|
131
134
|
- lib/rainbows/max_body.rb
|
132
135
|
- lib/rainbows/never_block.rb
|
133
136
|
- lib/rainbows/never_block/event_machine.rb
|
137
|
+
- lib/rainbows/queue_pool.rb
|
134
138
|
- lib/rainbows/rev.rb
|
135
139
|
- lib/rainbows/rev/client.rb
|
136
140
|
- lib/rainbows/rev/core.rb
|
@@ -145,6 +149,8 @@ files:
|
|
145
149
|
- lib/rainbows/tee_input.rb
|
146
150
|
- lib/rainbows/thread_pool.rb
|
147
151
|
- lib/rainbows/thread_spawn.rb
|
152
|
+
- lib/rainbows/writer_thread_pool.rb
|
153
|
+
- lib/rainbows/writer_thread_spawn.rb
|
148
154
|
- local.mk.sample
|
149
155
|
- man/man1/rainbows.1
|
150
156
|
- rainbows.gemspec
|
@@ -187,6 +193,8 @@ files:
|
|
187
193
|
- t/simple-http_Revactor.ru
|
188
194
|
- t/simple-http_ThreadPool.ru
|
189
195
|
- t/simple-http_ThreadSpawn.ru
|
196
|
+
- t/simple-http_WriterThreadPool.ru
|
197
|
+
- t/simple-http_WriterThreadSpawn.ru
|
190
198
|
- t/sleep.ru
|
191
199
|
- t/t0000-simple-http.sh
|
192
200
|
- t/t0000.ru
|