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
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
module Fiber
|
4
|
+
|
5
|
+
# A partially complete IO wrapper, this exports an IO.select()-able
|
6
|
+
# #to_io method and gives users the illusion of a synchronous
|
7
|
+
# interface that yields away from the current Fiber whenever
|
8
|
+
# the underlying IO object cannot read or write
|
9
|
+
class IO < Struct.new(:to_io, :f)
|
10
|
+
|
11
|
+
# for wrapping output response bodies
|
12
|
+
def each(&block)
|
13
|
+
begin
|
14
|
+
yield readpartial(16384)
|
15
|
+
rescue EOFError
|
16
|
+
break
|
17
|
+
end while true
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
to_io.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(buf)
|
26
|
+
begin
|
27
|
+
(w = to_io.write_nonblock(buf)) == buf.size and return
|
28
|
+
buf = buf[w..-1]
|
29
|
+
rescue Errno::EAGAIN
|
30
|
+
WR[self] = false
|
31
|
+
::Fiber.yield
|
32
|
+
WR.delete(self)
|
33
|
+
retry
|
34
|
+
end while true
|
35
|
+
end
|
36
|
+
|
37
|
+
# used for reading headers (respecting keepalive_timeout)
|
38
|
+
def read_timeout
|
39
|
+
expire = nil
|
40
|
+
begin
|
41
|
+
to_io.read_nonblock(16384)
|
42
|
+
rescue Errno::EAGAIN
|
43
|
+
return if expire && expire < Time.now
|
44
|
+
RD[self] = false
|
45
|
+
expire ||= Time.now + G.kato
|
46
|
+
::Fiber.yield
|
47
|
+
RD.delete(self)
|
48
|
+
retry
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def readpartial(length, buf = "")
|
53
|
+
begin
|
54
|
+
to_io.read_nonblock(length, buf)
|
55
|
+
rescue Errno::EAGAIN
|
56
|
+
RD[self] = false
|
57
|
+
::Fiber.yield
|
58
|
+
RD.delete(self)
|
59
|
+
retry
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
module Fiber
|
4
|
+
|
5
|
+
# a self-sufficient Queue implementation for Fiber-based concurrency
|
6
|
+
# models. This requires no external scheduler, so it may be used with
|
7
|
+
# Revactor as well as FiberSpawn and FiberPool.
|
8
|
+
class Queue < Struct.new(:queue, :waiters)
|
9
|
+
|
10
|
+
def initialize(queue = [], waiters = [])
|
11
|
+
# move elements of the Queue into an Array
|
12
|
+
if queue.class.name == "Queue"
|
13
|
+
queue = queue.length.times.map { queue.pop }
|
14
|
+
end
|
15
|
+
super queue, waiters
|
16
|
+
end
|
17
|
+
|
18
|
+
def shift
|
19
|
+
# ah the joys of not having to deal with race conditions
|
20
|
+
if queue.empty?
|
21
|
+
waiters << ::Fiber.current
|
22
|
+
::Fiber.yield
|
23
|
+
end
|
24
|
+
queue.shift
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(obj)
|
28
|
+
queue << obj
|
29
|
+
blocked = waiters.shift and blocked.resume
|
30
|
+
queue # not quite 100% compatible but no-one's looking :>
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'rainbows/fiber'
|
3
|
+
|
4
|
+
module Rainbows
|
5
|
+
|
6
|
+
# A Fiber-based concurrency model for Ruby 1.9. This uses a pool of
|
7
|
+
# Fibers to handle client IO to run the application and the root Fiber
|
8
|
+
# for scheduling and connection acceptance. The pool size is equal to
|
9
|
+
# the number of +worker_connections+. Compared to the ThreadPool
|
10
|
+
# model, Fibers are very cheap in terms of memory usage so you can
|
11
|
+
# have more active connections. This model supports a streaming
|
12
|
+
# "rack.input" with lightweight concurrency. Applications are
|
13
|
+
# strongly advised to wrap all slow IO objects (sockets, pipes) using
|
14
|
+
# the Rainbows::Fiber::IO class whenever possible.
|
15
|
+
|
16
|
+
module FiberPool
|
17
|
+
include Fiber::Base
|
18
|
+
|
19
|
+
def worker_loop(worker)
|
20
|
+
init_worker_process(worker)
|
21
|
+
pool = []
|
22
|
+
worker_connections.times {
|
23
|
+
::Fiber.new {
|
24
|
+
process_client(::Fiber.yield) while pool << ::Fiber.current
|
25
|
+
}.resume # resume to hit ::Fiber.yield so it waits on a client
|
26
|
+
}
|
27
|
+
Fiber::Base.const_set(:APP, app)
|
28
|
+
|
29
|
+
begin
|
30
|
+
schedule do |l|
|
31
|
+
fib = pool.shift or break # let another worker process take it
|
32
|
+
if io = Rainbows.accept(l)
|
33
|
+
fib.resume(Fiber::IO.new(io, fib))
|
34
|
+
else
|
35
|
+
pool << fib
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
Error.listen_loop(e)
|
40
|
+
end while G.alive || G.cur > 0
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'rainbows/fiber'
|
3
|
+
|
4
|
+
module Rainbows
|
5
|
+
|
6
|
+
# Simple Fiber-based concurrency model for 1.9. This spawns a new
|
7
|
+
# Fiber for every incoming client connection and the root Fiber for
|
8
|
+
# scheduling and connection acceptance. This exports a streaming
|
9
|
+
# "rack.input" with lightweight concurrency. Applications are
|
10
|
+
# strongly advised to wrap all slow IO objects (sockets, pipes) using
|
11
|
+
# the Rainbows::Fiber::IO class whenever possible.
|
12
|
+
|
13
|
+
module FiberSpawn
|
14
|
+
include Fiber::Base
|
15
|
+
|
16
|
+
def worker_loop(worker)
|
17
|
+
init_worker_process(worker)
|
18
|
+
Fiber::Base.const_set(:APP, app)
|
19
|
+
limit = worker_connections
|
20
|
+
fio = Rainbows::Fiber::IO
|
21
|
+
|
22
|
+
begin
|
23
|
+
schedule do |l|
|
24
|
+
break if G.cur >= limit
|
25
|
+
io = Rainbows.accept(l) or next
|
26
|
+
::Fiber.new { process_client(fio.new(io, ::Fiber.current)) }.resume
|
27
|
+
end
|
28
|
+
rescue => e
|
29
|
+
Error.listen_loop(e)
|
30
|
+
end while G.alive || G.cur > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/lib/rainbows/http_server.rb
CHANGED
@@ -52,10 +52,18 @@ module Rainbows
|
|
52
52
|
Module === mod or
|
53
53
|
raise ArgumentError, "concurrency model #{model.inspect} not supported"
|
54
54
|
extend(mod)
|
55
|
+
args.each do |opt|
|
56
|
+
case opt
|
57
|
+
when Hash; O.update(opt)
|
58
|
+
when Symbol; O[opt] = true
|
59
|
+
else; raise ArgumentError, "can't handle option: #{opt.inspect}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
mod.setup if mod.respond_to?(:setup)
|
55
63
|
Const::RACK_DEFAULTS['rainbows.model'] = @use = model.to_sym
|
56
64
|
Const::RACK_DEFAULTS['rack.multithread'] = !!(/Thread/ =~ model.to_s)
|
57
65
|
case @use
|
58
|
-
when :Rev, :EventMachine
|
66
|
+
when :Rev, :EventMachine, :NeverBlock
|
59
67
|
Const::RACK_DEFAULTS['rainbows.autochunk'] = true
|
60
68
|
end
|
61
69
|
end
|
@@ -68,6 +76,11 @@ module Rainbows
|
|
68
76
|
@worker_connections = nr
|
69
77
|
end
|
70
78
|
|
79
|
+
def keepalive_timeout(nr)
|
80
|
+
(Integer === nr && nr >= 0) or
|
81
|
+
raise ArgumentError, "keepalive must be a non-negative Integer"
|
82
|
+
G.kato = nr
|
83
|
+
end
|
71
84
|
end
|
72
85
|
|
73
86
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
# {NeverBlock}[www.espace.com.eg/neverblock/] library that combines
|
6
|
+
# the EventMachine library with Ruby Fibers. This includes use of
|
7
|
+
# Thread-based Fibers under Ruby 1.8. It currently does NOT support
|
8
|
+
# a streaming "rack.input" but is compatible with everything else
|
9
|
+
# EventMachine supports.
|
10
|
+
#
|
11
|
+
# In your Rainbows! config block, you may specify a Fiber pool size
|
12
|
+
# to limit your application concurrency (without using Rainbows::AppPool)
|
13
|
+
#
|
14
|
+
# Rainbows! do
|
15
|
+
# use :NeverBlock, :pool_size => 50
|
16
|
+
# worker_connections 100
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
module NeverBlock
|
20
|
+
|
21
|
+
DEFAULTS = {
|
22
|
+
:pool_size => 20, # same default size used by NB
|
23
|
+
:backend => :EventMachine, # NeverBlock doesn't support Rev yet
|
24
|
+
}
|
25
|
+
|
26
|
+
# same pool size NB core itself uses
|
27
|
+
def self.setup
|
28
|
+
DEFAULTS.each { |k,v| O[k] ||= v }
|
29
|
+
Integer === O[:pool_size] && O[:pool_size] > 0 or
|
30
|
+
raise ArgumentError, "pool_size must a be an Integer > 0"
|
31
|
+
mod = Rainbows.const_get(O[:backend])
|
32
|
+
require "never_block" # require EM first since we need a higher version
|
33
|
+
G.server.extend(mod)
|
34
|
+
G.server.extend(Core)
|
35
|
+
end
|
36
|
+
|
37
|
+
module Client
|
38
|
+
|
39
|
+
def self.setup
|
40
|
+
const_set(:POOL, ::NB::Pool::FiberPool.new(O[:pool_size]))
|
41
|
+
Rainbows.const_get(O[:backend]).const_get(:Client).module_eval do
|
42
|
+
include Rainbows::NeverBlock::Client
|
43
|
+
alias _app_call app_call
|
44
|
+
undef_method :app_call
|
45
|
+
alias app_call nb_app_call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def nb_app_call
|
50
|
+
POOL.spawn do
|
51
|
+
begin
|
52
|
+
_app_call
|
53
|
+
rescue => e
|
54
|
+
handle_error(e)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module Core
|
61
|
+
def init_worker_process(worker)
|
62
|
+
super
|
63
|
+
Client.setup
|
64
|
+
logger.info "NeverBlock/#{O[:backend]} pool_size=#{O[:pool_size]}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/rainbows/rev.rb
CHANGED
data/lib/rainbows/rev/client.rb
CHANGED
@@ -8,7 +8,7 @@ module Rainbows
|
|
8
8
|
G = Rainbows::G
|
9
9
|
|
10
10
|
def initialize(io)
|
11
|
-
|
11
|
+
CONN[self] = false
|
12
12
|
super(io)
|
13
13
|
post_init
|
14
14
|
@deferred_bodies = [] # for (fast) regular files only
|
@@ -23,9 +23,14 @@ module Rainbows
|
|
23
23
|
schedule_write unless out_headers # triggers a write
|
24
24
|
end
|
25
25
|
|
26
|
+
def timeout?
|
27
|
+
@_write_buffer.empty? && @deferred_bodies.empty? and close.nil?
|
28
|
+
end
|
29
|
+
|
26
30
|
def app_call
|
27
31
|
begin
|
28
|
-
(
|
32
|
+
KATO.delete(self)
|
33
|
+
@env[RACK_INPUT] = @input
|
29
34
|
@env[REMOTE_ADDR] = @remote_addr
|
30
35
|
response = APP.call(@env.update(RACK_DEFAULTS))
|
31
36
|
alive = @hp.keepalive? && G.alive
|
@@ -38,6 +43,7 @@ module Rainbows
|
|
38
43
|
@state = :headers
|
39
44
|
# keepalive requests are always body-less, so @input is unchanged
|
40
45
|
@hp.headers(@env, @buf) and next
|
46
|
+
KATO[self] = Time.now
|
41
47
|
else
|
42
48
|
quit
|
43
49
|
end
|
@@ -65,7 +71,7 @@ module Rainbows
|
|
65
71
|
end
|
66
72
|
|
67
73
|
def on_close
|
68
|
-
|
74
|
+
CONN.delete(self)
|
69
75
|
end
|
70
76
|
|
71
77
|
end # module Client
|
data/lib/rainbows/rev/core.rb
CHANGED
@@ -11,11 +11,8 @@ module Rainbows
|
|
11
11
|
# CL and MAX will be defined in the corresponding worker loop
|
12
12
|
|
13
13
|
def on_readable
|
14
|
-
return if
|
15
|
-
|
16
|
-
CL.new(@_io.accept_nonblock).attach(LOOP)
|
17
|
-
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
18
|
-
end
|
14
|
+
return if CONN.size >= MAX
|
15
|
+
io = Rainbows.accept(@_io) and CL.new(io).attach(LOOP)
|
19
16
|
end
|
20
17
|
end # class Server
|
21
18
|
|
@@ -10,7 +10,11 @@ module Rainbows
|
|
10
10
|
class Heartbeat < ::Rev::TimerWatcher
|
11
11
|
|
12
12
|
def on_timer
|
13
|
-
|
13
|
+
if (ot = G.kato) > 0
|
14
|
+
ot = Time.now - ot
|
15
|
+
KATO.delete_if { |client, time| time < ot and client.timeout? }
|
16
|
+
end
|
17
|
+
exit if (! G.tick && CONN.size <= 0)
|
14
18
|
end
|
15
19
|
|
16
20
|
end
|
@@ -1,93 +1,95 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'rainbows/rev'
|
3
|
-
require 'rainbows/ev_thread_core'
|
4
3
|
|
5
|
-
|
4
|
+
RUBY_VERSION =~ %r{\A1\.8} && ::Rev::VERSION < "0.3.2" and
|
5
|
+
warn "Rainbows::RevThreadSpawn + Rev (< 0.3.2)" \
|
6
|
+
" does not work well under Ruby 1.8"
|
6
7
|
|
7
8
|
module Rainbows
|
8
9
|
|
9
|
-
# This concurrency model is EXTREMELY experimental and does
|
10
|
-
# not perform very well.
|
11
|
-
#
|
12
10
|
# A combination of the Rev and ThreadSpawn models. This allows Ruby
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# applications running under this mode should be thread-safe.
|
20
|
-
# DevFdResponse should be used with this class to proxy asynchronous
|
21
|
-
# responses. All network I/O between the client and server are
|
22
|
-
# handled by the main thread (even when streaming "rack.input").
|
23
|
-
#
|
24
|
-
# Caveats:
|
11
|
+
# Thread-based concurrency for application processing. It DOES NOT
|
12
|
+
# expose a streamable "rack.input" for upload processing within the
|
13
|
+
# app. DevFdResponse should be used with this class to proxy
|
14
|
+
# asynchronous responses. All network I/O between the client and
|
15
|
+
# server are handled by the main thread and outside of the core
|
16
|
+
# application dispatch.
|
25
17
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# processing should be alright as the current Rev::IO::INPUT_SIZE
|
31
|
-
# of 16384 bytes matches the read size used by
|
32
|
-
# Rack::Utils::Multipart::parse_multipart.
|
18
|
+
# WARNING: this model does not currently perform well under 1.8. See the
|
19
|
+
# {rev-talk mailing list}[http://rubyforge.org/mailman/listinfo/rev-talk]
|
20
|
+
# for ongoing performance work that will hopefully make it into the
|
21
|
+
# next release of {Rev}[http://rev.rubyforge.org/].
|
33
22
|
|
34
23
|
module RevThreadSpawn
|
35
|
-
class Client < Rainbows::Rev::Client
|
36
|
-
include EvThreadCore
|
37
|
-
LOOP = ::Rev::Loop.default
|
38
|
-
DR = Rainbows::Rev::DeferredResponse
|
39
|
-
TEE_RESUMER = ::Rev::AsyncWatcher.new
|
40
24
|
|
41
|
-
|
42
|
-
@lock.synchronize { disable if enabled? }
|
43
|
-
end
|
25
|
+
class Master < ::Rev::AsyncWatcher
|
44
26
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
27
|
+
def initialize
|
28
|
+
super
|
29
|
+
@queue = Queue.new
|
48
30
|
end
|
49
31
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
super
|
54
|
-
else
|
55
|
-
@lock.synchronize { super }
|
56
|
-
end
|
32
|
+
def <<(output)
|
33
|
+
@queue << output
|
34
|
+
signal
|
57
35
|
end
|
58
36
|
|
59
|
-
def
|
60
|
-
|
37
|
+
def on_signal
|
38
|
+
client, response = @queue.pop
|
39
|
+
client.response_write(response)
|
61
40
|
end
|
41
|
+
end
|
62
42
|
|
63
|
-
|
43
|
+
class Client < Rainbows::Rev::Client
|
44
|
+
DR = Rainbows::Rev::DeferredResponse
|
45
|
+
KATO = Rainbows::Rev::KATO
|
46
|
+
|
47
|
+
def response_write(response)
|
48
|
+
enable
|
49
|
+
alive = @hp.keepalive? && G.alive
|
50
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
|
64
51
|
DR.write(self, response, out)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
52
|
+
return quit unless alive && G.alive
|
53
|
+
|
54
|
+
@env.clear
|
55
|
+
@hp.reset
|
56
|
+
@state = :headers
|
57
|
+
# keepalive requests are always body-less, so @input is unchanged
|
58
|
+
if @hp.headers(@env, @buf)
|
59
|
+
@input = HttpRequest::NULL_IO
|
60
|
+
app_call
|
61
|
+
else
|
62
|
+
KATO[self] = Time.now
|
63
|
+
end
|
70
64
|
end
|
71
65
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
66
|
+
# fails-safe application dispatch, we absolutely cannot
|
67
|
+
# afford to fail or raise an exception (killing the thread)
|
68
|
+
# here because that could cause a deadlock and we'd leak FDs
|
69
|
+
def app_response
|
70
|
+
begin
|
71
|
+
@env[REMOTE_ADDR] = @remote_addr
|
72
|
+
APP.call(@env.update(RACK_DEFAULTS))
|
73
|
+
rescue => e
|
74
|
+
Error.app(e) # we guarantee this does not raise
|
75
|
+
[ 500, {}, [] ]
|
81
76
|
end
|
82
77
|
end
|
83
78
|
|
79
|
+
def app_call
|
80
|
+
KATO.delete(client = self)
|
81
|
+
disable
|
82
|
+
@env[RACK_INPUT] = @input
|
83
|
+
@input = nil # not sure why, @input seems to get closed otherwise...
|
84
|
+
Thread.new { MASTER << [ client, app_response ] }
|
85
|
+
end
|
84
86
|
end
|
85
87
|
|
86
88
|
include Rainbows::Rev::Core
|
87
89
|
|
88
90
|
def init_worker_process(worker)
|
89
91
|
super
|
90
|
-
Client
|
92
|
+
Client.const_set(:MASTER, Master.new.attach(::Rev::Loop.default))
|
91
93
|
end
|
92
94
|
|
93
95
|
end
|