rainbows 0.94.0 → 0.95.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.
Files changed (84) hide show
  1. data/.document +1 -0
  2. data/.manifest +18 -0
  3. data/ChangeLog +394 -226
  4. data/GIT-VERSION-FILE +1 -1
  5. data/GIT-VERSION-GEN +1 -1
  6. data/GNUmakefile +6 -4
  7. data/NEWS +18 -0
  8. data/README +13 -5
  9. data/Static_Files +71 -0
  10. data/TODO +12 -0
  11. data/Test_Suite +1 -1
  12. data/bin/rainbows +1 -4
  13. data/lib/rainbows/actor_spawn.rb +1 -1
  14. data/lib/rainbows/app_pool.rb +1 -1
  15. data/lib/rainbows/base.rb +79 -89
  16. data/lib/rainbows/byte_slice.rb +17 -0
  17. data/lib/rainbows/configurator.rb +46 -0
  18. data/lib/rainbows/const.rb +2 -2
  19. data/lib/rainbows/dev_fd_response.rb +52 -44
  20. data/lib/rainbows/error.rb +1 -0
  21. data/lib/rainbows/ev_core.rb +3 -2
  22. data/lib/rainbows/event_machine.rb +26 -24
  23. data/lib/rainbows/fiber/base.rb +30 -40
  24. data/lib/rainbows/fiber/body.rb +34 -0
  25. data/lib/rainbows/fiber/io.rb +28 -8
  26. data/lib/rainbows/fiber/queue.rb +1 -0
  27. data/lib/rainbows/fiber/rev.rb +4 -2
  28. data/lib/rainbows/fiber.rb +1 -0
  29. data/lib/rainbows/fiber_pool.rb +2 -2
  30. data/lib/rainbows/fiber_spawn.rb +2 -2
  31. data/lib/rainbows/http_response.rb +20 -31
  32. data/lib/rainbows/http_server.rb +3 -4
  33. data/lib/rainbows/max_body.rb +1 -0
  34. data/lib/rainbows/never_block/event_machine.rb +2 -0
  35. data/lib/rainbows/never_block.rb +5 -4
  36. data/lib/rainbows/queue_pool.rb +1 -0
  37. data/lib/rainbows/response/body.rb +119 -0
  38. data/lib/rainbows/response.rb +43 -0
  39. data/lib/rainbows/rev/client.rb +79 -9
  40. data/lib/rainbows/rev/core.rb +4 -0
  41. data/lib/rainbows/rev/deferred_response.rb +1 -44
  42. data/lib/rainbows/rev/heartbeat.rb +1 -0
  43. data/lib/rainbows/rev/master.rb +1 -0
  44. data/lib/rainbows/rev/sendfile.rb +26 -0
  45. data/lib/rainbows/rev/thread.rb +2 -1
  46. data/lib/rainbows/rev.rb +2 -0
  47. data/lib/rainbows/rev_fiber_spawn.rb +3 -1
  48. data/lib/rainbows/rev_thread_pool.rb +7 -5
  49. data/lib/rainbows/rev_thread_spawn.rb +2 -2
  50. data/lib/rainbows/revactor.rb +146 -146
  51. data/lib/rainbows/sendfile.rb +10 -21
  52. data/lib/rainbows/server_token.rb +39 -0
  53. data/lib/rainbows/stream_file.rb +14 -0
  54. data/lib/rainbows/tee_input.rb +1 -0
  55. data/lib/rainbows/thread_pool.rb +12 -7
  56. data/lib/rainbows/thread_spawn.rb +2 -3
  57. data/lib/rainbows/writer_thread_pool.rb +13 -7
  58. data/lib/rainbows/writer_thread_spawn.rb +12 -9
  59. data/lib/rainbows.rb +16 -45
  60. data/rainbows.gemspec +8 -8
  61. data/t/.gitignore +1 -1
  62. data/t/GNUmakefile +26 -16
  63. data/t/README +1 -1
  64. data/t/async-response-no-autochunk.ru +0 -1
  65. data/t/async-response.ru +0 -1
  66. data/t/cramp/rainsocket.ru +26 -0
  67. data/t/fork-sleep.ru +0 -1
  68. data/t/my-tap-lib.sh +3 -2
  69. data/t/simple-http_ActorSpawn.ru +9 -0
  70. data/t/t0009-broken-app.sh +1 -1
  71. data/t/t0009.ru +0 -1
  72. data/t/t0011-close-on-exec-set.sh +1 -1
  73. data/t/t0015-working_directory.sh +56 -0
  74. data/t/t0016-onenine-encoding-is-tricky.sh +28 -0
  75. data/t/t0016.rb +15 -0
  76. data/t/t0020-large-sendfile-response.sh +141 -0
  77. data/t/t0300-async_sinatra.sh +0 -6
  78. data/t/t0501-cramp-rainsocket.sh +38 -0
  79. data/t/t9001-sendfile-to-path.sh +5 -4
  80. data/t/t9002-server-token.sh +37 -0
  81. data/t/t9002.ru +4 -0
  82. data/t/test-lib.sh +1 -1
  83. data/t/test_isolate.rb +14 -11
  84. metadata +87 -18
