rainbows 0.6.0 → 0.7.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/.document +1 -0
- data/Documentation/GNUmakefile +4 -1
- data/Documentation/comparison.css +6 -0
- data/Documentation/comparison.haml +297 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +24 -17
- data/README +32 -28
- data/Summary +7 -0
- data/TODO +4 -6
- data/bin/rainbows +2 -2
- data/lib/rainbows.rb +33 -3
- data/lib/rainbows/actor_spawn.rb +29 -0
- data/lib/rainbows/app_pool.rb +17 -6
- data/lib/rainbows/base.rb +10 -13
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/dev_fd_response.rb +6 -0
- data/lib/rainbows/error.rb +34 -0
- data/lib/rainbows/ev_core.rb +3 -12
- data/lib/rainbows/event_machine.rb +7 -9
- data/lib/rainbows/fiber.rb +15 -0
- data/lib/rainbows/fiber/base.rb +112 -0
- data/lib/rainbows/fiber/io.rb +65 -0
- data/lib/rainbows/fiber/queue.rb +35 -0
- data/lib/rainbows/fiber_pool.rb +44 -0
- data/lib/rainbows/fiber_spawn.rb +34 -0
- data/lib/rainbows/http_server.rb +14 -1
- data/lib/rainbows/never_block.rb +69 -0
- data/lib/rainbows/rev.rb +7 -0
- data/lib/rainbows/rev/client.rb +9 -3
- data/lib/rainbows/rev/core.rb +2 -5
- data/lib/rainbows/rev/heartbeat.rb +5 -1
- data/lib/rainbows/rev_thread_spawn.rb +62 -60
- data/lib/rainbows/revactor.rb +22 -23
- data/lib/rainbows/thread_pool.rb +28 -26
- data/lib/rainbows/thread_spawn.rb +33 -33
- data/local.mk.sample +9 -7
- data/rainbows.gemspec +8 -2
- data/t/GNUmakefile +14 -7
- data/t/fork-sleep.ru +10 -0
- data/t/simple-http_FiberPool.ru +9 -0
- data/t/simple-http_FiberSpawn.ru +9 -0
- data/t/simple-http_NeverBlock.ru +11 -0
- data/t/sleep.ru +2 -0
- data/t/t0000-simple-http.sh +12 -1
- data/t/t0001-unix-http.sh +12 -1
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009.ru +13 -0
- data/t/t0010-keepalive-timeout-effective.sh +42 -0
- data/t/t0011-close-on-exec-set.sh +54 -0
- data/t/t0300-async_sinatra.sh +1 -1
- data/t/t9000-rack-app-pool.sh +1 -1
- data/t/t9000.ru +8 -5
- data/t/test-lib.sh +14 -4
- metadata +33 -5
- data/lib/rainbows/ev_thread_core.rb +0 -80
data/lib/rainbows/revactor.rb
CHANGED
@@ -23,21 +23,30 @@ module Rainbows
|
|
23
23
|
module Revactor
|
24
24
|
require 'rainbows/revactor/tee_input'
|
25
25
|
|
26
|
+
RD_ARGS = {}
|
27
|
+
|
26
28
|
include Base
|
27
29
|
|
28
30
|
# once a client is accepted, it is processed in its entirety here
|
29
31
|
# in 3 easy steps: read request, call app, write app response
|
30
32
|
def process_client(client)
|
31
|
-
|
33
|
+
defined?(Fcntl::FD_CLOEXEC) and
|
34
|
+
client.instance_eval { @_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
35
|
+
rd_args = [ nil ]
|
36
|
+
remote_addr = if ::Revactor::TCP::Socket === client
|
37
|
+
rd_args << RD_ARGS
|
38
|
+
client.remote_addr
|
39
|
+
else
|
40
|
+
LOCALHOST
|
41
|
+
end
|
42
|
+
buf = client.read(*rd_args)
|
32
43
|
hp = HttpParser.new
|
33
44
|
env = {}
|
34
45
|
alive = true
|
35
|
-
remote_addr = ::Revactor::TCP::Socket === client ?
|
36
|
-
client.remote_addr : LOCALHOST
|
37
46
|
|
38
47
|
begin
|
39
48
|
while ! hp.headers(env, buf)
|
40
|
-
buf << client.read
|
49
|
+
buf << client.read(*rd_args)
|
41
50
|
end
|
42
51
|
|
43
52
|
env[Const::RACK_INPUT] = 0 == hp.content_length ?
|
@@ -56,9 +65,11 @@ module Rainbows
|
|
56
65
|
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
57
66
|
HttpResponse.write(client, response, out)
|
58
67
|
end while alive and hp.reset.nil? and env.clear
|
59
|
-
|
68
|
+
rescue ::Revactor::TCP::ReadError
|
60
69
|
rescue => e
|
61
70
|
handle_error(client, e)
|
71
|
+
ensure
|
72
|
+
client.close
|
62
73
|
end
|
63
74
|
|
64
75
|
# runs inside each forked worker, this sits around and waits
|
@@ -66,6 +77,7 @@ module Rainbows
|
|
66
77
|
# given a INT, QUIT, or TERM signal)
|
67
78
|
def worker_loop(worker)
|
68
79
|
init_worker_process(worker)
|
80
|
+
RD_ARGS[:timeout] = G.kato if G.kato > 0
|
69
81
|
|
70
82
|
root = Actor.current
|
71
83
|
root.trap_exit = true
|
@@ -85,8 +97,8 @@ module Rainbows
|
|
85
97
|
clients[actor.object_id] = actor
|
86
98
|
root.link(actor)
|
87
99
|
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
88
|
-
rescue
|
89
|
-
|
100
|
+
rescue => e
|
101
|
+
Error.listen_loop(e)
|
90
102
|
end while G.alive
|
91
103
|
end
|
92
104
|
end
|
@@ -109,23 +121,10 @@ module Rainbows
|
|
109
121
|
# if the socket is already closed or broken. We'll always ensure
|
110
122
|
# the socket is closed at the end of this function
|
111
123
|
def handle_error(client, e)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
when HttpParserError # try to tell the client they're bad
|
116
|
-
Const::ERROR_400_RESPONSE
|
117
|
-
else
|
118
|
-
logger.error "Read error: #{e.inspect}"
|
119
|
-
logger.error e.backtrace.join("\n")
|
120
|
-
Const::ERROR_500_RESPONSE
|
121
|
-
end
|
122
|
-
client.instance_eval do
|
123
|
-
# this is Revactor implementation dependent
|
124
|
-
@_io.write_nonblock(msg)
|
125
|
-
close
|
126
|
-
end
|
124
|
+
# this is Revactor implementation dependent
|
125
|
+
msg = Error.response(e) and
|
126
|
+
client.instance_eval { @_io.write_nonblock(msg) }
|
127
127
|
rescue
|
128
|
-
nil
|
129
128
|
end
|
130
129
|
|
131
130
|
def revactorize_listeners!
|
data/lib/rainbows/thread_pool.rb
CHANGED
@@ -27,42 +27,44 @@ module Rainbows
|
|
27
27
|
|
28
28
|
def worker_loop(worker)
|
29
29
|
init_worker_process(worker)
|
30
|
-
pool = (1..worker_connections).map
|
30
|
+
pool = (1..worker_connections).map do
|
31
|
+
Thread.new { LISTENERS.size == 1 ? sync_worker : async_worker }
|
32
|
+
end
|
31
33
|
|
32
34
|
while G.alive
|
33
35
|
# if any worker dies, something is serious wrong, bail
|
34
36
|
pool.each do |thr|
|
35
|
-
G.tick
|
37
|
+
G.tick or break
|
36
38
|
thr.join(1) and G.quit!
|
37
39
|
end
|
38
40
|
end
|
39
41
|
join_threads(pool)
|
40
42
|
end
|
41
43
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
44
|
+
def sync_worker
|
45
|
+
s = LISTENERS.first
|
46
|
+
begin
|
47
|
+
process_client(s.accept)
|
48
|
+
rescue Errno::EINTR, Errno::ECONNABORTED
|
49
|
+
rescue => e
|
50
|
+
Error.listen_loop(e)
|
51
|
+
end while G.alive
|
52
|
+
end
|
53
|
+
|
54
|
+
def async_worker
|
55
|
+
begin
|
56
|
+
# TODO: check if select() or accept() is a problem on large
|
57
|
+
# SMP systems under Ruby 1.9. Hundreds of native threads
|
58
|
+
# all working off the same socket could be a thundering herd
|
59
|
+
# problem. On the other hand, a thundering herd may not
|
60
|
+
# even incur as much overhead as an extra Mutex#synchronize
|
61
|
+
ret = IO.select(LISTENERS, nil, nil, 1) and ret.first.each do |s|
|
62
|
+
s = Rainbows.accept(s) and process_client(s)
|
63
|
+
end
|
64
|
+
rescue Errno::EINTR
|
65
|
+
rescue => e
|
66
|
+
Error.listen_loop(e)
|
67
|
+
end while G.alive
|
66
68
|
end
|
67
69
|
|
68
70
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
require 'thread'
|
2
3
|
module Rainbows
|
3
4
|
|
4
5
|
# Spawns a new thread for every client connection we accept(). This
|
@@ -19,43 +20,42 @@ module Rainbows
|
|
19
20
|
|
20
21
|
include Base
|
21
22
|
|
22
|
-
def
|
23
|
-
|
24
|
-
threads = ThreadGroup.new
|
23
|
+
def accept_loop(klass)
|
24
|
+
lock = Mutex.new
|
25
25
|
limit = worker_connections
|
26
|
-
|
27
|
-
|
28
|
-
ret = begin
|
29
|
-
G.tick or break
|
30
|
-
IO.select(LISTENERS, nil, nil, 1) or next
|
31
|
-
rescue Errno::EINTR
|
32
|
-
retry
|
33
|
-
rescue Errno::EBADF, TypeError
|
34
|
-
break
|
35
|
-
end
|
36
|
-
G.tick
|
37
|
-
|
38
|
-
ret.first.each do |l|
|
39
|
-
# Sleep if we're busy, another less busy worker process may
|
40
|
-
# take it for us if we sleep. This is gross but other options
|
41
|
-
# still suck because they require expensive/complicated
|
42
|
-
# synchronization primitives for _every_ case, not just this
|
43
|
-
# unlikely one. Since this case is (or should be) uncommon,
|
44
|
-
# just busy wait when we have to.
|
45
|
-
while threads.list.size > limit # unlikely
|
46
|
-
sleep(0.1) # hope another process took it
|
47
|
-
break # back to IO.select
|
48
|
-
end
|
26
|
+
LISTENERS.each do |l|
|
27
|
+
klass.new(l) do |l|
|
49
28
|
begin
|
50
|
-
|
51
|
-
|
52
|
-
|
29
|
+
if lock.synchronize { G.cur >= limit }
|
30
|
+
# Sleep if we're busy, another less busy worker process may
|
31
|
+
# take it for us if we sleep. This is gross but other options
|
32
|
+
# still suck because they require expensive/complicated
|
33
|
+
# synchronization primitives for _every_ case, not just this
|
34
|
+
# unlikely one. Since this case is (or should be) uncommon,
|
35
|
+
# just busy wait when we have to.
|
36
|
+
sleep(0.01)
|
37
|
+
else
|
38
|
+
klass.new(l.accept) do |c|
|
39
|
+
begin
|
40
|
+
lock.synchronize { G.cur += 1 }
|
41
|
+
process_client(c)
|
42
|
+
ensure
|
43
|
+
lock.synchronize { G.cur -= 1 }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue Errno::EINTR, Errno::ECONNABORTED
|
48
|
+
rescue => e
|
49
|
+
Error.listen_loop(e)
|
50
|
+
end while G.alive
|
53
51
|
end
|
54
|
-
|
55
|
-
|
56
|
-
end while true
|
57
|
-
join_threads(threads.list)
|
52
|
+
end
|
53
|
+
sleep 1 while G.tick || lock.synchronize { G.cur > 0 }
|
58
54
|
end
|
59
55
|
|
56
|
+
def worker_loop(worker)
|
57
|
+
init_worker_process(worker)
|
58
|
+
accept_loop(Thread)
|
59
|
+
end
|
60
60
|
end
|
61
61
|
end
|
data/local.mk.sample
CHANGED
@@ -4,12 +4,15 @@
|
|
4
4
|
#
|
5
5
|
# This is depends on a bunch of GNU-isms from bash, sed, touch.
|
6
6
|
|
7
|
+
RSYNC = rsync
|
7
8
|
DLEXT := so
|
8
9
|
gems := rack-1.0.1
|
9
|
-
# gems += unicorn-0.95.
|
10
|
-
gems += rev-0.3.1
|
10
|
+
# gems += unicorn-0.95.1 # installed via setup.rb
|
11
|
+
gems += rev-0.3.1 # up to 0.3.2 once it's out for 1.8
|
12
|
+
gems += iobuffer-0.1.3
|
11
13
|
gems += eventmachine-0.12.10
|
12
14
|
gems += async_sinatra-0.1.5 sinatra-0.9.4
|
15
|
+
gems += espace-neverblock-0.1.6.1
|
13
16
|
|
14
17
|
# Avoid loading rubygems to speed up tests because gmake is
|
15
18
|
# fork+exec heavy with Ruby.
|
@@ -51,20 +54,19 @@ latest: NEWS
|
|
51
54
|
# publishes docs to http://rainbows.rubyforge.org
|
52
55
|
publish_doc:
|
53
56
|
-git set-file-times
|
54
|
-
$(RM) -r doc
|
55
|
-
$(MAKE) doc
|
57
|
+
$(RM) -r doc ChangeLog NEWS
|
58
|
+
$(MAKE) doc LOG_VERSION=$(shell git tag -l | tail -1)
|
56
59
|
$(MAKE) -s latest > doc/LATEST
|
57
60
|
find doc/images doc/js -type f | \
|
58
61
|
TZ=UTC xargs touch -d '1970-01-01 00:00:00' doc/rdoc.css
|
59
62
|
$(MAKE) doc_gz
|
60
63
|
chmod 644 $$(find doc -type f)
|
61
|
-
|
62
|
-
|
64
|
+
$(RSYNC) -av doc/ rubyforge.org:/var/www/gforge-projects/rainbows/
|
65
|
+
$(RSYNC) -av doc/ dcvr:/srv/rainbows/
|
63
66
|
git ls-files | xargs touch
|
64
67
|
|
65
68
|
# Create gzip variants of the same timestamp as the original so nginx
|
66
69
|
# "gzip_static on" can serve the gzipped versions directly.
|
67
|
-
doc_gz: suf := html js css
|
68
70
|
doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.\(gif\|jpg\|png\|gz\)$$')
|
69
71
|
doc_gz:
|
70
72
|
touch doc/NEWS.atom.xml -d "$$(awk 'NR==1{print $$4,$$5,$$6}' NEWS)"
|
data/rainbows.gemspec
CHANGED
@@ -54,10 +54,16 @@ Gem::Specification.new do |s|
|
|
54
54
|
# s.add_dependency(%q<revactor>, [">= 0.1.5"])
|
55
55
|
#
|
56
56
|
# Revactor depends on Rev, too, 0.3.0 got the ability to attach IOs
|
57
|
-
# s.add_dependency(%q<rev>, [">= 0.3.
|
57
|
+
# s.add_dependency(%q<rev>, [">= 0.3.1"])
|
58
|
+
#
|
59
|
+
# Rev depends on IOBuffer, which got faster in 0.1.3
|
60
|
+
# s.add_dependency(%q<iobuffer>, [">= 0.1.3"])
|
58
61
|
#
|
59
62
|
# We use the new EM::attach/watch API in 0.12.10
|
60
63
|
# s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
|
64
|
+
#
|
65
|
+
# NeverBlock, currently only available on http://gems.github.com/
|
66
|
+
# s.add_dependency(%q<espace-neverblock>, ["~> 0.1.6.1"])
|
61
67
|
|
62
|
-
# s.licenses = %w(GPLv2 Ruby) # accessor not compatible with older
|
68
|
+
# s.licenses = %w(GPLv2 Ruby) # accessor not compatible with older RubyGems
|
63
69
|
end
|
data/t/GNUmakefile
CHANGED
@@ -4,13 +4,17 @@ all::
|
|
4
4
|
|
5
5
|
pid := $(shell echo $$PPID)
|
6
6
|
|
7
|
-
RUBY =
|
7
|
+
RUBY = ruby
|
8
8
|
rainbows_lib := $(shell cd ../lib && pwd)
|
9
9
|
-include ../local.mk
|
10
10
|
ifeq ($(RUBY_VERSION),)
|
11
11
|
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
12
12
|
endif
|
13
13
|
|
14
|
+
ifeq ($(RUBY_VERSION),)
|
15
|
+
$(error unable to detect RUBY_VERSION)
|
16
|
+
endif
|
17
|
+
|
14
18
|
ifeq ($(RUBYLIB),)
|
15
19
|
RUBYLIB := $(rainbows_lib)
|
16
20
|
else
|
@@ -18,10 +22,16 @@ else
|
|
18
22
|
endif
|
19
23
|
export RUBYLIB RUBY_VERSION
|
20
24
|
|
21
|
-
models
|
22
|
-
|
23
|
-
|
25
|
+
models = ThreadPool ThreadSpawn Rev EventMachine NeverBlock
|
26
|
+
rp := )
|
27
|
+
ONENINE := $(shell case $(RUBY_VERSION) in 1.9.*$(rp) echo true;;esac)
|
28
|
+
ifeq ($(ONENINE),true)
|
24
29
|
models += Revactor
|
30
|
+
models += FiberSpawn
|
31
|
+
models += FiberPool
|
32
|
+
|
33
|
+
# technically this works under 1.8, but wait until rev 0.3.2
|
34
|
+
models += RevThreadSpawn
|
25
35
|
endif
|
26
36
|
all_models := $(models) Base
|
27
37
|
|
@@ -37,9 +47,6 @@ t0002-graceful.sh: MODELS = $(all_models)
|
|
37
47
|
t0002-parser-error.sh: MODELS = $(all_models)
|
38
48
|
t0003-reopen-logs.sh: MODELS = $(all_models)
|
39
49
|
|
40
|
-
# this test is not compatible with non-Thread models yet
|
41
|
-
t9000-rack-app-pool.sh: MODELS = ThreadPool ThreadSpawn
|
42
|
-
|
43
50
|
# recursively run per-model tests
|
44
51
|
# haven't figured out a good way to make make non-recursive here, yet...
|
45
52
|
$(T):
|
data/t/fork-sleep.ru
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#\-E none
|
2
|
+
# we do not want Rack::Lint or anything to protect us
|
3
|
+
use Rack::ContentLength
|
4
|
+
use Rack::ContentType, "text/plain"
|
5
|
+
trap(:CHLD) { $stderr.puts Process.waitpid2(-1).inspect }
|
6
|
+
map "/" do
|
7
|
+
time = ENV["nr"] || '15'
|
8
|
+
pid = fork { exec('sleep', time) }
|
9
|
+
run lambda { |env| [ 200, {}, [ "#{pid}\n" ] ] }
|
10
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
use Rack::ContentLength
|
2
|
+
use Rack::ContentType
|
3
|
+
run lambda { |env|
|
4
|
+
if env['rack.multithread'] == false &&
|
5
|
+
EM.reactor_running? &&
|
6
|
+
env['rainbows.model'] == :NeverBlock
|
7
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
8
|
+
else
|
9
|
+
raise env.inspect
|
10
|
+
end
|
11
|
+
}
|
data/t/sleep.ru
CHANGED
data/t/t0000-simple-http.sh
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
. ./test-lib.sh
|
3
|
-
t_plan
|
3
|
+
t_plan 25 "simple HTTP connection keepalive/pipelining tests for $model"
|
4
4
|
|
5
5
|
t_begin "checking for config.ru for $model" && {
|
6
6
|
tbase=simple-http_$model.ru
|
@@ -21,6 +21,17 @@ t_begin "single request" && {
|
|
21
21
|
curl -sSfv http://$listen/
|
22
22
|
}
|
23
23
|
|
24
|
+
t_begin "handles client EOF gracefully" && {
|
25
|
+
printf 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n' | \
|
26
|
+
socat - TCP4:$listen > $tmp
|
27
|
+
dbgcat tmp
|
28
|
+
if grep 'HTTP.* 500' $tmp
|
29
|
+
then
|
30
|
+
die "500 error returned on client shutdown(SHUT_WR)"
|
31
|
+
fi
|
32
|
+
check_stderr
|
33
|
+
}
|
34
|
+
|
24
35
|
dbgcat r_err
|
25
36
|
|
26
37
|
t_begin "two requests with keepalive" && {
|
data/t/t0001-unix-http.sh
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
. ./test-lib.sh
|
3
|
-
t_plan
|
3
|
+
t_plan 19 "simple HTTP connection keepalive/pipelining tests for $model"
|
4
4
|
|
5
5
|
t_begin "checking for config.ru for $model" && {
|
6
6
|
tbase=simple-http_$model.ru
|
@@ -23,6 +23,17 @@ t_begin "single TCP request" && {
|
|
23
23
|
curl -sSfv http://$listen/
|
24
24
|
}
|
25
25
|
|
26
|
+
t_begin "handles client EOF gracefully" && {
|
27
|
+
printf 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n' | \
|
28
|
+
socat - UNIX:$unix_socket > $tmp
|
29
|
+
dbgcat tmp
|
30
|
+
if grep 'HTTP.* 500' $tmp
|
31
|
+
then
|
32
|
+
die "500 error returned on client shutdown(SHUT_WR)"
|
33
|
+
fi
|
34
|
+
check_stderr
|
35
|
+
}
|
36
|
+
|
26
37
|
dbgcat r_err
|
27
38
|
|
28
39
|
t_begin "pipelining partial requests" && {
|