rainbows 0.3.0 → 0.4.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/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
|