@@ -0,0 +1,119 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # non-portable body response stuff goes here
4
+ #
5
+ # The sendfile 1.0.0 RubyGem includes IO#sendfile and
6
+ # IO#sendfile_nonblock. Previous versions of "sendfile" didn't have
7
+ # IO#sendfile_nonblock, and IO#sendfile in previous versions could
8
+ # block other threads under 1.8 with large files
9
+ #
10
+ # IO#sendfile currently (June 2010) beats 1.9 IO.copy_stream with
11
+ # non-Linux support and large files on 32-bit. We still fall back to
12
+ # IO.copy_stream (if available) if we're dealing with DevFdResponse
13
+ # objects, though.
14
+ #
15
+ # Linux-only splice(2) support via the "io_splice" gem will eventually
16
+ # be added for streaming sockets/pipes, too.
17
+ #
18
+ # * write_body_file - regular files (sendfile or pread+write)
19
+ # * write_body_stream - socket/pipes (read+write, splice later)
20
+ # * write_body_each - generic fallback
21
+ #
22
+ # callgraph is as follows:
23
+ #
24
+ # write_body
25
+ # `- write_body_each
26
+ # `- write_body_path
27
+ # `- write_body_file
28
+ # `- write_body_stream
29
+ #
30
+ module Rainbows::Response::Body # :nodoc:
31
+ ALIASES = {}
32
+
33
+ # to_io is not part of the Rack spec, but make an exception here
34
+ # since we can conserve path lookups and file descriptors.
35
+ # \Rainbows! will never get here without checking for the existence
36
+ # of body.to_path first.
37
+ def body_to_io(body)
38
+ if body.respond_to?(:to_io)
39
+ body.to_io
40
+ else
41
+ # try to take advantage of Rainbows::DevFdResponse, calling File.open
42
+ # is a last resort
43
+ path = body.to_path
44
+ path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path, 'rb')
45
+ end
46
+ end
47
+
48
+ if IO.method_defined?(:sendfile_nonblock)
49
+ def write_body_file(sock, body)
50
+ sock.sendfile(body, 0)
51
+ end
52
+ end
53
+
54
+ if IO.respond_to?(:copy_stream)
55
+ unless method_defined?(:write_body_file)
56
+ # try to use sendfile() via IO.copy_stream, otherwise pread()+write()
57
+ def write_body_file(sock, body)
58
+ IO.copy_stream(body, sock, nil, 0)
59
+ end
60
+ end
61
+
62
+ # only used when body is a pipe or socket that can't handle
63
+ # pread() semantics
64
+ def write_body_stream(sock, body)
65
+ IO.copy_stream(body, sock)
66
+ ensure
67
+ body.respond_to?(:close) and body.close
68
+ end
69
+ else
70
+ # fall back to body#each, which is a Rack standard
71
+ ALIASES[:write_body_stream] = :write_body_each
72
+ end
73
+
74
+ if method_defined?(:write_body_file)
75
+
76
+ # middlewares/apps may return with a body that responds to +to_path+
77
+ def write_body_path(sock, body)
78
+ inp = body_to_io(body)
79
+ if inp.stat.file?
80
+ begin
81
+ write_body_file(sock, inp)
82
+ ensure
83
+ inp.close if inp != body
84
+ end
85
+ else
86
+ write_body_stream(sock, inp)
87
+ end
88
+ ensure
89
+ body.respond_to?(:close) && inp != body and body.close
90
+ end
91
+ else
92
+ def write_body_path(sock, body)
93
+ write_body_stream(sock, body_to_io(body))
94
+ end
95
+ end
96
+
97
+ if method_defined?(:write_body_path)
98
+ def write_body(client, body)
99
+ body.respond_to?(:to_path) ?
100
+ write_body_path(client, body) :
101
+ write_body_each(client, body)
102
+ end
103
+ else
104
+ ALIASES[:write_body] = :write_body_each
105
+ end
106
+
107
+ # generic body writer, used for most dynamically generated responses
108
+ def write_body_each(socket, body)
109
+ body.each { |chunk| socket.write(chunk) }
110
+ ensure
111
+ body.respond_to?(:close) and body.close
112
+ end
113
+
114
+ def self.included(klass)
115
+ ALIASES.each do |new_method, orig_method|
116
+ klass.__send__(:alias_method, new_method, orig_method)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ require 'time' # for Time#httpdate
4
+
5
+ module Rainbows::Response
6
+
7
+ CODES = Unicorn::HttpResponse::CODES
8
+
9
+ def response_header(response, out)
10
+ status, headers = response
11
+ status = CODES[status.to_i] || status
12
+
13
+ headers.each do |key, value|
14
+ next if %r{\A(?:X-Rainbows-|Connection\z|Date\z|Status\z)}i =~ key
15
+ if value =~ /\n/
16
+ # avoiding blank, key-only cookies with /\n+/
17
+ out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
18
+ else
19
+ out << "#{key}: #{value}\r\n"
20
+ end
21
+ end
22
+
23
+ "HTTP/1.1 #{status}\r\n" \
24
+ "Date: #{Time.now.httpdate}\r\n" \
25
+ "Status: #{status}\r\n" \
26
+ "#{out.join('')}\r\n"
27
+ end
28
+
29
+ def write_header(socket, response, out)
30
+ out and socket.write(response_header(response, out))
31
+ end
32
+
33
+ def write_response(socket, response, out)
34
+ write_header(socket, response, out)
35
+ write_body(socket, response[2])
36
+ end
37
+
38
+ # called after forking
39
+ def self.setup(klass)
40
+ require('rainbows/response/body') and
41
+ klass.__send__(:include, Rainbows::Response::Body)
42
+ end
43
+ end
@@ -1,11 +1,15 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  require 'rainbows/ev_core'
3
4
  module Rainbows
