rainbows 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +12 -6
- data/README +3 -2
- data/Rakefile +3 -3
- data/TODO +2 -9
- data/lib/rainbows.rb +1 -0
- data/lib/rainbows/app_pool.rb +10 -6
- data/lib/rainbows/base.rb +1 -1
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/ev_core.rb +88 -0
- data/lib/rainbows/event_machine.rb +218 -0
- data/lib/rainbows/http_server.rb +4 -1
- data/lib/rainbows/rev.rb +21 -89
- data/lib/rainbows/revactor.rb +4 -10
- data/local.mk.sample +13 -7
- data/rainbows.gemspec +17 -2
- data/t/.gitignore +1 -0
- data/t/GNUmakefile +63 -40
- data/t/README +6 -2
- data/t/async_sinatra.ru +13 -0
- data/t/bin/unused_listen +1 -1
- data/t/large-file-response.ru +1 -0
- data/t/my-tap-lib.sh +200 -0
- data/t/simple-http_Base.ru +3 -0
- data/t/simple-http_EventMachine.ru +9 -0
- data/t/simple-http_Rev.ru +9 -0
- data/t/simple-http_Revactor.ru +10 -0
- data/t/simple-http_ThreadPool.ru +10 -0
- data/t/simple-http_ThreadSpawn.ru +10 -0
- data/t/t0000-simple-http.sh +142 -0
- data/t/t0001-unix-http.sh +103 -0
- data/t/t0002-graceful.sh +32 -0
- data/t/t0002-parser-error.sh +31 -0
- data/t/t0003-reopen-logs.sh +97 -0
- data/t/t0005-large-file-response.sh +83 -0
- data/t/t0100-rack-input-hammer.sh +45 -0
- data/t/t0101-rack-input-trailer.sh +68 -0
- data/t/t0200-async-response.sh +66 -0
- data/t/t0201-async-response-no-autochunk.sh +3 -0
- data/t/t0300-async_sinatra.sh +65 -0
- data/t/t9000-rack-app-pool.sh +45 -33
- data/t/test-lib.sh +67 -56
- metadata +26 -56
- data/t/lib-async-response-no-autochunk.sh +0 -6
- data/t/lib-async-response.sh +0 -45
- data/t/lib-graceful.sh +0 -40
- data/t/lib-input-trailer.sh +0 -63
- data/t/lib-large-file-response.sh +0 -45
- data/t/lib-parser-error.sh +0 -29
- data/t/lib-rack-input-hammer.sh +0 -38
- data/t/lib-reopen-logs.sh +0 -60
- data/t/lib-simple-http.sh +0 -92
- data/t/t0000-basic.sh +0 -2
- data/t/t1000-thread-pool-basic.sh +0 -2
- data/t/t1002-thread-pool-graceful.sh +0 -2
- data/t/t1003-thread-pool-reopen-logs.sh +0 -2
- data/t/t1004-thread-pool-async-response.sh +0 -45
- data/t/t1005-thread-pool-large-file-response.sh +0 -45
- data/t/t1006-thread-pool-async-response-no-autochunk.sh +0 -6
- data/t/t1100-thread-pool-rack-input.sh +0 -2
- data/t/t1101-thread-pool-input-trailer.sh +0 -2
- data/t/t2000-thread-spawn-basic.sh +0 -2
- data/t/t2002-thread-spawn-graceful.sh +0 -2
- data/t/t2003-thread-spawn-reopen-logs.sh +0 -2
- data/t/t2004-thread-spawn-async-response.sh +0 -45
- data/t/t2005-thread-spawn-large-file-response.sh +0 -45
- data/t/t2006-thread-spawn-async-response-no-autochunk.sh +0 -6
- data/t/t2100-thread-spawn-rack-input.sh +0 -2
- data/t/t2101-thread-spawn-input-trailer.sh +0 -2
- data/t/t3000-revactor-basic.sh +0 -2
- data/t/t3002-revactor-graceful.sh +0 -2
- data/t/t3003-revactor-reopen-logs.sh +0 -2
- data/t/t3004-revactor-async-response.sh +0 -45
- data/t/t3005-revactor-large-file-response.sh +0 -2
- data/t/t3006-revactor-async-response-no-autochunk.sh +0 -6
- data/t/t3100-revactor-rack-input.sh +0 -2
- data/t/t3101-revactor-rack-input-trailer.sh +0 -2
- data/t/t4000-rev-basic.sh +0 -2
- data/t/t4002-rev-graceful.sh +0 -2
- data/t/t4003-rev-parser-error.sh +0 -2
- data/t/t4003-rev-reopen-logs.sh +0 -2
- data/t/t4004-rev-async-response.sh +0 -45
- data/t/t4005-rev-large-file-response.sh +0 -2
- data/t/t4006-rev-async-response-no-autochunk.sh +0 -6
- data/t/t4100-rev-rack-input.sh +0 -2
- data/t/t4101-rev-rack-input-trailer.sh +0 -2
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# use GNU Make to run tests in parallel, and without depending on Rubygems
|
2
2
|
all::
|
3
|
-
|
3
|
+
RUBY = ruby
|
4
4
|
rake = rake
|
5
5
|
GIT_URL = git://git.bogomips.org/rainbows.git
|
6
6
|
|
@@ -8,11 +8,17 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
|
|
8
8
|
@./GIT-VERSION-GEN
|
9
9
|
-include GIT-VERSION-FILE
|
10
10
|
-include local.mk
|
11
|
+
ifdef ruby
|
12
|
+
ifeq ($(RUBY),ruby)
|
13
|
+
$(warning ruby variable is deprecated, use RUBY instead)
|
14
|
+
RUBY = $(ruby)
|
15
|
+
endif
|
16
|
+
endif
|
11
17
|
ifeq ($(DLEXT),) # "so" for Linux
|
12
|
-
DLEXT := $(shell $(
|
18
|
+
DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts Config::CONFIG["DLEXT"]')
|
13
19
|
endif
|
14
20
|
ifeq ($(RUBY_VERSION),)
|
15
|
-
RUBY_VERSION := $(shell $(
|
21
|
+
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
16
22
|
endif
|
17
23
|
|
18
24
|
base_bins := rainbows
|
@@ -25,7 +31,7 @@ install: $(bins)
|
|
25
31
|
$(RM) -r .install-tmp
|
26
32
|
mkdir .install-tmp
|
27
33
|
cp -p bin/* .install-tmp
|
28
|
-
$(
|
34
|
+
$(RUBY) setup.rb all
|
29
35
|
$(RM) $^
|
30
36
|
mv .install-tmp/* bin/
|
31
37
|
$(RM) -r .install-tmp
|
@@ -82,10 +88,10 @@ doc: .document NEWS ChangeLog
|
|
82
88
|
cd doc && for i in $(base_bins); do \
|
83
89
|
sed -e '/"documentation">/r man1/'$$i'.1.html' \
|
84
90
|
< $${i}_1.html > tmp && mv tmp $${i}_1.html; done
|
85
|
-
$(
|
91
|
+
$(RUBY) -i -p -e \
|
86
92
|
'$$_.gsub!("</title>",%q{\&$(call atom,$(cgit_atom))})' \
|
87
93
|
doc/ChangeLog.html
|
88
|
-
$(
|
94
|
+
$(RUBY) -i -p -e \
|
89
95
|
'$$_.gsub!("</title>",%q{\&$(call atom,$(news_atom))})' \
|
90
96
|
doc/NEWS.html doc/README.html
|
91
97
|
$(rake) -s news_atom > doc/NEWS.atom.xml
|
data/README
CHANGED
@@ -13,10 +13,11 @@ suck; differently.
|
|
13
13
|
|
14
14
|
For network concurrency, models we currently support are:
|
15
15
|
|
16
|
-
* {:ThreadSpawn}[link:Rainbows/ThreadSpawn.html]
|
17
|
-
* {:ThreadPool}[link:Rainbows/ThreadPool.html]
|
18
16
|
* {:Revactor}[link:Rainbows/Revactor.html]
|
17
|
+
* {:ThreadPool}[link:Rainbows/ThreadPool.html]
|
19
18
|
* {:Rev}[link:Rainbows/Rev.html]*
|
19
|
+
* {:ThreadSpawn}[link:Rainbows/ThreadSpawn.html]
|
20
|
+
* {:EventMachine}[link:Rainbows/EventMachine.html]
|
20
21
|
|
21
22
|
We have {more on the way}[link:TODO.html] for handling network concurrency.
|
22
23
|
Additionally, we also use multiple processes (managed by Unicorn) for
|
data/Rakefile
CHANGED
@@ -12,8 +12,8 @@ def tags
|
|
12
12
|
body ||= "initial"
|
13
13
|
{
|
14
14
|
:time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
|
15
|
-
:tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1],
|
16
|
-
:tagger_email => %r{<([^>]+)>}.match(tagger)[1],
|
15
|
+
:tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
|
16
|
+
:tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
|
17
17
|
:id => `git rev-parse refs/tags/#{tag}`.chomp!,
|
18
18
|
:tag => tag,
|
19
19
|
:subject => subject,
|
@@ -49,7 +49,7 @@ task :news_atom do
|
|
49
49
|
url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
|
50
50
|
link! :rel => "alternate", :type => "text/html", :href =>url
|
51
51
|
id! url
|
52
|
-
content(:type => 'text'
|
52
|
+
content({:type => 'text'}, tag[:body])
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
data/TODO
CHANGED
@@ -7,18 +7,9 @@ care about.
|
|
7
7
|
unit tests, only integration tests that exercise externally
|
8
8
|
visible parts.
|
9
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
|
-
|
13
10
|
* Rev + Thread - current Rev model with threading, which will give
|
14
11
|
us a streaming (but rewindable) "rack.input".
|
15
12
|
|
16
|
-
* EventMachine - much like Rev, but we haven't looked at this one much
|
17
|
-
(our benevolent dictator doesn't like C++). If we can figure out how
|
18
|
-
to do Rev without Revactor, then this should be pretty easy.
|
19
|
-
|
20
|
-
* EventMachine + catch/throw :async API used in Thin
|
21
|
-
|
22
13
|
* EventMachine.spawn - should be like Revactor, maybe?
|
23
14
|
|
24
15
|
* Rev + callcc - current Rev model with callcc (should work with MBARI)
|
@@ -28,6 +19,8 @@ care about.
|
|
28
19
|
|
29
20
|
* Omnibus - haven't looked into it, probably like Revactor with 1.8?
|
30
21
|
|
22
|
+
* Packet - pure Ruby, EventMachine-like library
|
23
|
+
|
31
24
|
* Rubinius Actors - should be like Revactor and easily doable once
|
32
25
|
Rubinius gets more mature.
|
33
26
|
|
data/lib/rainbows.rb
CHANGED
data/lib/rainbows/app_pool.rb
CHANGED
@@ -39,14 +39,18 @@ 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 Rev
|
43
|
-
# single-threaded/single-instance as far as application
|
44
|
-
# In other words, +P+ is always +one+ when using
|
45
|
-
# only works with the
|
46
|
-
#
|
42
|
+
# AppPool has no effect on the Rev or EventMachine concurrency models
|
43
|
+
# as those are single-threaded/single-instance as far as application
|
44
|
+
# concurrency goes. In other words, +P+ is always +one+ when using
|
45
|
+
# Rev or EventMachine. AppPool currently only works with the
|
46
|
+
# ThreadSpawn and ThreadPool models. It does not yet work reliably
|
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.
|
47
50
|
#
|
48
51
|
# Since this is Rack middleware, you may load this in your Rack
|
49
|
-
# config.ru file and even use it in servers other than
|
52
|
+
# config.ru file and even use it in threaded servers other than
|
53
|
+
# \Rainbows!
|
50
54
|
#
|
51
55
|
# use Rainbows::AppPool, :size => 30
|
52
56
|
# map "/lobster" do
|
data/lib/rainbows/base.rb
CHANGED
@@ -93,7 +93,7 @@ module Rainbows
|
|
93
93
|
Rainbows::G.alive = false
|
94
94
|
expire = Time.now + (timeout * 2.0)
|
95
95
|
m = 0
|
96
|
-
|
96
|
+
until (threads.delete_if { |thr| ! thr.alive? }).empty?
|
97
97
|
threads.each { |thr|
|
98
98
|
worker.tmp.chmod(m = 0 == m ? 1 : 0)
|
99
99
|
thr.join(1)
|
data/lib/rainbows/const.rb
CHANGED
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
# base module for evented models like Rev and EventMachine
|
6
|
+
module EvCore
|
7
|
+
include Unicorn
|
8
|
+
include Rainbows::Const
|
9
|
+
G = Rainbows::G
|
10
|
+
|
11
|
+
def post_init
|
12
|
+
@remote_addr = ::TCPSocket === @_io ? @_io.peeraddr.last : LOCALHOST
|
13
|
+
@env = {}
|
14
|
+
@hp = HttpParser.new
|
15
|
+
@state = :headers # [ :body [ :trailers ] ] :app_call :close
|
16
|
+
@buf = ""
|
17
|
+
@deferred_bodies = [] # for (fast) regular files only
|
18
|
+
end
|
19
|
+
|
20
|
+
# graceful exit, like SIGQUIT
|
21
|
+
def quit
|
22
|
+
@state = :close
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_error(e)
|
26
|
+
msg = case e
|
27
|
+
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
28
|
+
ERROR_500_RESPONSE
|
29
|
+
when HttpParserError # try to tell the client they're bad
|
30
|
+
ERROR_400_RESPONSE
|
31
|
+
else
|
32
|
+
G.logger.error "Read error: #{e.inspect}"
|
33
|
+
G.logger.error e.backtrace.join("\n")
|
34
|
+
ERROR_500_RESPONSE
|
35
|
+
end
|
36
|
+
write(msg)
|
37
|
+
quit
|
38
|
+
end
|
39
|
+
|
40
|
+
def tmpio
|
41
|
+
io = Util.tmpio
|
42
|
+
def io.size
|
43
|
+
# already sync=true at creation, so no need to flush before stat
|
44
|
+
stat.size
|
45
|
+
end
|
46
|
+
io
|
47
|
+
end
|
48
|
+
|
49
|
+
# TeeInput doesn't map too well to this right now...
|
50
|
+
def on_read(data)
|
51
|
+
case @state
|
52
|
+
when :headers
|
53
|
+
@hp.headers(@env, @buf << data) or return
|
54
|
+
@state = :body
|
55
|
+
len = @hp.content_length
|
56
|
+
if len == 0
|
57
|
+
@input = HttpRequest::NULL_IO
|
58
|
+
app_call # common case
|
59
|
+
else # nil or len > 0
|
60
|
+
# since we don't do streaming input, we have no choice but
|
61
|
+
# to take over 100-continue handling from the Rack application
|
62
|
+
if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
|
63
|
+
write(EXPECT_100_RESPONSE)
|
64
|
+
@env.delete(HTTP_EXPECT)
|
65
|
+
end
|
66
|
+
@input = len && len <= MAX_BODY ? StringIO.new("") : tmpio
|
67
|
+
@hp.filter_body(@buf2 = @buf.dup, @buf)
|
68
|
+
@input << @buf2
|
69
|
+
on_read("")
|
70
|
+
end
|
71
|
+
when :body
|
72
|
+
if @hp.body_eof?
|
73
|
+
@state = :trailers
|
74
|
+
on_read(data)
|
75
|
+
elsif data.size > 0
|
76
|
+
@hp.filter_body(@buf2, @buf << data)
|
77
|
+
@input << @buf2
|
78
|
+
on_read("")
|
79
|
+
end
|
80
|
+
when :trailers
|
81
|
+
@hp.trailers(@env, @buf << data) and app_call
|
82
|
+
end
|
83
|
+
rescue Object => e
|
84
|
+
handle_error(e)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'eventmachine'
|
3
|
+
EM::VERSION >= '0.12.10' or abort 'eventmachine 0.12.10 is required'
|
4
|
+
require 'rainbows/ev_core'
|
5
|
+
|
6
|
+
module Rainbows
|
7
|
+
|
8
|
+
# Implements a basic single-threaded event model with
|
9
|
+
# {EventMachine}[http://rubyeventmachine.com/]. It is capable of
|
10
|
+
# handling thousands of simultaneous client connections, but with only
|
11
|
+
# a single-threaded app dispatch. It is suited for slow clients and
|
12
|
+
# fast applications (applications that do not have slow network
|
13
|
+
# dependencies) or applications that use DevFdResponse for deferrable
|
14
|
+
# response bodies. It does not require your Rack application to be
|
15
|
+
# thread-safe, reentrancy is only required for the DevFdResponse body
|
16
|
+
# generator.
|
17
|
+
#
|
18
|
+
# Compatibility: Whatever \EventMachine ~> 0.12.10 and Unicorn both
|
19
|
+
# support, currently Ruby 1.8/1.9.
|
20
|
+
#
|
21
|
+
# This model is compatible with users of "async.callback" in the Rack
|
22
|
+
# environment such as
|
23
|
+
# {async_sinatra}[http://github.com/raggi/async_sinatra].
|
24
|
+
#
|
25
|
+
# This model does not implement as streaming "rack.input" which allows
|
26
|
+
# the Rack application to process data as it arrives. This means
|
27
|
+
# "rack.input" will be fully buffered in memory or to a temporary file
|
28
|
+
# before the application is entered.
|
29
|
+
|
30
|
+
module EventMachine
|
31
|
+
|
32
|
+
include Base
|
33
|
+
|
34
|
+
class Client < EM::Connection
|
35
|
+
include Rainbows::EvCore
|
36
|
+
G = Rainbows::G
|
37
|
+
|
38
|
+
# Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
|
39
|
+
ASYNC_CALLBACK = 'async.callback'.freeze
|
40
|
+
|
41
|
+
def initialize(io)
|
42
|
+
@_io = io
|
43
|
+
end
|
44
|
+
|
45
|
+
alias write send_data
|
46
|
+
alias receive_data on_read
|
47
|
+
|
48
|
+
def quit
|
49
|
+
super
|
50
|
+
close_connection_after_writing
|
51
|
+
end
|
52
|
+
|
53
|
+
def app_call
|
54
|
+
begin
|
55
|
+
(@env[RACK_INPUT] = @input).rewind
|
56
|
+
alive = @hp.keepalive?
|
57
|
+
@env[REMOTE_ADDR] = @remote_addr
|
58
|
+
@env[ASYNC_CALLBACK] = method(:response_write)
|
59
|
+
|
60
|
+
response = catch(:async) { G.app.call(@env.update(RACK_DEFAULTS)) }
|
61
|
+
|
62
|
+
# too tricky to support pipelining with :async since the
|
63
|
+
# second (pipelined) request could be a stuck behind a
|
64
|
+
# long-running async response
|
65
|
+
(response.nil? || -1 == response.first) and return @state = :close
|
66
|
+
|
67
|
+
alive &&= G.alive
|
68
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
|
69
|
+
response_write(response, out, alive)
|
70
|
+
|
71
|
+
if alive
|
72
|
+
@env.clear
|
73
|
+
@hp.reset
|
74
|
+
@state = :headers
|
75
|
+
# keepalive requests are always body-less, so @input is unchanged
|
76
|
+
@hp.headers(@env, @buf) and next
|
77
|
+
end
|
78
|
+
return
|
79
|
+
end while true
|
80
|
+
end
|
81
|
+
|
82
|
+
def response_write(response, out = [], alive = false)
|
83
|
+
body = response.last
|
84
|
+
unless body.respond_to?(:to_path)
|
85
|
+
HttpResponse.write(self, response, out)
|
86
|
+
quit unless alive
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
headers = Rack::Utils::HeaderHash.new(response[1])
|
91
|
+
path = body.to_path
|
92
|
+
io = body.to_io if body.respond_to?(:to_io)
|
93
|
+
io ||= IO.new($1.to_i) if path =~ %r{\A/dev/fd/(\d+)\z}
|
94
|
+
io ||= File.open(path, 'rb') # could be a named pipe
|
95
|
+
|
96
|
+
st = io.stat
|
97
|
+
if st.file?
|
98
|
+
headers.delete('Transfer-Encoding')
|
99
|
+
headers['Content-Length'] ||= st.size.to_s
|
100
|
+
response = [ response.first, headers.to_hash, [] ]
|
101
|
+
HttpResponse.write(self, response, out)
|
102
|
+
stream = stream_file_data(path)
|
103
|
+
stream.callback { quit } unless alive
|
104
|
+
elsif st.socket? || st.pipe?
|
105
|
+
do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
|
106
|
+
do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
|
107
|
+
if out.nil?
|
108
|
+
do_chunk = false
|
109
|
+
else
|
110
|
+
out[0] = CONN_CLOSE
|
111
|
+
end
|
112
|
+
response = [ response.first, headers.to_hash, [] ]
|
113
|
+
HttpResponse.write(self, response, out)
|
114
|
+
if do_chunk
|
115
|
+
EM.watch(io, ResponseChunkPipe, self).notify_readable = true
|
116
|
+
else
|
117
|
+
EM.enable_proxy(EM.attach(io, ResponsePipe, self), self)
|
118
|
+
end
|
119
|
+
else
|
120
|
+
HttpResponse.write(self, response, out)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def unbind
|
125
|
+
@_io.close
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module ResponsePipe
|
130
|
+
def initialize(client)
|
131
|
+
@client = client
|
132
|
+
end
|
133
|
+
|
134
|
+
def unbind
|
135
|
+
@io.close
|
136
|
+
@client.quit
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
module ResponseChunkPipe
|
141
|
+
include ResponsePipe
|
142
|
+
|
143
|
+
def unbind
|
144
|
+
@client.write("0\r\n\r\n")
|
145
|
+
super
|
146
|
+
end
|
147
|
+
|
148
|
+
def notify_readable
|
149
|
+
begin
|
150
|
+
data = begin
|
151
|
+
@io.read_nonblock(16384)
|
152
|
+
rescue Errno::EINTR
|
153
|
+
retry
|
154
|
+
rescue Errno::EAGAIN
|
155
|
+
return
|
156
|
+
rescue EOFError
|
157
|
+
detach
|
158
|
+
return
|
159
|
+
end
|
160
|
+
@client.send_data(sprintf("%x\r\n", data.size))
|
161
|
+
@client.send_data(data)
|
162
|
+
@client.send_data("\r\n")
|
163
|
+
end while true
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
module Server
|
168
|
+
|
169
|
+
def initialize(conns)
|
170
|
+
@limit = Rainbows::G.max + HttpServer::LISTENERS.size
|
171
|
+
@em_conns = conns
|
172
|
+
end
|
173
|
+
|
174
|
+
def close
|
175
|
+
detach
|
176
|
+
@io.close
|
177
|
+
end
|
178
|
+
|
179
|
+
def notify_readable
|
180
|
+
return if @em_conns.size >= @limit
|
181
|
+
begin
|
182
|
+
io = @io.accept_nonblock
|
183
|
+
sig = EM.attach_fd(io.fileno, false)
|
184
|
+
@em_conns[sig] = Client.new(sig, io)
|
185
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# runs inside each forked worker, this sits around and waits
|
191
|
+
# for connections and doesn't die until the parent dies (or is
|
192
|
+
# given a INT, QUIT, or TERM signal)
|
193
|
+
def worker_loop(worker)
|
194
|
+
init_worker_process(worker)
|
195
|
+
m = 0
|
196
|
+
|
197
|
+
# enable them both, should be non-fatal if not supported
|
198
|
+
EM.epoll
|
199
|
+
EM.kqueue
|
200
|
+
logger.info "EventMachine: epoll=#{EM.epoll?} kqueue=#{EM.kqueue?}"
|
201
|
+
EM.run {
|
202
|
+
conns = EM.instance_variable_get(:@conns) or
|
203
|
+
raise RuntimeError, "EM @conns instance variable not accessible!"
|
204
|
+
EM.add_periodic_timer(1) do
|
205
|
+
worker.tmp.chmod(m = 0 == m ? 1 : 0)
|
206
|
+
unless G.alive
|
207
|
+
conns.each_value { |client| Client === client and client.quit }
|
208
|
+
EM.stop if conns.empty? && EM.reactor_running?
|
209
|
+
end
|
210
|
+
end
|
211
|
+
LISTENERS.map! do |s|
|
212
|
+
EM.watch(s, Server, conns) { |c| c.notify_readable = true }
|
213
|
+
end
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|