rainbows 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/Summary
ADDED
data/TODO
CHANGED
@@ -7,15 +7,13 @@ care about.
|
|
7
7
|
unit tests, only integration tests that exercise externally
|
8
8
|
visible parts.
|
9
9
|
|
10
|
-
* Rev + Thread - current Rev model with threading, which will give
|
11
|
-
us a streaming (but rewindable) "rack.input".
|
12
|
-
|
13
10
|
* EventMachine.spawn - should be like Revactor, maybe?
|
14
11
|
|
15
|
-
* Rev
|
12
|
+
* {Packet,Rev,EventMachine}+Fibers
|
16
13
|
|
17
|
-
*
|
18
|
-
|
14
|
+
* {Packet,Rev}ThreadPool
|
15
|
+
|
16
|
+
* Rev + callcc - current Rev model with callcc (should work with MBARI)
|
19
17
|
|
20
18
|
* Omnibus - haven't looked into it, probably like Revactor with 1.8?
|
21
19
|
|
data/bin/rainbows
CHANGED
@@ -69,7 +69,7 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
69
69
|
opts.on("-P", "--pid FILE", "DEPRECATED") do |f|
|
70
70
|
warn %q{Use of --pid/-P is strongly discouraged}
|
71
71
|
warn %q{Use the 'pid' directive in the Unicorn config file instead}
|
72
|
-
options[:pid] =
|
72
|
+
options[:pid] = f
|
73
73
|
end
|
74
74
|
|
75
75
|
opts.on("-s", "--server SERVER",
|
@@ -86,7 +86,7 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
86
86
|
end
|
87
87
|
|
88
88
|
opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
|
89
|
-
options[:config_file] =
|
89
|
+
options[:config_file] = f
|
90
90
|
end
|
91
91
|
|
92
92
|
# I'm avoiding Unicorn-specific config options on the command-line.
|
data/lib/rainbows.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'unicorn'
|
3
|
+
require 'rainbows/error'
|
4
|
+
require 'fcntl'
|
3
5
|
|
4
6
|
module Rainbows
|
5
7
|
|
6
8
|
# global vars because class/instance variables are confusing me :<
|
7
9
|
# this struct is only accessed inside workers and thus private to each
|
8
|
-
# G.cur may not be used the network concurrency model
|
9
|
-
class State < Struct.new(:alive,:m,:cur,:server,:tmp)
|
10
|
+
# G.cur may not be used in the network concurrency model
|
11
|
+
class State < Struct.new(:alive,:m,:cur,:kato,:server,:tmp)
|
10
12
|
def tick
|
11
13
|
tmp.chmod(self.m = m == 0 ? 1 : 0)
|
12
14
|
alive && server.master_pid == Process.ppid or quit!
|
@@ -18,7 +20,8 @@ module Rainbows
|
|
18
20
|
false
|
19
21
|
end
|
20
22
|
end
|
21
|
-
G = State.new(true, 0, 0)
|
23
|
+
G = State.new(true, 0, 0, 2)
|
24
|
+
O = {}
|
22
25
|
|
23
26
|
require 'rainbows/const'
|
24
27
|
require 'rainbows/http_server'
|
@@ -43,6 +46,7 @@ module Rainbows
|
|
43
46
|
# Rainbows! do
|
44
47
|
# use :Revactor # this may also be :ThreadSpawn or :ThreadPool
|
45
48
|
# worker_connections 400
|
49
|
+
# keepalive_timeout 0 # zero disables keepalives entirely
|
46
50
|
# end
|
47
51
|
#
|
48
52
|
# # the rest of the Unicorn configuration
|
@@ -53,6 +57,12 @@ module Rainbows
|
|
53
57
|
# each of them. The total number of clients we're able to serve is
|
54
58
|
# +worker_processes+ * +worker_connections+, so in the above example
|
55
59
|
# we can serve 8 * 400 = 3200 clients concurrently.
|
60
|
+
#
|
61
|
+
# The default is +keepalive_timeout+ is 2 seconds, which should be
|
62
|
+
# enough under most conditions for browsers to render the page and
|
63
|
+
# start retrieving extra elements for. Increasing this beyond 5
|
64
|
+
# seconds is not recommended. Zero disables keepalive entirely
|
65
|
+
# (but pipelining fully-formed requests is still works).
|
56
66
|
def Rainbows!(&block)
|
57
67
|
block_given? or raise ArgumentError, "Rainbows! requires a block"
|
58
68
|
HttpServer.setup(block)
|
@@ -69,10 +79,30 @@ module Rainbows
|
|
69
79
|
:Rev => 50,
|
70
80
|
:RevThreadSpawn => 50,
|
71
81
|
:EventMachine => 50,
|
82
|
+
:FiberSpawn => 50,
|
83
|
+
:FiberPool => 50,
|
84
|
+
:ActorSpawn => 50,
|
85
|
+
:NeverBlock => 50,
|
72
86
|
}.each do |model, _|
|
73
87
|
u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" }
|
74
88
|
autoload model, "rainbows/#{u.downcase!}"
|
75
89
|
end
|
90
|
+
autoload :Fiber, 'rainbows/fiber' # core class
|
91
|
+
|
92
|
+
# returns nil if accept fails
|
93
|
+
if defined?(Fcntl::FD_CLOEXEC)
|
94
|
+
def self.accept(sock)
|
95
|
+
rv = sock.accept_nonblock
|
96
|
+
rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
97
|
+
rv
|
98
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
99
|
+
end
|
100
|
+
else
|
101
|
+
def self.accept(sock)
|
102
|
+
sock.accept_nonblock
|
103
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
104
|
+
end
|
105
|
+
end
|
76
106
|
|
77
107
|
end
|
78
108
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
require 'actor'
|
4
|
+
module Rainbows
|
5
|
+
|
6
|
+
# Actor concurrency model for Rubinius. We can't seem to get message
|
7
|
+
# passing working right, so we're throwing a Mutex into the mix for
|
8
|
+
# now. Hopefully somebody can fix things for us. Currently, this is
|
9
|
+
# exactly the same as the ThreadSpawn model since we don't use the
|
10
|
+
# message passing capabilities of the Actor model (and even then
|
11
|
+
# it wouldn't really make sense since Actors in Rubinius are just
|
12
|
+
# Threads underneath and our ThreadSpawn model is one layer of
|
13
|
+
# complexity less.
|
14
|
+
#
|
15
|
+
# This is different from the Revactor one which is not prone to race
|
16
|
+
# conditions within the same process at all (since it uses Fibers).
|
17
|
+
module ActorSpawn
|
18
|
+
include ThreadSpawn
|
19
|
+
|
20
|
+
# runs inside each forked worker, this sits around and waits
|
21
|
+
# for connections and doesn't die until the parent dies (or is
|
22
|
+
# given a INT, QUIT, or TERM signal)
|
23
|
+
def worker_loop(worker)
|
24
|
+
Const::RACK_DEFAULTS["rack.multithread"] = true # :(
|
25
|
+
init_worker_process(worker)
|
26
|
+
accept_loop(Actor)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rainbows/app_pool.rb
CHANGED
@@ -42,11 +42,8 @@ module Rainbows
|
|
42
42
|
# AppPool has no effect on the Rev or EventMachine concurrency models
|
43
43
|
# as those are single-threaded/single-instance as far as application
|
44
44
|
# concurrency goes. In other words, +P+ is always +one+ when using
|
45
|
-
# Rev or EventMachine.
|
46
|
-
#
|
47
|
-
# with the Revactor model, but actors are far more lightweight and
|
48
|
-
# probably better suited for lightweight applications that would
|
49
|
-
# not benefit from AppPool.
|
45
|
+
# Rev or EventMachine. As of \Rainbows! 0.7.0, it is safe to use with
|
46
|
+
# Revactor and the new FiberSpawn and FiberPool concurrency models.
|
50
47
|
#
|
51
48
|
# Since this is Rack middleware, you may load this in your Rack
|
52
49
|
# config.ru file and even use it in threaded servers other than
|
@@ -60,7 +57,7 @@ module Rainbows
|
|
60
57
|
# You may to load this earlier or later in your middleware chain
|
61
58
|
# depending on the concurrency/copy-friendliness of your middleware(s).
|
62
59
|
|
63
|
-
class AppPool < Struct.new(:pool)
|
60
|
+
class AppPool < Struct.new(:pool, :re)
|
64
61
|
|
65
62
|
# +opt+ is a hash, +:size+ is the size of the pool (default: 6)
|
66
63
|
# meaning you can have up to 6 concurrent instances of +app+
|
@@ -86,6 +83,20 @@ module Rainbows
|
|
86
83
|
|
87
84
|
# Rack application endpoint, +env+ is the Rack environment
|
88
85
|
def call(env)
|
86
|
+
|
87
|
+
# we have to do this check at call time (and not initialize)
|
88
|
+
# because of preload_app=true and models being changeable with SIGHUP
|
89
|
+
# fortunately this is safe for all the reentrant (but not multithreaded)
|
90
|
+
# classes that depend on it and a safe no-op for multithreaded
|
91
|
+
# concurrency models
|
92
|
+
self.re ||= begin
|
93
|
+
case env["rainbows.model"]
|
94
|
+
when :FiberSpawn, :FiberPool, :Revactor, :NeverBlock
|
95
|
+
self.pool = Rainbows::Fiber::Queue.new(pool)
|
96
|
+
end
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
89
100
|
app = pool.shift
|
90
101
|
app.call(env)
|
91
102
|
ensure
|
data/lib/rainbows/base.rb
CHANGED
@@ -10,10 +10,9 @@ module Rainbows
|
|
10
10
|
include Rainbows::Const
|
11
11
|
G = Rainbows::G
|
12
12
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
logger.error e.backtrace.join("\n")
|
13
|
+
def handle_error(client, e)
|
14
|
+
msg = Error.response(e) and client.write_nonblock(msg)
|
15
|
+
rescue
|
17
16
|
end
|
18
17
|
|
19
18
|
def init_worker_process(worker)
|
@@ -32,7 +31,7 @@ module Rainbows
|
|
32
31
|
# once a client is accepted, it is processed in its entirety here
|
33
32
|
# in 3 easy steps: read request, call app, write app response
|
34
33
|
def process_client(client)
|
35
|
-
buf = client.readpartial(CHUNK_SIZE)
|
34
|
+
buf = client.readpartial(CHUNK_SIZE) # accept filters protect us here
|
36
35
|
hp = HttpParser.new
|
37
36
|
env = {}
|
38
37
|
alive = true
|
@@ -40,6 +39,7 @@ module Rainbows
|
|
40
39
|
|
41
40
|
begin # loop
|
42
41
|
while ! hp.headers(env, buf)
|
42
|
+
IO.select([client], nil, nil, G.kato) or return
|
43
43
|
buf << client.readpartial(CHUNK_SIZE)
|
44
44
|
end
|
45
45
|
|
@@ -59,25 +59,22 @@ module Rainbows
|
|
59
59
|
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
60
60
|
HttpResponse.write(client, response, out)
|
61
61
|
end while alive and hp.reset.nil? and env.clear
|
62
|
-
client.close
|
63
62
|
# if we get any error, try to write something back to the client
|
64
63
|
# assuming we haven't closed the socket, but don't get hung up
|
65
64
|
# if the socket is already closed or broken. We'll always ensure
|
66
65
|
# the socket is closed at the end of this function
|
67
66
|
rescue => e
|
68
67
|
handle_error(client, e)
|
68
|
+
ensure
|
69
|
+
client.close
|
69
70
|
end
|
70
71
|
|
71
72
|
def join_threads(threads)
|
72
|
-
G.quit!
|
73
73
|
expire = Time.now + (timeout * 2.0)
|
74
|
-
until
|
75
|
-
threads.
|
76
|
-
G.tick
|
77
|
-
thr.join(1)
|
78
|
-
break if Time.now >= expire
|
79
|
-
}
|
74
|
+
until threads.empty? || Time.now >= expire
|
75
|
+
threads.delete_if { |thr| thr.alive? ? thr.join(0.01) : true }
|
80
76
|
end
|
77
|
+
exit!(0) unless threads.empty?
|
81
78
|
end
|
82
79
|
|
83
80
|
def self.included(klass)
|
data/lib/rainbows/const.rb
CHANGED
@@ -36,6 +36,12 @@ module Rainbows
|
|
36
36
|
else
|
37
37
|
headers['X-Rainbows-Autochunk'] = 'no'
|
38
38
|
end
|
39
|
+
|
40
|
+
# we need to make sure our pipe output is Fiber-compatible
|
41
|
+
case env["rainbows.model"]
|
42
|
+
when :FiberSpawn, :FiberPool
|
43
|
+
return [ status, headers.to_hash, Fiber::IO.new(io,::Fiber.current) ]
|
44
|
+
end
|
39
45
|
else # unlikely, char/block device file, directory, ...
|
40
46
|
return response
|
41
47
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
|
4
|
+
class Error
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def app(e)
|
8
|
+
G.server.logger.error "app error: #{e.inspect}"
|
9
|
+
G.server.logger.error e.backtrace.join("\n")
|
10
|
+
rescue
|
11
|
+
end
|
12
|
+
|
13
|
+
def listen_loop(e)
|
14
|
+
G.alive or return
|
15
|
+
G.server.logger.error "listen loop error: #{e.inspect}."
|
16
|
+
G.server.logger.error e.backtrace.join("\n")
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
|
20
|
+
def response(e)
|
21
|
+
case e
|
22
|
+
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
23
|
+
# swallow error if client shuts down one end or disconnects
|
24
|
+
when Unicorn::HttpParserError
|
25
|
+
Const::ERROR_400_RESPONSE # try to tell the client they're bad
|
26
|
+
else
|
27
|
+
app(e)
|
28
|
+
Const::ERROR_500_RESPONSE
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/rainbows/ev_core.rb
CHANGED
@@ -26,17 +26,7 @@ module Rainbows
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def handle_error(e)
|
29
|
-
msg =
|
30
|
-
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
31
|
-
ERROR_500_RESPONSE
|
32
|
-
when HttpParserError # try to tell the client they're bad
|
33
|
-
ERROR_400_RESPONSE
|
34
|
-
else
|
35
|
-
G.server.logger.error "Read error: #{e.inspect}"
|
36
|
-
G.server.logger.error e.backtrace.join("\n")
|
37
|
-
ERROR_500_RESPONSE
|
38
|
-
end
|
39
|
-
write(msg)
|
29
|
+
msg = Error.response(e) and write(msg)
|
40
30
|
ensure
|
41
31
|
quit
|
42
32
|
end
|
@@ -74,11 +64,12 @@ module Rainbows
|
|
74
64
|
end
|
75
65
|
when :trailers
|
76
66
|
if @hp.trailers(@env, @buf << data)
|
67
|
+
@input.rewind
|
77
68
|
app_call
|
78
69
|
@input.close if File === @input
|
79
70
|
end
|
80
71
|
end
|
81
|
-
rescue
|
72
|
+
rescue => e
|
82
73
|
handle_error(e)
|
83
74
|
end
|
84
75
|
|
@@ -51,8 +51,9 @@ module Rainbows
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def app_call
|
54
|
+
set_comm_inactivity_timeout 0
|
54
55
|
begin
|
55
|
-
|
56
|
+
@env[RACK_INPUT] = @input
|
56
57
|
@env[REMOTE_ADDR] = @remote_addr
|
57
58
|
@env[ASYNC_CALLBACK] = method(:response_write)
|
58
59
|
|
@@ -73,6 +74,7 @@ module Rainbows
|
|
73
74
|
@state = :headers
|
74
75
|
# keepalive requests are always body-less, so @input is unchanged
|
75
76
|
@hp.headers(@env, @buf) and next
|
77
|
+
set_comm_inactivity_timeout G.kato
|
76
78
|
end
|
77
79
|
return
|
78
80
|
end while true
|
@@ -172,12 +174,9 @@ module Rainbows
|
|
172
174
|
|
173
175
|
def notify_readable
|
174
176
|
return if CUR.size >= MAX
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
CUR[sig] = Client.new(sig, io)
|
179
|
-
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
180
|
-
end
|
177
|
+
io = Rainbows.accept(@io) or return
|
178
|
+
sig = EM.attach_fd(io.fileno, false)
|
179
|
+
CUR[sig] = Client.new(sig, io)
|
181
180
|
end
|
182
181
|
end
|
183
182
|
|
@@ -191,8 +190,7 @@ module Rainbows
|
|
191
190
|
EM.epoll
|
192
191
|
EM.kqueue
|
193
192
|
logger.info "EventMachine: epoll=#{EM.epoll?} kqueue=#{EM.kqueue?}"
|
194
|
-
Server.const_set(:MAX,
|
195
|
-
HttpServer::LISTENERS.size)
|
193
|
+
Server.const_set(:MAX, worker_connections + LISTENERS.size)
|
196
194
|
EvCore.setup(Client)
|
197
195
|
EM.run {
|
198
196
|
conns = EM.instance_variable_get(:@conns) or
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
begin
|
3
|
+
require 'fiber'
|
4
|
+
rescue LoadError
|
5
|
+
defined?(NeverBlock) or raise
|
6
|
+
end
|
7
|
+
|
8
|
+
module Rainbows
|
9
|
+
|
10
|
+
# core module for all things that use Fibers in Rainbows!
|
11
|
+
module Fiber
|
12
|
+
autoload :Base, 'rainbows/fiber/base'
|
13
|
+
autoload :Queue, 'rainbows/fiber/queue'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'rainbows/fiber/io'
|
3
|
+
|
4
|
+
module Rainbows
|
5
|
+
module Fiber
|
6
|
+
|
7
|
+
# blocked readers (key: Rainbows::Fiber::IO object, value is irrelevant)
|
8
|
+
RD = {}
|
9
|
+
|
10
|
+
# blocked writers (key: Rainbows::Fiber::IO object, value is irrelevant)
|
11
|
+
WR = {}
|
12
|
+
|
13
|
+
# sleeping fibers go here (key: Fiber object, value: wakeup time)
|
14
|
+
ZZ = {}
|
15
|
+
|
16
|
+
# puts the current Fiber into uninterruptible sleep for at least
|
17
|
+
# +seconds+. Unlike Kernel#sleep, this it is not possible to sleep
|
18
|
+
# indefinitely to be woken up (nobody wants that in a web server,
|
19
|
+
# right?).
|
20
|
+
def self.sleep(seconds)
|
21
|
+
ZZ[::Fiber.current] = Time.now + seconds
|
22
|
+
::Fiber.yield
|
23
|
+
end
|
24
|
+
|
25
|
+
# base module used by FiberSpawn and FiberPool
|
26
|
+
module Base
|
27
|
+
include Rainbows::Base
|
28
|
+
|
29
|
+
# the scheduler method that powers both FiberSpawn and FiberPool
|
30
|
+
# concurrency models. It times out idle clients and attempts to
|
31
|
+
# schedules ones that were blocked on I/O. At most it'll sleep
|
32
|
+
# for one second (returned by the schedule_sleepers method) which
|
33
|
+
# will cause it.
|
34
|
+
def schedule(&block)
|
35
|
+
ret = begin
|
36
|
+
G.tick
|
37
|
+
RD.keys.each { |c| c.f.resume } # attempt to time out idle clients
|
38
|
+
t = schedule_sleepers
|
39
|
+
Kernel.select(RD.keys.concat(LISTENERS), WR.keys, nil, t) or return
|
40
|
+
rescue Errno::EINTR
|
41
|
+
retry
|
42
|
+
rescue Errno::EBADF, TypeError
|
43
|
+
LISTENERS.compact!
|
44
|
+
raise
|
45
|
+
end or return
|
46
|
+
|
47
|
+
# active writers first, then _all_ readers for keepalive timeout
|
48
|
+
ret[1].concat(RD.keys).each { |c| c.f.resume }
|
49
|
+
|
50
|
+
# accept is an expensive syscall, filter out listeners we don't want
|
51
|
+
(ret.first & LISTENERS).each(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# wakes up any sleepers that need to be woken and
|
55
|
+
# returns an interval to IO.select on
|
56
|
+
def schedule_sleepers
|
57
|
+
max = nil
|
58
|
+
now = Time.now
|
59
|
+
ZZ.delete_if { |fib, time|
|
60
|
+
if now >= time
|
61
|
+
fib.resume
|
62
|
+
now = Time.now
|
63
|
+
else
|
64
|
+
max = time
|
65
|
+
false
|
66
|
+
end
|
67
|
+
}
|
68
|
+
max.nil? || max > (now + 1) ? 1 : max - now
|
69
|
+
end
|
70
|
+
|
71
|
+
def process_client(client)
|
72
|
+
G.cur += 1
|
73
|
+
io = client.to_io
|
74
|
+
buf = client.read_timeout or return
|
75
|
+
hp = HttpParser.new
|
76
|
+
env = {}
|
77
|
+
alive = true
|
78
|
+
remote_addr = TCPSocket === io ? io.peeraddr.last : LOCALHOST
|
79
|
+
|
80
|
+
begin # loop
|
81
|
+
while ! hp.headers(env, buf)
|
82
|
+
buf << (client.read_timeout or return)
|
83
|
+
end
|
84
|
+
|
85
|
+
env[RACK_INPUT] = 0 == hp.content_length ?
|
86
|
+
HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
|
87
|
+
env[REMOTE_ADDR] = remote_addr
|
88
|
+
response = APP.call(env.update(RACK_DEFAULTS))
|
89
|
+
|
90
|
+
if 100 == response.first.to_i
|
91
|
+
client.write(EXPECT_100_RESPONSE)
|
92
|
+
env.delete(HTTP_EXPECT)
|
93
|
+
response = APP.call(env)
|
94
|
+
end
|
95
|
+
|
96
|
+
alive = hp.keepalive? && G.alive
|
97
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
98
|
+
HttpResponse.write(client, response, out)
|
99
|
+
end while alive and hp.reset.nil? and env.clear
|
100
|
+
rescue => e
|
101
|
+
handle_error(io, e)
|
102
|
+
ensure
|
103
|
+
G.cur -= 1
|
104
|
+
RD.delete(client)
|
105
|
+
WR.delete(client)
|
106
|
+
ZZ.delete(client.f)
|
107
|
+
io.close unless io.closed?
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|