4
5
  module Rev
5
6
 
6
7
  class Client < ::Rev::IO
8
+ include Rainbows::ByteSlice
7
9
  include Rainbows::EvCore
10
+ include Rainbows::Response
8
11
  G = Rainbows::G
12
+ HH = Rack::Utils::HeaderHash
9
13
 
10
14
  def initialize(io)
11
15
  CONN[self] = false
@@ -14,6 +18,33 @@ module Rainbows
14
18
  @deferred_bodies = [] # for (fast) regular files only
15
19
  end
16
20
 
21
+ def quit
22
+ super
23
+ close if @deferred_bodies.empty? && @_write_buffer.empty?
24
+ end
25
+
26
+ # override the ::Rev::IO#write method try to write directly to the
27
+ # kernel socket buffers to avoid an extra userspace copy if
28
+ # possible.
29
+ def write(buf)
30
+ if @_write_buffer.empty?
31
+ begin
32
+ w = @_io.write_nonblock(buf)
33
+ if w == Rack::Utils.bytesize(buf)
34
+ return on_write_complete
35
+ end
36
+ # we never care for the return value, but yes, we may return
37
+ # a "fake" short write from super(buf) if anybody cares.
38
+ buf = byte_slice(buf, w..-1)
39
+ rescue Errno::EAGAIN
40
+ break # fall through to super(buf)
41
+ rescue
42
+ return close
43
+ end while true
44
+ end
45
+ super(buf)
46
+ end
47
+
17
48
  # queued, optional response bodies, it should only be unpollable "fast"
