rainbows 0.2.0 → 0.3.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/GIT-VERSION-GEN +1 -1
- data/TODO +15 -0
- data/TUNING +14 -0
- data/lib/rainbows.rb +7 -0
- data/lib/rainbows/app_pool.rb +4 -3
- data/lib/rainbows/base.rb +17 -11
- data/lib/rainbows/const.rb +5 -1
- data/lib/rainbows/dev_fd_response.rb +69 -0
- data/lib/rainbows/http_response.rb +1 -0
- data/lib/rainbows/http_server.rb +2 -0
- data/lib/rainbows/rev.rb +136 -43
- data/lib/rainbows/revactor.rb +6 -14
- data/lib/rainbows/thread_pool.rb +11 -13
- data/lib/rainbows/thread_spawn.rb +4 -4
- data/local.mk.sample +9 -1
- data/t/GNUmakefile +4 -4
- data/t/README +1 -1
- data/t/async-response-no-autochunk.ru +24 -0
- data/t/async-response.ru +13 -0
- data/t/bin/content-md5-put +1 -1
- data/t/bin/utee +12 -0
- data/t/env.ru +3 -0
- data/t/large-file-response.ru +13 -0
- data/t/lib-async-response-no-autochunk.sh +6 -0
- data/t/lib-async-response.sh +45 -0
- data/t/lib-graceful.sh +40 -0
- data/t/lib-input-trailer.sh +63 -0
- data/t/lib-large-file-response.sh +45 -0
- data/t/lib-parser-error.sh +29 -0
- data/t/{t3100-revactor-tee-input.sh → lib-rack-input-hammer.sh} +3 -9
- data/t/lib-reopen-logs.sh +60 -0
- data/t/lib-simple-http.sh +92 -0
- data/t/sleep.ru +13 -6
- data/t/t0000-basic.sh +1 -36
- data/t/t1000-thread-pool-basic.sh +1 -41
- data/t/t1002-thread-pool-graceful.sh +1 -36
- data/t/t1003-thread-pool-reopen-logs.sh +2 -0
- data/t/t1004-thread-pool-async-response.sh +45 -0
- data/t/t1005-thread-pool-large-file-response.sh +45 -0
- data/t/t1006-thread-pool-async-response-no-autochunk.sh +6 -0
- data/t/t1100-thread-pool-rack-input.sh +2 -0
- data/t/t1101-thread-pool-input-trailer.sh +2 -0
- data/t/t2000-thread-spawn-basic.sh +1 -37
- data/t/t2002-thread-spawn-graceful.sh +1 -36
- data/t/t2003-thread-spawn-reopen-logs.sh +2 -0
- data/t/t2004-thread-spawn-async-response.sh +45 -0
- data/t/t2005-thread-spawn-large-file-response.sh +45 -0
- data/t/t2006-thread-spawn-async-response-no-autochunk.sh +6 -0
- data/t/t2100-thread-spawn-rack-input.sh +2 -0
- data/t/t2101-thread-spawn-input-trailer.sh +2 -0
- data/t/t3000-revactor-basic.sh +1 -39
- data/t/t3002-revactor-graceful.sh +1 -37
- data/t/t3003-revactor-reopen-logs.sh +1 -53
- data/t/t3004-revactor-async-response.sh +45 -0
- data/t/t3005-revactor-large-file-response.sh +2 -0
- data/t/t3006-revactor-async-response-no-autochunk.sh +6 -0
- data/t/t3100-revactor-rack-input.sh +2 -0
- data/t/t3101-revactor-rack-input-trailer.sh +2 -0
- data/t/t4000-rev-basic.sh +1 -50
- data/t/t4002-rev-graceful.sh +1 -51
- data/t/t4003-rev-parser-error.sh +1 -33
- data/t/t4003-rev-reopen-logs.sh +2 -0
- data/t/t4004-rev-async-response.sh +45 -0
- data/t/t4005-rev-large-file-response.sh +2 -0
- data/t/t4006-rev-async-response-no-autochunk.sh +6 -0
- data/t/t4100-rev-rack-input.sh +1 -43
- data/t/t4101-rev-rack-input-trailer.sh +1 -50
- data/t/t9000-rack-app-pool.sh +2 -3
- data/t/test-lib.sh +80 -18
- metadata +39 -4
- data/t/t3001-revactor-pipeline.sh +0 -46
data/GIT-VERSION-GEN
CHANGED
data/TODO
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
We're lazy and pick the easy items to do first, then the ones people
|
4
4
|
care about.
|
5
5
|
|
6
|
+
* Improve test suite coverage. We won't waste cycles with puny
|
7
|
+
unit tests, only integration tests that exercise externally
|
8
|
+
visible parts.
|
9
|
+
|
10
|
+
* (maybe) make our tests TAP-compatible. Unfortunately the the
|
11
|
+
only shell library we've seen for TAP seems to use bashisms.
|
12
|
+
|
6
13
|
* Rev + Thread - current Rev model with threading, which will give
|
7
14
|
us a streaming (but rewindable) "rack.input".
|
8
15
|
|
@@ -10,6 +17,12 @@ care about.
|
|
10
17
|
(our benevolent dictator doesn't like C++). If we can figure out how
|
11
18
|
to do Rev without Revactor, then this should be pretty easy.
|
12
19
|
|
20
|
+
* EventMachine + catch/throw :async API used in Thin
|
21
|
+
|
22
|
+
* EventMachine.spawn - should be like Revactor, maybe?
|
23
|
+
|
24
|
+
* Rev + callcc - current Rev model with callcc (should work with MBARI)
|
25
|
+
|
13
26
|
* Fiber support - Revactor already uses these with Ruby 1.9, also not
|
14
27
|
sure how TeeInput can be done with this.
|
15
28
|
|
@@ -17,3 +30,5 @@ care about.
|
|
17
30
|
|
18
31
|
* Rubinius Actors - should be like Revactor and easily doable once
|
19
32
|
Rubinius gets more mature.
|
33
|
+
|
34
|
+
* test and improve performance (throughput/latency/memory usage)
|
data/TUNING
CHANGED
@@ -7,6 +7,13 @@ of the time waiting on network activity. Thus memory usage and memory
|
|
7
7
|
bandwidth for keeping connections open are often limiting factors as
|
8
8
|
well.
|
9
9
|
|
10
|
+
As of October 2009, absolutely ZERO work has been done for performance
|
11
|
+
validation and tuning. Furthermore, \Rainbows! is NOT expected to do well on
|
12
|
+
traditional benchmarks. Remember that \Rainbows! is only designed for
|
13
|
+
applications that sleep and/or trickle network traffic. In the future,
|
14
|
+
*may* do well in traditional benchmarks as a side effect, but that will
|
15
|
+
never be the primary goal of the project.
|
16
|
+
|
10
17
|
== \Rainbows! configuration
|
11
18
|
|
12
19
|
* Don't set +worker_connections+ too high. It is often better to start
|
@@ -18,6 +25,13 @@ well.
|
|
18
25
|
robust against crashes and are more likely to be fairly scheduled by
|
19
26
|
the kernel.
|
20
27
|
|
28
|
+
* If your workers do not seem to be releasing memory to the OS after
|
29
|
+
traffic spikes, consider the {mall}[http://bogomips.org/mall/] library
|
30
|
+
which allows access to the mallopt(3) function from Ruby. As of
|
31
|
+
October 2009 tcmalloc (the default allocator for Ruby Enterprise
|
32
|
+
Edition) does not release memory back to the kernel, the best it can
|
33
|
+
do is use madvise(2) in an effort to swap out unused pages.
|
34
|
+
|
21
35
|
== nginx configuration
|
22
36
|
|
23
37
|
If you intend to use nginx as a reverse-proxy in front of \Rainbows! to
|
data/lib/rainbows.rb
CHANGED
@@ -3,11 +3,18 @@ require 'unicorn'
|
|
3
3
|
|
4
4
|
module Rainbows
|
5
5
|
|
6
|
+
# global vars because class/instance variables are confusing me :<
|
7
|
+
# this struct is only accessed inside workers and thus private to each
|
8
|
+
G = Struct.new(:cur, :max, :logger, :alive, :app).new
|
9
|
+
# G.cur may not be used the network concurrency model
|
10
|
+
G.alive = true
|
11
|
+
|
6
12
|
require 'rainbows/const'
|
7
13
|
require 'rainbows/http_server'
|
8
14
|
require 'rainbows/http_response'
|
9
15
|
require 'rainbows/base'
|
10
16
|
autoload :AppPool, 'rainbows/app_pool'
|
17
|
+
autoload :DevFdResponse, 'rainbows/dev_fd_response'
|
11
18
|
|
12
19
|
class << self
|
13
20
|
|
data/lib/rainbows/app_pool.rb
CHANGED
@@ -39,10 +39,11 @@ module Rainbows
|
|
39
39
|
# AppPool should be used if you want to enforce a lower value of +P+
|
40
40
|
# than +N+.
|
41
41
|
#
|
42
|
-
# AppPool has no effect on the
|
42
|
+
# AppPool has no effect on the Rev concurrency model as that is
|
43
43
|
# single-threaded/single-instance as far as application concurrency goes.
|
44
|
-
# In other words, +P+ is always +one+ when using
|
45
|
-
#
|
44
|
+
# In other words, +P+ is always +one+ when using Rev. AppPool currently
|
45
|
+
# only works with the ThreadSpawn and ThreadPool models. It does not
|
46
|
+
# yet work reliably with the Revactor model, yet.
|
46
47
|
#
|
47
48
|
# Since this is Rack middleware, you may load this in your Rack
|
48
49
|
# config.ru file and even use it in servers other than \Rainbows!
|
data/lib/rainbows/base.rb
CHANGED
@@ -8,6 +8,7 @@ module Rainbows
|
|
8
8
|
|
9
9
|
include Unicorn
|
10
10
|
include Rainbows::Const
|
11
|
+
G = Rainbows::G
|
11
12
|
|
12
13
|
# write a response without caring if it went out or not for error
|
13
14
|
# messages.
|
@@ -17,21 +18,28 @@ module Rainbows
|
|
17
18
|
client.close rescue nil
|
18
19
|
end
|
19
20
|
|
20
|
-
# TODO: migrate into Unicorn::HttpServer
|
21
21
|
def listen_loop_error(e)
|
22
|
+
G.alive or return
|
22
23
|
logger.error "Unhandled listen loop exception #{e.inspect}."
|
23
24
|
logger.error e.backtrace.join("\n")
|
24
25
|
end
|
25
26
|
|
26
27
|
def init_worker_process(worker)
|
27
28
|
super(worker)
|
29
|
+
G.cur = 0
|
30
|
+
G.max = worker_connections
|
31
|
+
G.logger = logger
|
32
|
+
G.app = app
|
28
33
|
|
29
34
|
# we're don't use the self-pipe mechanism in the Rainbows! worker
|
30
35
|
# since we don't defer reopening logs
|
31
36
|
HttpServer::SELF_PIPE.each { |x| x.close }.clear
|
32
37
|
trap(:USR1) { reopen_worker_logs(worker.nr) rescue nil }
|
33
|
-
|
34
|
-
|
38
|
+
trap(:QUIT) do
|
39
|
+
G.alive = false
|
40
|
+
# closing anything we IO.select on will raise EBADF
|
41
|
+
HttpServer::LISTENERS.map! { |s| s.close rescue nil }
|
42
|
+
end
|
35
43
|
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
36
44
|
logger.info "Rainbows! #@use worker_connections=#@worker_connections"
|
37
45
|
end
|
@@ -62,7 +70,7 @@ module Rainbows
|
|
62
70
|
response = app.call(env)
|
63
71
|
end
|
64
72
|
|
65
|
-
alive = hp.keepalive? &&
|
73
|
+
alive = hp.keepalive? && G.alive
|
66
74
|
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
67
75
|
HttpResponse.write(client, response, out)
|
68
76
|
end while alive and hp.reset.nil? and env.clear
|
@@ -82,23 +90,21 @@ module Rainbows
|
|
82
90
|
end
|
83
91
|
|
84
92
|
def join_threads(threads, worker)
|
85
|
-
|
86
|
-
|
87
|
-
t0 = Time.now
|
88
|
-
timeleft = timeout * 2.0
|
93
|
+
Rainbows::G.alive = false
|
94
|
+
expire = Time.now + (timeout * 2.0)
|
89
95
|
m = 0
|
90
|
-
while (nr = threads.count { |thr| thr.alive? }) > 0
|
96
|
+
while (nr = threads.count { |thr| thr.alive? }) > 0
|
91
97
|
threads.each { |thr|
|
92
98
|
worker.tmp.chmod(m = 0 == m ? 1 : 0)
|
93
99
|
thr.join(1)
|
94
|
-
break if
|
100
|
+
break if Time.now >= expire
|
95
101
|
}
|
96
102
|
end
|
97
|
-
logger.info "Done joining threads. #{nr} left running"
|
98
103
|
end
|
99
104
|
|
100
105
|
def self.included(klass)
|
101
106
|
klass.const_set :LISTENERS, HttpServer::LISTENERS
|
107
|
+
klass.const_set :G, Rainbows::G
|
102
108
|
end
|
103
109
|
|
104
110
|
end
|
data/lib/rainbows/const.rb
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
module Rainbows
|
4
4
|
|
5
5
|
module Const
|
6
|
-
RAINBOWS_VERSION = '0.
|
6
|
+
RAINBOWS_VERSION = '0.3.0'
|
7
7
|
|
8
8
|
include Unicorn::Const
|
9
9
|
|
10
10
|
RACK_DEFAULTS = ::Unicorn::HttpRequest::DEFAULTS.merge({
|
11
11
|
"SERVER_SOFTWARE" => "Rainbows! #{RAINBOWS_VERSION}",
|
12
|
+
|
13
|
+
# using the Rev model, we'll automatically chunk pipe and socket objects
|
14
|
+
# if they're the response body
|
15
|
+
'rainbows.autochunk' => false,
|
12
16
|
})
|
13
17
|
|
14
18
|
CONN_CLOSE = "Connection: close\r\n"
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
# Rack response middleware wrapping any IO-like object with an
|
6
|
+
# OS-level file descriptor associated with it. May also be used to
|
7
|
+
# create responses from integer file descriptors or existing +IO+
|
8
|
+
# objects. This may be used in conjunction with the #to_path method
|
9
|
+
# on servers that support it to pass arbitrary file descriptors into
|
10
|
+
# the HTTP response without additional open(2) syscalls
|
11
|
+
|
12
|
+
class DevFdResponse < Struct.new(:app, :to_io, :to_path)
|
13
|
+
include Rack::Utils
|
14
|
+
|
15
|
+
# Rack middleware entry point, we'll just pass through responses
|
16
|
+
# unless they respond to +to_io+ or +to_path+
|
17
|
+
def call(env)
|
18
|
+
status, headers, body = response = app.call(env)
|
19
|
+
|
20
|
+
# totally uninteresting to us if there's no body
|
21
|
+
return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
22
|
+
|
23
|
+
io = body.to_io if body.respond_to?(:to_io)
|
24
|
+
io ||= File.open(body.to_path, 'rb') if body.respond_to?(:to_path)
|
25
|
+
return response if io.nil?
|
26
|
+
|
27
|
+
headers = HeaderHash.new(headers)
|
28
|
+
st = io.stat
|
29
|
+
if st.file?
|
30
|
+
headers['Content-Length'] ||= st.size.to_s
|
31
|
+
headers.delete('Transfer-Encoding')
|
32
|
+
elsif st.pipe? || st.socket? # epoll-able things
|
33
|
+
if env['rainbows.autochunk']
|
34
|
+
headers['Transfer-Encoding'] = 'chunked'
|
35
|
+
headers.delete('Content-Length')
|
36
|
+
else
|
37
|
+
headers['X-Rainbows-Autochunk'] = 'no'
|
38
|
+
end
|
39
|
+
else # unlikely, char/block device file, directory, ...
|
40
|
+
return response
|
41
|
+
end
|
42
|
+
resp = dup # be reentrant here
|
43
|
+
resp.to_path = "/dev/fd/#{io.fileno}"
|
44
|
+
resp.to_io = io
|
45
|
+
[ status, headers.to_hash, resp ]
|
46
|
+
end
|
47
|
+
|
48
|
+
# called by the webserver or other middlewares if they can't
|
49
|
+
# handle #to_path
|
50
|
+
def each(&block)
|
51
|
+
to_io.each(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# remain Rack::Lint-compatible for people with wonky systems :P
|
55
|
+
unless File.exist?("/dev/fd/0")
|
56
|
+
alias to_path_orig to_path
|
57
|
+
undef_method :to_path
|
58
|
+
end
|
59
|
+
|
60
|
+
# called by the web server after #each
|
61
|
+
def close
|
62
|
+
begin
|
63
|
+
to_io.close if to_io.respond_to?(:close)
|
64
|
+
rescue IOError # could've been IO::new()'ed and closed
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end # class
|
69
|
+
end
|
data/lib/rainbows/http_server.rb
CHANGED
@@ -32,6 +32,8 @@ module Rainbows
|
|
32
32
|
raise ArgumentError, "concurrency model #{model.inspect} not supported"
|
33
33
|
extend(mod)
|
34
34
|
Const::RACK_DEFAULTS['rainbows.model'] = @use = model
|
35
|
+
Const::RACK_DEFAULTS['rack.multithread'] = !!(/Thread/ =~ model.to_s)
|
36
|
+
Const::RACK_DEFAULTS['rainbows.autochunk'] = (model.to_s == "Rev")
|
35
37
|
end
|
36
38
|
|
37
39
|
def worker_connections(*args)
|
data/lib/rainbows/rev.rb
CHANGED
@@ -12,8 +12,10 @@ module Rainbows
|
|
12
12
|
# thousands of simultaneous client connections, but with only a
|
13
13
|
# single-threaded app dispatch. It is suited for slow clients and
|
14
14
|
# fast applications (applications that do not have slow network
|
15
|
-
# dependencies)
|
16
|
-
#
|
15
|
+
# dependencies) or applications that use DevFdResponse for deferrable
|
16
|
+
# response bodies. It does not require your Rack application to be
|
17
|
+
# thread-safe, reentrancy is only required for the DevFdResponse body
|
18
|
+
# generator.
|
17
19
|
#
|
18
20
|
# Compatibility: Whatever \Rev itself supports, currently Ruby
|
19
21
|
# 1.8/1.9.
|
@@ -22,39 +24,41 @@ module Rainbows
|
|
22
24
|
# allows the Rack application to process data as it arrives. This
|
23
25
|
# means "rack.input" will be fully buffered in memory or to a
|
24
26
|
# temporary file before the application is entered.
|
25
|
-
#
|
26
|
-
# Caveats: this model can buffer all output for slow clients in
|
27
|
-
# memory. This can be a problem if your application generates large
|
28
|
-
# responses (including static files served with Rack) as it will cause
|
29
|
-
# the memory footprint of your process to explode. If your workers
|
30
|
-
# seem to be eating a lot of memory from this, consider the
|
31
|
-
# {mall}[http://bogomips.org/mall/] library which allows access to the
|
32
|
-
# mallopt(3) function from Ruby.
|
33
27
|
|
34
28
|
module Rev
|
35
29
|
|
36
|
-
# global vars because class/instance variables are confusing me :<
|
37
|
-
# this struct is only accessed inside workers and thus private to each
|
38
|
-
G = Struct.new(:nr, :max, :logger, :alive, :app).new
|
39
|
-
|
40
30
|
include Base
|
41
31
|
|
42
32
|
class Client < ::Rev::IO
|
43
33
|
include Unicorn
|
44
34
|
include Rainbows::Const
|
45
|
-
G = Rainbows::
|
35
|
+
G = Rainbows::G
|
36
|
+
|
37
|
+
# queued, optional response bodies, it should only be unpollable "fast"
|
38
|
+
# devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
|
39
|
+
# are also part of this. We'll also stick DeferredResponse bodies in
|
40
|
+
# here to prevent connections from being closed on us.
|
41
|
+
attr_reader :deferred_bodies
|
46
42
|
|
47
43
|
def initialize(io)
|
48
|
-
G.
|
44
|
+
G.cur += 1
|
49
45
|
super(io)
|
50
46
|
@remote_addr = ::TCPSocket === io ? io.peeraddr.last : LOCALHOST
|
51
47
|
@env = {}
|
52
48
|
@hp = HttpParser.new
|
53
49
|
@state = :headers # [ :body [ :trailers ] ] :app_call :close
|
54
50
|
@buf = ""
|
51
|
+
@deferred_bodies = [] # for (fast) regular files only
|
52
|
+
end
|
53
|
+
|
54
|
+
# graceful exit, like SIGQUIT
|
55
|
+
def quit
|
56
|
+
@deferred_bodies.clear
|
57
|
+
@state = :close
|
55
58
|
end
|
56
59
|
|
57
60
|
def handle_error(e)
|
61
|
+
quit
|
58
62
|
msg = case e
|
59
63
|
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
60
64
|
ERROR_500_RESPONSE
|
@@ -66,33 +70,51 @@ module Rainbows
|
|
66
70
|
ERROR_500_RESPONSE
|
67
71
|
end
|
68
72
|
write(msg)
|
69
|
-
ensure
|
70
|
-
@state = :close
|
71
73
|
end
|
72
74
|
|
73
75
|
def app_call
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
76
|
+
begin
|
77
|
+
(@env[RACK_INPUT] = @input).rewind
|
78
|
+
alive = @hp.keepalive?
|
79
|
+
@env[REMOTE_ADDR] = @remote_addr
|
80
|
+
response = G.app.call(@env.update(RACK_DEFAULTS))
|
81
|
+
alive &&= G.alive
|
82
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
|
83
|
+
|
84
|
+
DeferredResponse.write(self, response, out)
|
85
|
+
if alive
|
86
|
+
@env.clear
|
87
|
+
@hp.reset
|
88
|
+
@state = :headers
|
89
|
+
# keepalive requests are always body-less, so @input is unchanged
|
90
|
+
@hp.headers(@env, @buf) and next
|
91
|
+
else
|
92
|
+
@state = :close
|
93
|
+
end
|
94
|
+
return
|
95
|
+
end while true
|
88
96
|
end
|
89
97
|
|
90
98
|
def on_write_complete
|
91
|
-
|
99
|
+
if body = @deferred_bodies.first
|
100
|
+
return if DeferredResponse === body
|
101
|
+
begin
|
102
|
+
begin
|
103
|
+
write(body.sysread(CHUNK_SIZE))
|
104
|
+
rescue EOFError # expected at file EOF
|
105
|
+
@deferred_bodies.shift
|
106
|
+
body.close
|
107
|
+
end
|
108
|
+
rescue Object => e
|
109
|
+
handle_error(e)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
close if :close == @state
|
113
|
+
end
|
92
114
|
end
|
93
115
|
|
94
116
|
def on_close
|
95
|
-
G.
|
117
|
+
G.cur -= 1
|
96
118
|
end
|
97
119
|
|
98
120
|
def tmpio
|
@@ -144,10 +166,10 @@ module Rainbows
|
|
144
166
|
end
|
145
167
|
|
146
168
|
class Server < ::Rev::IO
|
147
|
-
G = Rainbows::
|
169
|
+
G = Rainbows::G
|
148
170
|
|
149
171
|
def on_readable
|
150
|
-
return if G.
|
172
|
+
return if G.cur >= G.max
|
151
173
|
begin
|
152
174
|
Client.new(@_io.accept_nonblock).attach(::Rev::Loop.default)
|
153
175
|
rescue Errno::EAGAIN, Errno::ECONNBORTED
|
@@ -156,18 +178,89 @@ module Rainbows
|
|
156
178
|
|
157
179
|
end
|
158
180
|
|
181
|
+
class DeferredResponse < ::Rev::IO
|
182
|
+
include Unicorn
|
183
|
+
include Rainbows::Const
|
184
|
+
G = Rainbows::G
|
185
|
+
|
186
|
+
def self.defer!(client, response, out)
|
187
|
+
body = response.last
|
188
|
+
headers = Rack::Utils::HeaderHash.new(response[1])
|
189
|
+
|
190
|
+
# to_io is not part of the Rack spec, but make an exception
|
191
|
+
# here since we can't get here without checking to_path first
|
192
|
+
io = body.to_io if body.respond_to?(:to_io)
|
193
|
+
io ||= ::IO.new($1.to_i) if body.to_path =~ %r{\A/dev/fd/(\d+)\z}
|
194
|
+
io ||= File.open(File.expand_path(body.to_path), 'rb')
|
195
|
+
st = io.stat
|
196
|
+
|
197
|
+
if st.socket? || st.pipe?
|
198
|
+
do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
|
199
|
+
do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
|
200
|
+
# too tricky to support keepalive/pipelining when a response can
|
201
|
+
# take an indeterminate amount of time here.
|
202
|
+
out[0] = CONN_CLOSE
|
203
|
+
|
204
|
+
io = new(io, client, do_chunk, body).attach(::Rev::Loop.default)
|
205
|
+
elsif st.file?
|
206
|
+
headers.delete('Transfer-Encoding')
|
207
|
+
headers['Content-Length'] ||= st.size.to_s
|
208
|
+
else # char/block device, directory, whatever... nobody cares
|
209
|
+
return response
|
210
|
+
end
|
211
|
+
client.deferred_bodies << io
|
212
|
+
[ response.first, headers.to_hash, [] ]
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.write(client, response, out)
|
216
|
+
response.last.respond_to?(:to_path) and
|
217
|
+
response = defer!(client, response, out)
|
218
|
+
HttpResponse.write(client, response, out)
|
219
|
+
end
|
220
|
+
|
221
|
+
def initialize(io, client, do_chunk, body)
|
222
|
+
super(io)
|
223
|
+
@client, @do_chunk, @body = client, do_chunk, body
|
224
|
+
end
|
225
|
+
|
226
|
+
def on_read(data)
|
227
|
+
@do_chunk and @client.write(sprintf("%x\r\n", data.size))
|
228
|
+
@client.write(data)
|
229
|
+
@do_chunk and @client.write("\r\n")
|
230
|
+
end
|
231
|
+
|
232
|
+
def on_close
|
233
|
+
@do_chunk and @client.write("0\r\n\r\n")
|
234
|
+
@client.quit
|
235
|
+
@body.respond_to?(:close) and @body.close
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# This timer handles the fchmod heartbeat to prevent our master
|
240
|
+
# from killing us.
|
241
|
+
class Heartbeat < ::Rev::TimerWatcher
|
242
|
+
G = Rainbows::G
|
243
|
+
|
244
|
+
def initialize(tmp)
|
245
|
+
@m, @tmp = 0, tmp
|
246
|
+
super(1, true)
|
247
|
+
end
|
248
|
+
|
249
|
+
def on_timer
|
250
|
+
@tmp.chmod(@m = 0 == @m ? 1 : 0)
|
251
|
+
exit if (! G.alive && G.cur <= 0)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
159
255
|
# runs inside each forked worker, this sits around and waits
|
160
256
|
# for connections and doesn't die until the parent dies (or is
|
161
257
|
# given a INT, QUIT, or TERM signal)
|
162
258
|
def worker_loop(worker)
|
163
259
|
init_worker_process(worker)
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
G.app = app
|
169
|
-
LISTENERS.map! { |s| Server.new(s).attach(::Rev::Loop.default) }
|
170
|
-
::Rev::Loop.default.run
|
260
|
+
rloop = ::Rev::Loop.default
|
261
|
+
Heartbeat.new(worker.tmp).attach(rloop)
|
262
|
+
LISTENERS.map! { |s| Server.new(s).attach(rloop) }
|
263
|
+
rloop.run
|
171
264
|
end
|
172
265
|
|
173
266
|
end
|