rainbows 0.94.0 → 0.95.0

Sign up to get free protection for your applications and to get access to all the features.
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)