18
49
  # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
19
50
  # are also part of this. We'll also stick DeferredResponse bodies in
@@ -27,6 +58,42 @@ module Rainbows
27
58
  @_write_buffer.empty? && @deferred_bodies.empty? and close.nil?
28
59
  end
29
60
 
61
+ def rev_write_response(response, out)
62
+ status, headers, body = response
63
+
64
+ body.respond_to?(:to_path) or
65
+ return write_response(self, response, out)
66
+
67
+ headers = HH.new(headers)
68
+ io = body_to_io(body)
69
+ st = io.stat
70
+
71
+ if st.socket? || st.pipe?
72
+ do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
73
+ do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
74
+ # too tricky to support keepalive/pipelining when a response can
75
+ # take an indeterminate amount of time here.
76
+ if out.nil?
77
+ do_chunk = false
78
+ else
79
+ out[0] = CONN_CLOSE
80
+ end
81
+
82
+ # we only want to attach to the Rev::Loop belonging to the
83
+ # main thread in Ruby 1.9
84
+ io = DeferredResponse.new(io, self, do_chunk, body).
85
+ attach(Server::LOOP)
86
+ elsif st.file?
87
+ headers.delete('Transfer-Encoding')
88
+ headers['Content-Length'] ||= st.size.to_s
89
+ io = to_sendfile(io)
90
+ else # char/block device, directory, whatever... nobody cares
91
+ return write_response(self, response, out)
92
+ end
93
+ defer_body(io, out)
94
+ write_header(self, response, out)
95
+ end
96
+
30
97
  def app_call
31
98
  begin
32
99
  KATO.delete(self)
@@ -36,7 +103,7 @@ module Rainbows
36
103
  alive = @hp.keepalive? && G.alive
37
104
  out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
38
105
 
39
- DeferredResponse.write(self, response, out)
106
+ rev_write_response(response, out)
40
107
  if alive
41
108
  @env.clear
42
109
  @hp.reset
@@ -52,16 +119,16 @@ module Rainbows
52
119
  end
53
120
 
54
121
  def on_write_complete
55
- if body = @deferred_bodies.first
122
+ if body = @deferred_bodies[0]
123
+ # no socket or pipes, body must be a regular file to continue here
56
124
  return if DeferredResponse === body
125
+
57
126
  begin
58
- begin
59
- write(body.sysread(CHUNK_SIZE))
60
- rescue EOFError # expected at file EOF
61
- @deferred_bodies.shift
62
- body.close
63
- close if :close == @state && @deferred_bodies.empty?
64
- end
127
+ rev_sendfile(body)
128
+ rescue EOFError # expected at file EOF
129
+ @deferred_bodies.shift
130
+ body.close
131
+ close if :close == @state && @deferred_bodies.empty?
65
132
  rescue => e
66
133
  handle_error(e)
67
134
  end
@@ -71,6 +138,9 @@ module Rainbows
71
138
  end
72
139
 
73
140
  def on_close
141
+ while f = @deferred_bodies.shift
142
+ DeferredResponse === f or f.close
143
+ end
74
144
  CONN.delete(self)
75
145
  end
76
146
 
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  require 'rev'
3
4
  Rev::VERSION >= '0.3.0' or abort 'rev >= 0.3.0 is required'
4
5
  require 'rainbows/rev/heartbeat'
@@ -22,6 +23,9 @@ module Rainbows
22
23
  # for connections and doesn't die until the parent dies (or is
23
24
  # given a INT, QUIT, or TERM signal)
24
25
  def worker_loop(worker)
26
+ Rainbows::Response.setup(Rainbows::Rev::Client)
27
+ require 'rainbows/rev/sendfile'
28
+ Rainbows::Rev::Client.__send__(:include, Rainbows::Rev::Sendfile)
25
29
  init_worker_process(worker)
26
30
  mod = self.class.const_get(@use)
27
31
  rloop = Server.const_set(:LOOP, ::Rev::Loop.default)
@@ -1,55 +1,12 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  module Rainbows
3
4
  module Rev
4
5
 
5
6
  # this is class is specific to Rev for writing large static files
6
7
  # or proxying IO-derived objects
7
8
  class DeferredResponse < ::Rev::IO
8
- include Unicorn
9
9
  include Rainbows::Const
10
- G = Rainbows::G
11
- HH = Rack::Utils::HeaderHash
12
-
13
- def self.write(client, response, out)
14
- status, headers, body = response
15
-
16
- body.respond_to?(:to_path) or
17
- return HttpResponse.write(client, response, out)
18
-
19
- headers = HH.new(headers)
20
-
21
- # to_io is not part of the Rack spec, but make an exception
22
- # here since we can't get here without checking to_path first
23
- io = body.to_io if body.respond_to?(:to_io)
24
- io ||= ::IO.new($1.to_i) if body.to_path =~ %r{\A/dev/fd/(\d+)\z}
25
- io ||= File.open(body.to_path, 'rb')
26
- st = io.stat
27
-
28
- if st.socket? || st.pipe?
29
- do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
30
- do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
31
- # too tricky to support keepalive/pipelining when a response can
32
- # take an indeterminate amount of time here.
33
- if out.nil?
34
- do_chunk = false
35
- else
36
- out[0] = CONN_CLOSE
37
- end
38
-
39
- # we only want to attach to the Rev::Loop belonging to the
40
- # main thread in Ruby 1.9
41
- io = new(io, client, do_chunk, body).attach(Server::LOOP)
42
- elsif st.file?
43
- headers.delete('Transfer-Encoding')
44
- headers['Content-Length'] ||= st.size.to_s
45
- else # char/block device, directory, whatever... nobody cares
46
- return HttpResponse.write(client, response, out)
47
- end
48
- client.defer_body(io, out)
49
- out.nil? or
50
- client.write(HttpResponse.header_string(status, headers.to_hash, out))
51
- end
52
-
53
10
  def initialize(io, client, do_chunk, body)
54
11
  super(io)
55
12
  @client, @do_chunk, @body = client, do_chunk, body
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  module Rainbows
3
4
  module Rev
4
5
 
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  require 'rainbows/rev'
3
4
 
4
5
  module Rainbows
@@ -0,0 +1,26 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ module Rainbows::Rev::Sendfile
4
+ if IO.method_defined?(:sendfile_nonblock)
5
+ F = Rainbows::StreamFile
6
+
7
+ def to_sendfile(io)
8
+ F[0, io]
9
+ end
10
+
11
+ def rev_sendfile(body)
12
+ body.offset += @_io.sendfile_nonblock(body, body.offset, 0x10000)
13
+ enable_write_watcher
14
+ rescue Errno::EAGAIN
15
+ enable_write_watcher
16
+ end
17
+ else
18
+ def to_sendfile(io)
19
+ io
20
+ end
21
+
22
+ def rev_sendfile(body)
23
+ write(body.sysread(0x4000))
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  require 'thread'
3
4
  require 'rainbows/rev/master'
4
5
 
@@ -22,7 +23,7 @@ module Rainbows
22
23
  enable
23
24
  alive = @hp.keepalive? && G.alive
24
25
  out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
25
- DeferredResponse.write(self, response, out)
26
+ rev_write_response(response, out)
26
27
  return quit unless alive && G.alive
27
28
 
28
29
  @env.clear
data/lib/rainbows/rev.rb CHANGED
@@ -25,6 +25,7 @@ module Rainbows
25
25
 
26
26
  module Rev
27
27
 
28
+ # :stopdoc:
28
29
  # keep-alive timeout scoreboard
29
30
  KATO = {}
30
31
 
@@ -37,5 +38,6 @@ module Rainbows
37
38
  end
38
39
 
39
40
  include Core
41
+ # :startdoc:
40
42
  end
41
43
  end
@@ -15,9 +15,11 @@ module Rainbows
15
15
  include Base
16
16
  include Fiber::Rev
17
17
 
18
- def worker_loop(worker)
18
+ def worker_loop(worker) # :nodoc:
19
+ Rainbows::Response.setup(Rainbows::Fiber::Rev::Server)
19
20
  init_worker_process(worker)
20
21
  Server.const_set(:MAX, @worker_connections)
22
+ Rainbows::Fiber::Base.setup(Rainbows::Fiber::Rev::Server, nil)
21
23
  Server.const_set(:APP, G.server.app)
22
24
  Heartbeat.new(1, true).attach(::Rev::Loop.default)
23
25
  kato = Kato.new.attach(::Rev::Loop.default)
@@ -20,17 +20,19 @@ module Rainbows
20
20
 
21
21
  module RevThreadPool
22
22
 
23
+ # :stopdoc:
23
24
  DEFAULTS = {
24
25
  :pool_size => 20, # same default size as ThreadPool (w/o Rev)
25
26
  }
27
+ #:startdoc:
26
28
 
27
- def self.setup
29
+ def self.setup # :nodoc:
28
30
  DEFAULTS.each { |k,v| O[k] ||= v }
29
31
  Integer === O[:pool_size] && O[:pool_size] > 0 or
30
32
  raise ArgumentError, "pool_size must a be an Integer > 0"
31
33
  end
32
34
 
33
- class PoolWatcher < ::Rev::TimerWatcher
35
+ class PoolWatcher < ::Rev::TimerWatcher # :nodoc: all
34
36
  def initialize(threads)
35
37
  @threads = threads
36
38
  super(G.server.timeout, true)
@@ -41,7 +43,7 @@ module Rainbows
41
43
  end
42
44
  end
43
45
 
44
- class Client < Rainbows::Rev::ThreadClient
46
+ class Client < Rainbows::Rev::ThreadClient # :nodoc:
45
47
  def app_dispatch
46
48
  QUEUE << self
47
49
  end
@@ -49,7 +51,7 @@ module Rainbows
49
51
 
50
52
  include Rainbows::Rev::Core
51
53
 
52
- def init_worker_threads(master, queue)
54
+ def init_worker_threads(master, queue) # :nodoc:
53
55
  O[:pool_size].times.map do
54
56
  Thread.new do
55
57
  begin
@@ -62,7 +64,7 @@ module Rainbows
62
64
  end
63
65
  end
64
66
 
65
- def init_worker_process(worker)
67
+ def init_worker_process(worker) # :nodoc:
66
68
  super
67
69
  master = Rev::Master.new(Queue.new).attach(::Rev::Loop.default)
68
70
  queue = Client.const_set(:QUEUE, Queue.new)
@@ -20,7 +20,7 @@ module Rainbows
20
20
 
21
21
  module RevThreadSpawn
22
22
 
23
- class Client < Rainbows::Rev::ThreadClient
23
+ class Client < Rainbows::Rev::ThreadClient # :nodoc: all
24
24
  def app_dispatch
25
25
  Thread.new(self) { |client| MASTER << [ client, app_response ] }
26
26
  end
@@ -28,7 +28,7 @@ module Rainbows
28
28
 
29
29
  include Rainbows::Rev::Core
30
30
 
31
- def init_worker_process(worker)
31
+ def init_worker_process(worker) # :nodoc:
32
32
  super
33
33
  master = Rev::Master.new(Queue.new).attach(::Rev::Loop.default)
34
34
  Client.const_set(:MASTER, master)