unicorn-shopify 4.8.2.5.23

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 (150) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitignore +25 -0
  5. data/.mailmap +26 -0
  6. data/.olddoc.yml +15 -0
  7. data/Application_Timeouts +77 -0
  8. data/CONTRIBUTORS +35 -0
  9. data/COPYING +674 -0
  10. data/DESIGN +97 -0
  11. data/Documentation/.gitignore +5 -0
  12. data/Documentation/GNUmakefile +30 -0
  13. data/Documentation/unicorn.1.txt +185 -0
  14. data/Documentation/unicorn_rails.1.txt +175 -0
  15. data/FAQ +61 -0
  16. data/GIT-VERSION-GEN +39 -0
  17. data/GNUmakefile +252 -0
  18. data/HACKING +120 -0
  19. data/ISSUES +100 -0
  20. data/KNOWN_ISSUES +79 -0
  21. data/LICENSE +67 -0
  22. data/Links +59 -0
  23. data/PHILOSOPHY +145 -0
  24. data/README +145 -0
  25. data/Rakefile +16 -0
  26. data/SIGNALS +123 -0
  27. data/Sandbox +103 -0
  28. data/TODO +5 -0
  29. data/TUNING +101 -0
  30. data/archive/.gitignore +3 -0
  31. data/archive/slrnpull.conf +4 -0
  32. data/bin/unicorn +126 -0
  33. data/bin/unicorn_rails +209 -0
  34. data/examples/big_app_gc.rb +2 -0
  35. data/examples/echo.ru +27 -0
  36. data/examples/init.sh +74 -0
  37. data/examples/logger_mp_safe.rb +25 -0
  38. data/examples/logrotate.conf +29 -0
  39. data/examples/nginx.conf +156 -0
  40. data/examples/unicorn.conf.minimal.rb +13 -0
  41. data/examples/unicorn.conf.rb +113 -0
  42. data/ext/unicorn_http/CFLAGS +13 -0
  43. data/ext/unicorn_http/c_util.h +124 -0
  44. data/ext/unicorn_http/common_field_optimization.h +111 -0
  45. data/ext/unicorn_http/ext_help.h +82 -0
  46. data/ext/unicorn_http/extconf.rb +10 -0
  47. data/ext/unicorn_http/global_variables.h +97 -0
  48. data/ext/unicorn_http/httpdate.c +78 -0
  49. data/ext/unicorn_http/unicorn_http.rl +934 -0
  50. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  51. data/lib/unicorn.rb +112 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +686 -0
  56. data/lib/unicorn/const.rb +21 -0
  57. data/lib/unicorn/http_request.rb +125 -0
  58. data/lib/unicorn/http_response.rb +73 -0
  59. data/lib/unicorn/http_server.rb +816 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +81 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +197 -0
  64. data/lib/unicorn/stream_input.rb +146 -0
  65. data/lib/unicorn/tee_input.rb +133 -0
  66. data/lib/unicorn/tmpio.rb +27 -0
  67. data/lib/unicorn/util.rb +90 -0
  68. data/lib/unicorn/worker.rb +140 -0
  69. data/setup.rb +1586 -0
  70. data/t/.gitignore +4 -0
  71. data/t/GNUmakefile +74 -0
  72. data/t/README +42 -0
  73. data/t/before_murder.ru +7 -0
  74. data/t/bin/content-md5-put +36 -0
  75. data/t/bin/sha1sum.rb +17 -0
  76. data/t/bin/unused_listen +40 -0
  77. data/t/broken-app.ru +12 -0
  78. data/t/detach.ru +11 -0
  79. data/t/env.ru +3 -0
  80. data/t/fails-rack-lint.ru +5 -0
  81. data/t/heartbeat-timeout.ru +12 -0
  82. data/t/hijack.ru +42 -0
  83. data/t/listener_names.ru +4 -0
  84. data/t/my-tap-lib.sh +201 -0
  85. data/t/oob_gc.ru +20 -0
  86. data/t/oob_gc_path.ru +20 -0
  87. data/t/pid.ru +3 -0
  88. data/t/preread_input.ru +17 -0
  89. data/t/rack-input-tests.ru +21 -0
  90. data/t/t0000-http-basic.sh +50 -0
  91. data/t/t0001-reload-bad-config.sh +53 -0
  92. data/t/t0002-config-conflict.sh +49 -0
  93. data/t/t0002-parser-error.sh +94 -0
  94. data/t/t0003-working_directory.sh +51 -0
  95. data/t/t0004-heartbeat-timeout.sh +69 -0
  96. data/t/t0004-working_directory_broken.sh +24 -0
  97. data/t/t0005-working_directory_app.rb.sh +40 -0
  98. data/t/t0006-reopen-logs.sh +83 -0
  99. data/t/t0006.ru +13 -0
  100. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  101. data/t/t0008-back_out_of_upgrade.sh +110 -0
  102. data/t/t0009-broken-app.sh +56 -0
  103. data/t/t0009-winch_ttin.sh +59 -0
  104. data/t/t0010-reap-logging.sh +55 -0
  105. data/t/t0011-active-unix-socket.sh +79 -0
  106. data/t/t0012-reload-empty-config.sh +85 -0
  107. data/t/t0013-rewindable-input-false.sh +24 -0
  108. data/t/t0013.ru +12 -0
  109. data/t/t0014-rewindable-input-true.sh +24 -0
  110. data/t/t0014.ru +12 -0
  111. data/t/t0015-configurator-internals.sh +25 -0
  112. data/t/t0018-write-on-close.sh +23 -0
  113. data/t/t0019-max_header_len.sh +49 -0
  114. data/t/t0020-at_exit-handler.sh +49 -0
  115. data/t/t0021-process_detach.sh +29 -0
  116. data/t/t0022-listener_names-preload_app.sh +32 -0
  117. data/t/t0023-before-murder.sh +40 -0
  118. data/t/t0024-before-murder_once.sh +52 -0
  119. data/t/t0100-rack-input-tests.sh +124 -0
  120. data/t/t0116-client_body_buffer_size.sh +80 -0
  121. data/t/t0116.ru +16 -0
  122. data/t/t0200-rack-hijack.sh +27 -0
  123. data/t/t0300-no-default-middleware.sh +20 -0
  124. data/t/t9000-preread-input.sh +48 -0
  125. data/t/t9001-oob_gc.sh +47 -0
  126. data/t/t9002-oob_gc-path.sh +75 -0
  127. data/t/test-lib.sh +128 -0
  128. data/t/write-on-close.ru +11 -0
  129. data/test/aggregate.rb +15 -0
  130. data/test/benchmark/README +50 -0
  131. data/test/benchmark/dd.ru +18 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/exec/README +5 -0
  134. data/test/exec/test_exec.rb +1047 -0
  135. data/test/test_helper.rb +297 -0
  136. data/test/unit/test_configurator.rb +175 -0
  137. data/test/unit/test_droplet.rb +28 -0
  138. data/test/unit/test_http_parser.rb +854 -0
  139. data/test/unit/test_http_parser_ng.rb +622 -0
  140. data/test/unit/test_request.rb +182 -0
  141. data/test/unit/test_response.rb +93 -0
  142. data/test/unit/test_server.rb +268 -0
  143. data/test/unit/test_signals.rb +188 -0
  144. data/test/unit/test_socket_helper.rb +197 -0
  145. data/test/unit/test_stream_input.rb +203 -0
  146. data/test/unit/test_tee_input.rb +304 -0
  147. data/test/unit/test_upload.rb +306 -0
  148. data/test/unit/test_util.rb +105 -0
  149. data/unicorn.gemspec +41 -0
  150. metadata +311 -0
@@ -0,0 +1,21 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module Unicorn::Const # :nodoc:
4
+ # default TCP listen host address (0.0.0.0, all interfaces)
5
+ DEFAULT_HOST = "0.0.0.0"
6
+
7
+ # default TCP listen port (8080)
8
+ DEFAULT_PORT = 8080
9
+
10
+ # default TCP listen address and port (0.0.0.0:8080)
11
+ DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}"
12
+
13
+ # The basic request body size we'll try to read at once (16 kilobytes).
14
+ CHUNK_SIZE = 16 * 1024
15
+
16
+ # Maximum request body size before it is moved out of memory and into a
17
+ # temporary file for reading (112 kilobytes). This is the default
18
+ # value of client_body_buffer_size.
19
+ MAX_BODY = 1024 * 112
20
+ end
21
+ require_relative 'version'
@@ -0,0 +1,125 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # no stable API here
4
+ require 'unicorn_http'
5
+
6
+ # TODO: remove redundant names
7
+ Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
8
+ class Unicorn::HttpParser
9
+
10
+ # default parameters we merge into the request env for Rack handlers
11
+ DEFAULTS = {
12
+ "rack.errors" => $stderr,
13
+ "rack.multiprocess" => true,
14
+ "rack.multithread" => false,
15
+ "rack.run_once" => false,
16
+ "rack.version" => [1, 1],
17
+ "SCRIPT_NAME" => "",
18
+
19
+ # this is not in the Rack spec, but some apps may rely on it
20
+ "SERVER_SOFTWARE" => "Unicorn #{Unicorn::Const::UNICORN_VERSION}"
21
+ }
22
+
23
+ NULL_IO = StringIO.new("")
24
+
25
+ attr_accessor :response_start_sent
26
+
27
+ # :stopdoc:
28
+ # A frozen format for this is about 15% faster
29
+ # Drop these frozen strings when Ruby 2.2 becomes more prevalent,
30
+ # 2.2+ optimizes hash assignments when used with literal string keys
31
+ REMOTE_ADDR = 'REMOTE_ADDR'.freeze
32
+ RACK_INPUT = 'rack.input'.freeze
33
+ HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ']
34
+ @@input_class = Unicorn::TeeInput
35
+ @@check_client_connection = false
36
+
37
+ def self.input_class
38
+ @@input_class
39
+ end
40
+
41
+ def self.input_class=(klass)
42
+ @@input_class = klass
43
+ end
44
+
45
+ def self.check_client_connection
46
+ @@check_client_connection
47
+ end
48
+
49
+ def self.check_client_connection=(bool)
50
+ @@check_client_connection = bool
51
+ end
52
+
53
+ # :startdoc:
54
+
55
+ # Does the majority of the IO processing. It has been written in
56
+ # Ruby using about 8 different IO processing strategies.
57
+ #
58
+ # It is currently carefully constructed to make sure that it gets
59
+ # the best possible performance for the common case: GET requests
60
+ # that are fully complete after a single read(2)
61
+ #
62
+ # Anyone who thinks they can make it faster is more than welcome to
63
+ # take a crack at it.
64
+ #
65
+ # returns an environment hash suitable for Rack if successful
66
+ # This does minimal exception trapping and it is up to the caller
67
+ # to handle any socket errors (e.g. user aborted upload).
68
+ def read(socket)
69
+ clear
70
+ e = env
71
+
72
+ # From http://www.ietf.org/rfc/rfc3875:
73
+ # "Script authors should be aware that the REMOTE_ADDR and
74
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
75
+ # may not identify the ultimate source of the request. They
76
+ # identify the client for the immediate request to the server;
77
+ # that client may be a proxy, gateway, or other intermediary
78
+ # acting on behalf of the actual source client."
79
+ e[REMOTE_ADDR] = socket.kgio_addr
80
+
81
+ # short circuit the common case with small GET requests first
82
+ socket.kgio_read!(16384, buf)
83
+ if parse.nil?
84
+ # Parser is not done, queue up more data to read and continue parsing
85
+ # an Exception thrown from the parser will throw us out of the loop
86
+ false until add_parse(socket.kgio_read!(16384))
87
+ end
88
+
89
+ # detect if the socket is valid by writing a partial response:
90
+ if @@check_client_connection && headers?
91
+ @response_start_sent = true
92
+ HTTP_RESPONSE_START.each { |c| socket.write(c) }
93
+ end
94
+
95
+ e[RACK_INPUT] = 0 == content_length ?
96
+ NULL_IO : @@input_class.new(socket, self)
97
+ hijack_setup(e, socket)
98
+ e.merge!(DEFAULTS)
99
+ end
100
+
101
+ # Rack 1.5.0 (protocol version 1.2) adds hijack request support
102
+ if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
103
+ DEFAULTS["rack.hijack?"] = true
104
+ DEFAULTS["rack.version"] = [1, 2]
105
+
106
+ RACK_HIJACK = "rack.hijack".freeze
107
+ RACK_HIJACK_IO = "rack.hijack_io".freeze
108
+
109
+ def hijacked?
110
+ env.include?(RACK_HIJACK_IO)
111
+ end
112
+
113
+ def hijack_setup(e, socket)
114
+ e[RACK_HIJACK] = proc { e[RACK_HIJACK_IO] = socket }
115
+ end
116
+ else
117
+ # old Rack, do nothing.
118
+ def hijack_setup(e, _)
119
+ end
120
+
121
+ def hijacked?
122
+ false
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,73 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # Writes a Rack response to your client using the HTTP/1.1 specification.
4
+ # You use it by simply doing:
5
+ #
6
+ # status, headers, body = rack_app.call(env)
7
+ # http_response_write(socket, status, headers, body)
8
+ #
9
+ # Most header correctness (including Content-Length and Content-Type)
10
+ # is the job of Rack, with the exception of the "Date" and "Status" header.
11
+ module Unicorn::HttpResponse
12
+
13
+ # Every standard HTTP code mapped to the appropriate message.
14
+ CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
15
+ hash[code] = "#{code} #{msg}"
16
+ hash
17
+ }
18
+ CRLF = "\r\n"
19
+
20
+ def err_response(code, response_start_sent)
21
+ "#{response_start_sent ? '' : 'HTTP/1.1 '}#{CODES[code]}\r\n\r\n"
22
+ end
23
+
24
+ # writes the rack_response to socket as an HTTP response
25
+ def http_response_write(socket, status, headers, body,
26
+ response_start_sent=false)
27
+ hijack = nil
28
+
29
+ http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
30
+ if headers
31
+ buf = "#{http_response_start}#{CODES[status.to_i] || status}\r\n" \
32
+ "Date: #{httpdate}\r\n" \
33
+ "Connection: close\r\n"
34
+ headers.each do |key, value|
35
+ case key
36
+ when %r{\A(?:Date\z|Connection\z)}i
37
+ next
38
+ when "rack.hijack"
39
+ # this was an illegal key in Rack < 1.5, so it should be
40
+ # OK to silently discard it for those older versions
41
+ hijack = hijack_prepare(value)
42
+ else
43
+ if value =~ /\n/
44
+ # avoiding blank, key-only cookies with /\n+/
45
+ buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
46
+ else
47
+ buf << "#{key}: #{value}\r\n"
48
+ end
49
+ end
50
+ end
51
+ socket.write(buf << CRLF)
52
+ end
53
+
54
+ if hijack
55
+ body = nil # ensure we do not close body
56
+ hijack.call(socket)
57
+ else
58
+ body.each { |chunk| socket.write(chunk) }
59
+ end
60
+ ensure
61
+ body.respond_to?(:close) and body.close
62
+ end
63
+
64
+ # Rack 1.5.0 (protocol version 1.2) adds response hijacking support
65
+ if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
66
+ def hijack_prepare(value)
67
+ value
68
+ end
69
+ else
70
+ def hijack_prepare(_)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,816 @@
1
+ # -*- encoding: binary -*-
2
+ require 'set'
3
+
4
+ # This is the process manager of Unicorn. This manages worker
5
+ # processes which in turn handle the I/O and application process.
6
+ # Listener sockets are started in the master process and shared with
7
+ # forked worker children.
8
+ #
9
+ # Users do not need to know the internals of this class, but reading the
10
+ # {source}[http://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
11
+ # is education for programmers wishing to learn how \Unicorn works.
12
+ # See Unicorn::Configurator for information on how to configure \Unicorn.
13
+ class Unicorn::HttpServer
14
+ # :stopdoc:
15
+ attr_accessor :app, :request, :timeout, :worker_processes,
16
+ :before_fork, :after_fork, :before_exec,
17
+ :listener_opts, :preload_app,
18
+ :reexec_pid, :orig_app, :init_listeners,
19
+ :master_pid, :config, :ready_pipe, :user,
20
+ :before_murder
21
+
22
+ attr_reader :pid, :logger
23
+ include Unicorn::SocketHelper
24
+ include Unicorn::HttpResponse
25
+
26
+ # all bound listener sockets
27
+ LISTENERS = []
28
+
29
+ # listeners we have yet to bind
30
+ NEW_LISTENERS = []
31
+
32
+ # list of signals we care about and trap in master.
33
+ QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
34
+
35
+ # :startdoc:
36
+ # We populate this at startup so we can figure out how to reexecute
37
+ # and upgrade the currently running instance of Unicorn
38
+ # This Hash is considered a stable interface and changing its contents
39
+ # will allow you to switch between different installations of Unicorn
40
+ # or even different installations of the same applications without
41
+ # downtime. Keys of this constant Hash are described as follows:
42
+ #
43
+ # * 0 - the path to the unicorn/unicorn_rails executable
44
+ # * :argv - a deep copy of the ARGV array the executable originally saw
45
+ # * :cwd - the working directory of the application, this is where
46
+ # you originally started Unicorn.
47
+ #
48
+ # To change your unicorn executable to a different path without downtime,
49
+ # you can set the following in your Unicorn config file, HUP and then
50
+ # continue with the traditional USR2 + QUIT upgrade steps:
51
+ #
52
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.2.0/bin/unicorn"
53
+ START_CTX = {
54
+ :argv => ARGV.map(&:dup),
55
+ 0 => $0.dup,
56
+ }
57
+ # We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
58
+ # and like systems
59
+ START_CTX[:cwd] = begin
60
+ a = File.stat(pwd = ENV['PWD'])
61
+ b = File.stat(Dir.pwd)
62
+ a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
63
+ rescue
64
+ Dir.pwd
65
+ end
66
+ # :stopdoc:
67
+
68
+ # Creates a working server on host:port (strange things happen if
69
+ # port isn't a Number). Use HttpServer::run to start the server and
70
+ # HttpServer.run.join to join the thread that's processing
71
+ # incoming requests on the socket.
72
+ def initialize(app, options = {})
73
+ @app = app
74
+ @request = Unicorn::HttpRequest.new
75
+ self.reexec_pid = 0
76
+ options = options.dup
77
+ @ready_pipe = options.delete(:ready_pipe)
78
+ @init_listeners = options[:listeners] ? options[:listeners].dup : []
79
+ options[:use_defaults] = true
80
+ self.config = Unicorn::Configurator.new(options)
81
+ self.listener_opts = {}
82
+
83
+ # We use @self_pipe differently in the master and worker processes:
84
+ #
85
+ # * The master process never closes or reinitializes this once
86
+ # initialized. Signal handlers in the master process will write to
87
+ # it to wake up the master from IO.select in exactly the same manner
88
+ # djb describes in http://cr.yp.to/docs/selfpipe.html
89
+ #
90
+ # * The workers immediately close the pipe they inherit. See the
91
+ # Unicorn::Worker class for the pipe workers use.
92
+ @self_pipe = []
93
+ @workers = {} # hash maps PIDs to Workers
94
+ @sig_queue = [] # signal queue used for self-piping
95
+ @before_murder_called = Set.new # ensures before_murder is called only once
96
+
97
+ # we try inheriting listeners first, so we bind them later.
98
+ # we don't write the pid file until we've bound listeners in case
99
+ # unicorn was started twice by mistake. Even though our #pid= method
100
+ # checks for stale/existing pid files, race conditions are still
101
+ # possible (and difficult/non-portable to avoid) and can be likely
102
+ # to clobber the pid if the second start was in quick succession
103
+ # after the first, so we rely on the listener binding to fail in
104
+ # that case. Some tests (in and outside of this source tree) and
105
+ # monitoring tools may also rely on pid files existing before we
106
+ # attempt to connect to the listener(s)
107
+ config.commit!(self, :skip => [:listeners, :pid])
108
+ self.orig_app = app
109
+ end
110
+
111
+ # Runs the thing. Returns self so you can run join on it
112
+ def start
113
+ inherit_listeners!
114
+ # this pipe is used to wake us up from select(2) in #join when signals
115
+ # are trapped. See trap_deferred.
116
+ @self_pipe.replace(Unicorn.pipe)
117
+ @master_pid = $$
118
+
119
+ # setup signal handlers before writing pid file in case people get
120
+ # trigger happy and send signals as soon as the pid file exists.
121
+ # Note that signals don't actually get handled until the #join method
122
+ QUEUE_SIGS.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
123
+ trap(:CHLD) { awaken_master }
124
+
125
+ # write pid early for Mongrel compatibility if we're not inheriting sockets
126
+ # This is needed for compatibility some Monit setups at least.
127
+ # This unfortunately has the side effect of clobbering valid PID if
128
+ # we upgrade and the upgrade breaks during preload_app==true && build_app!
129
+ self.pid = config[:pid]
130
+
131
+ build_app! if preload_app
132
+ bind_new_listeners!
133
+
134
+ spawn_missing_workers
135
+ self
136
+ end
137
+
138
+ # replaces current listener set with +listeners+. This will
139
+ # close the socket if it will not exist in the new listener set
140
+ def listeners=(listeners)
141
+ cur_names, dead_names = [], []
142
+ listener_names.each do |name|
143
+ if ?/ == name[0]
144
+ # mark unlinked sockets as dead so we can rebind them
145
+ (File.socket?(name) ? cur_names : dead_names) << name
146
+ else
147
+ cur_names << name
148
+ end
149
+ end
150
+ set_names = listener_names(listeners)
151
+ dead_names.concat(cur_names - set_names).uniq!
152
+
153
+ LISTENERS.delete_if do |io|
154
+ if dead_names.include?(sock_name(io))
155
+ (io.close rescue nil).nil? # true
156
+ else
157
+ set_server_sockopt(io, listener_opts[sock_name(io)])
158
+ false
159
+ end
160
+ end
161
+
162
+ (set_names - cur_names).each { |addr| listen(addr) }
163
+ end
164
+
165
+ def stdout_path=(path); redirect_io($stdout, path); end
166
+ def stderr_path=(path); redirect_io($stderr, path); end
167
+
168
+ def logger=(obj)
169
+ Unicorn::HttpRequest::DEFAULTS["rack.logger"] = @logger = obj
170
+ end
171
+
172
+ def clobber_pid(path)
173
+ unlink_pid_safe(@pid) if @pid
174
+ if path
175
+ fp = begin
176
+ tmp = "#{File.dirname(path)}/#{rand}.#$$"
177
+ File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
178
+ rescue Errno::EEXIST
179
+ retry
180
+ end
181
+ fp.syswrite("#$$\n")
182
+ File.rename(fp.path, path)
183
+ fp.close
184
+ end
185
+ end
186
+
187
+ # sets the path for the PID file of the master process
188
+ def pid=(path)
189
+ if path
190
+ if x = valid_pid?(path)
191
+ return path if pid && path == pid && x == $$
192
+ if x == reexec_pid && pid.end_with?('.oldbin')
193
+ logger.warn("will not set pid=#{path} while reexec-ed "\
194
+ "child is running PID:#{x}")
195
+ return
196
+ end
197
+ raise ArgumentError, "Already running on PID:#{x} " \
198
+ "(or pid=#{path} is stale)"
199
+ end
200
+ end
201
+
202
+ # rename the old pid if possible
203
+ if @pid && path
204
+ begin
205
+ File.rename(@pid, path)
206
+ rescue Errno::ENOENT, Errno::EXDEV
207
+ # a user may have accidentally removed the original,
208
+ # obviously cross-FS renames don't work, either.
209
+ clobber_pid(path)
210
+ end
211
+ else
212
+ clobber_pid(path)
213
+ end
214
+ @pid = path
215
+ end
216
+
217
+ # add a given address to the +listeners+ set, idempotently
218
+ # Allows workers to add a private, per-process listener via the
219
+ # after_fork hook. Very useful for debugging and testing.
220
+ # +:tries+ may be specified as an option for the number of times
221
+ # to retry, and +:delay+ may be specified as the time in seconds
222
+ # to delay between retries.
223
+ # A negative value for +:tries+ indicates the listen will be
224
+ # retried indefinitely, this is useful when workers belonging to
225
+ # different masters are spawned during a transparent upgrade.
226
+ def listen(address, opt = {}.merge(listener_opts[address] || {}))
227
+ address = config.expand_addr(address)
228
+ return if String === address && listener_names.include?(address)
229
+
230
+ delay = opt[:delay] || 0.5
231
+ tries = opt[:tries] || 5
232
+ begin
233
+ io = bind_listen(address, opt)
234
+ unless Kgio::TCPServer === io || Kgio::UNIXServer === io
235
+ io.autoclose = false
236
+ io = server_cast(io)
237
+ end
238
+ logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
239
+ LISTENERS << io
240
+ io
241
+ rescue Errno::EADDRINUSE => err
242
+ logger.error "adding listener failed addr=#{address} (in use)"
243
+ raise err if tries == 0
244
+ tries -= 1
245
+ logger.error "retrying in #{delay} seconds " \
246
+ "(#{tries < 0 ? 'infinite' : tries} tries left)"
247
+ sleep(delay)
248
+ retry
249
+ rescue => err
250
+ logger.fatal "error adding listener addr=#{address}"
251
+ raise err
252
+ end
253
+ end
254
+
255
+ # monitors children and receives signals forever
256
+ # (or until a termination signal is sent). This handles signals
257
+ # one-at-a-time time and we'll happily drop signals in case somebody
258
+ # is signalling us too often.
259
+ def join
260
+ respawn = true
261
+ last_check = time_now
262
+
263
+ proc_name 'master'
264
+ logger.info "master process ready" # test_exec.rb relies on this message
265
+ if @ready_pipe
266
+ begin
267
+ @ready_pipe.syswrite($$.to_s)
268
+ rescue => e
269
+ logger.warn("grandparent died too soon?: #{e.message} (#{e.class})")
270
+ end
271
+ @ready_pipe = @ready_pipe.close rescue nil
272
+ end
273
+ begin
274
+ reap_all_workers
275
+ case @sig_queue.shift
276
+ when nil
277
+ # avoid murdering workers after our master process (or the
278
+ # machine) comes out of suspend/hibernation
279
+ if (last_check + @timeout) >= (last_check = time_now)
280
+ sleep_time = murder_lazy_workers
281
+ else
282
+ sleep_time = @timeout/2.0 + 1
283
+ @logger.debug("waiting #{sleep_time}s after suspend/hibernation")
284
+ end
285
+ maintain_worker_count if respawn
286
+ master_sleep(sleep_time)
287
+ when :QUIT # graceful shutdown
288
+ break
289
+ when :TERM, :INT # immediate shutdown
290
+ stop(false)
291
+ break
292
+ when :USR1 # rotate logs
293
+ logger.info "master reopening logs..."
294
+ Unicorn::Util.reopen_logs
295
+ logger.info "master done reopening logs"
296
+ soft_kill_each_worker(:USR1)
297
+ when :USR2 # exec binary, stay alive in case something went wrong
298
+ reexec
299
+ when :WINCH
300
+ if Unicorn::Configurator::RACKUP[:daemonized]
301
+ respawn = false
302
+ logger.info "gracefully stopping all workers"
303
+ soft_kill_each_worker(:QUIT)
304
+ self.worker_processes = 0
305
+ else
306
+ logger.info "SIGWINCH ignored because we're not daemonized"
307
+ end
308
+ when :TTIN
309
+ respawn = true
310
+ self.worker_processes += 1
311
+ when :TTOU
312
+ self.worker_processes -= 1 if self.worker_processes > 0
313
+ when :HUP
314
+ respawn = true
315
+ if config.config_file
316
+ load_config!
317
+ else # exec binary and exit if there's no config file
318
+ logger.info "config_file not present, reexecuting binary"
319
+ reexec
320
+ end
321
+ end
322
+ rescue => e
323
+ Unicorn.log_error(@logger, "master loop error", e)
324
+ end while true
325
+ stop # gracefully shutdown all workers on our way out
326
+ logger.info "master complete"
327
+ unlink_pid_safe(pid) if pid
328
+ end
329
+
330
+ # Terminates all workers, but does not exit master process
331
+ def stop(graceful = true)
332
+ self.listeners = []
333
+ limit = time_now + timeout
334
+ until @workers.empty? || time_now > limit
335
+ if graceful
336
+ soft_kill_each_worker(:QUIT)
337
+ else
338
+ kill_each_worker(:TERM)
339
+ end
340
+ sleep(0.1)
341
+ reap_all_workers
342
+ end
343
+ kill_each_worker(:KILL)
344
+ end
345
+
346
+ def rewindable_input
347
+ Unicorn::HttpRequest.input_class.method_defined?(:rewind)
348
+ end
349
+
350
+ def rewindable_input=(bool)
351
+ Unicorn::HttpRequest.input_class = bool ?
352
+ Unicorn::TeeInput : Unicorn::StreamInput
353
+ end
354
+
355
+ def client_body_buffer_size
356
+ Unicorn::TeeInput.client_body_buffer_size
357
+ end
358
+
359
+ def client_body_buffer_size=(bytes)
360
+ Unicorn::TeeInput.client_body_buffer_size = bytes
361
+ end
362
+
363
+ def check_client_connection
364
+ Unicorn::HttpRequest.check_client_connection
365
+ end
366
+
367
+ def check_client_connection=(bool)
368
+ Unicorn::HttpRequest.check_client_connection = bool
369
+ end
370
+
371
+ private
372
+
373
+ # wait for a signal hander to wake us up and then consume the pipe
374
+ def master_sleep(sec)
375
+ @self_pipe[0].kgio_wait_readable(sec) or return
376
+ # 11 bytes is the maximum string length which can be embedded within
377
+ # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
378
+ # Most reads are only one byte here and uncommon, so it's not worth a
379
+ # persistent buffer, either:
380
+ @self_pipe[0].kgio_tryread(11)
381
+ end
382
+
383
+ def awaken_master
384
+ return if $$ != @master_pid
385
+ @self_pipe[1].kgio_trywrite('.') # wakeup master process from select
386
+ end
387
+
388
+ # reaps all unreaped workers
389
+ def reap_all_workers
390
+ begin
391
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
392
+ wpid or return
393
+ if reexec_pid == wpid
394
+ logger.error "reaped #{status.inspect} exec()-ed"
395
+ self.reexec_pid = 0
396
+ self.pid = pid.chomp('.oldbin') if pid
397
+ proc_name 'master'
398
+ else
399
+ @before_murder_called.delete(wpid)
400
+ worker = @workers.delete(wpid) and worker.close rescue nil
401
+ m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
402
+ status.success? ? logger.info(m) : logger.error(m)
403
+ end
404
+ rescue Errno::ECHILD
405
+ break
406
+ end while true
407
+ end
408
+
409
+ # reexecutes the START_CTX with a new binary
410
+ def reexec
411
+ if reexec_pid > 0
412
+ begin
413
+ Process.kill(0, reexec_pid)
414
+ logger.error "reexec-ed child already running PID:#{reexec_pid}"
415
+ return
416
+ rescue Errno::ESRCH
417
+ self.reexec_pid = 0
418
+ end
419
+ end
420
+
421
+ if pid
422
+ old_pid = "#{pid}.oldbin"
423
+ begin
424
+ self.pid = old_pid # clear the path for a new pid file
425
+ rescue ArgumentError
426
+ logger.error "old PID:#{valid_pid?(old_pid)} running with " \
427
+ "existing pid=#{old_pid}, refusing rexec"
428
+ return
429
+ rescue => e
430
+ logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
431
+ return
432
+ end
433
+ end
434
+
435
+ self.reexec_pid = fork do
436
+ listener_fds = {}
437
+ LISTENERS.each do |sock|
438
+ sock.close_on_exec = false
439
+ listener_fds[sock.fileno] = sock
440
+ end
441
+ ENV['UNICORN_FD'] = listener_fds.keys.join(',')
442
+ Dir.chdir(START_CTX[:cwd])
443
+ cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
444
+
445
+ # avoid leaking FDs we don't know about, but let before_exec
446
+ # unset FD_CLOEXEC, if anything else in the app eventually
447
+ # relies on FD inheritence.
448
+ (3..1024).each do |io|
449
+ next if listener_fds.include?(io)
450
+ io = IO.for_fd(io) rescue next
451
+ io.autoclose = false
452
+ io.close_on_exec = true
453
+ end
454
+
455
+ # exec(command, hash) works in at least 1.9.1+, but will only be
456
+ # required in 1.9.4/2.0.0 at earliest.
457
+ cmd << listener_fds
458
+ logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
459
+ before_exec.call(self)
460
+ exec(*cmd)
461
+ end
462
+ proc_name 'master (old)'
463
+ end
464
+
465
+ # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
466
+ def murder_lazy_workers
467
+ next_sleep = @timeout - 1
468
+ now = time_now.to_i
469
+ @workers.dup.each_pair do |wpid, worker|
470
+ tick = worker.tick
471
+ 0 == tick and next # skip workers that haven't processed any clients
472
+ diff = now - tick
473
+ tmp = @timeout - diff
474
+ if tmp >= 0
475
+ next_sleep > tmp and next_sleep = tmp
476
+ next
477
+ end
478
+ next_sleep = 0
479
+ logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
480
+ "(#{diff}s > #{@timeout}s), killing"
481
+ # Ensure we call before_murder only once
482
+ before_murder.call(self, worker, wpid) if before_murder and @before_murder_called.add?(wpid)
483
+ kill_worker(:KILL, wpid) # take no prisoners for timeout violations
484
+ end
485
+ next_sleep <= 0 ? 1 : next_sleep
486
+ end
487
+
488
+ def after_fork_internal
489
+ @self_pipe.each(&:close).clear # this is master-only, now
490
+ @ready_pipe.close if @ready_pipe
491
+ Unicorn::Configurator::RACKUP.clear
492
+ @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
493
+
494
+ srand # http://redmine.ruby-lang.org/issues/4338
495
+
496
+ # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
497
+ # dying workers can recycle pids
498
+ OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
499
+ end
500
+
501
+ def spawn_missing_workers
502
+ worker_nr = -1
503
+ until (worker_nr += 1) == @worker_processes
504
+ @workers.value?(worker_nr) and next
505
+ worker = Unicorn::Worker.new(worker_nr)
506
+ before_fork.call(self, worker)
507
+ if pid = fork
508
+ @workers[pid] = worker
509
+ worker.atfork_parent
510
+ else
511
+ after_fork_internal
512
+ worker_loop(worker)
513
+ exit
514
+ end
515
+ end
516
+ rescue => e
517
+ @logger.error(e) rescue nil
518
+ exit!
519
+ end
520
+
521
+ def maintain_worker_count
522
+ (off = @workers.size - worker_processes) == 0 and return
523
+ off < 0 and return spawn_missing_workers
524
+ @workers.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
525
+ end
526
+
527
+ # if we get any error, try to write something back to the client
528
+ # assuming we haven't closed the socket, but don't get hung up
529
+ # if the socket is already closed or broken. We'll always ensure
530
+ # the socket is closed at the end of this function
531
+ def handle_error(client, exception, env)
532
+ code = case exception
533
+ when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::ENOTCONN
534
+ Unicorn.log_error(@logger, "client disconnected #{env.inspect}", exception)
535
+ # client disconnected on us and there's nothing we can do
536
+ when Unicorn::RequestURITooLongError
537
+ 414
538
+ when Unicorn::RequestEntityTooLargeError
539
+ 413
540
+ when Unicorn::HttpParserError # try to tell the client they're bad
541
+ 400
542
+ else
543
+ Unicorn.log_error(@logger, "app error", exception)
544
+ 500
545
+ end
546
+ if code
547
+ client.kgio_trywrite(err_response(code, @request.response_start_sent))
548
+ end
549
+ client.close
550
+ rescue
551
+ end
552
+
553
+ def e100_response_write(client, env)
554
+ # We use String#freeze to avoid allocations under Ruby 2.1+
555
+ # Not many users hit this code path, so it's better to reduce the
556
+ # constant table sizes even for 1.9.3-2.0 users who'll hit extra
557
+ # allocations here.
558
+ client.write(@request.response_start_sent ?
559
+ "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
560
+ "HTTP/1.1 100 Continue\r\n\r\n".freeze)
561
+ env.delete('HTTP_EXPECT'.freeze)
562
+ end
563
+
564
+ # once a client is accepted, it is processed in its entirety here
565
+ # in 3 easy steps: read request, call app, write app response
566
+ def process_client(client)
567
+ status, headers, body = @app.call(env = @request.read(client))
568
+ return if @request.hijacked?
569
+
570
+ if 100 == status.to_i
571
+ e100_response_write(client, env)
572
+ status, headers, body = @app.call(env)
573
+ return if @request.hijacked?
574
+ end
575
+ @request.headers? or headers = nil
576
+ http_response_write(client, status, headers, body,
577
+ @request.response_start_sent)
578
+ unless client.closed? # rack.hijack may've close this for us
579
+ client.shutdown # in case of fork() in Rack app
580
+ client.close # flush and uncork socket immediately, no keepalive
581
+ end
582
+ rescue => exception
583
+ handle_error(client, exception, env)
584
+ end
585
+
586
+ EXIT_SIGS = [ :QUIT, :TERM, :INT ]
587
+ WORKER_QUEUE_SIGS = QUEUE_SIGS - EXIT_SIGS
588
+
589
+ def nuke_listeners!(readers)
590
+ # only called from the worker, ordering is important here
591
+ tmp = readers.dup
592
+ readers.replace([false]) # ensure worker does not continue ASAP
593
+ tmp.each { |io| io.close rescue nil } # break out of IO.select
594
+ end
595
+
596
+ # gets rid of stuff the worker has no business keeping track of
597
+ # to free some resources and drops all sig handlers.
598
+ # traps for USR1, USR2, and HUP may be set in the after_fork Proc
599
+ # by the user.
600
+ def init_worker_process(worker)
601
+ worker.atfork_child
602
+ # we'll re-trap :QUIT later for graceful shutdown iff we accept clients
603
+ EXIT_SIGS.each { |sig| trap(sig) { exit!(0) } }
604
+ exit!(0) if (@sig_queue & EXIT_SIGS)[0]
605
+ WORKER_QUEUE_SIGS.each { |sig| trap(sig, nil) }
606
+ trap(:CHLD, 'DEFAULT')
607
+ @sig_queue.clear
608
+ proc_name "worker[#{worker.nr}]"
609
+ START_CTX.clear
610
+ @workers.clear
611
+
612
+ after_fork.call(self, worker) # can drop perms and create listeners
613
+ LISTENERS.each { |sock| sock.close_on_exec = true }
614
+
615
+ worker.user(*user) if user.kind_of?(Array) && ! worker.switched
616
+ self.timeout /= 2.0 # halve it for select()
617
+ @config = nil
618
+ build_app! unless preload_app
619
+ @after_fork = @listener_opts = @orig_app = nil
620
+ readers = LISTENERS.dup
621
+ readers << worker
622
+ trap(:QUIT) { nuke_listeners!(readers) }
623
+ readers
624
+ end
625
+
626
+ def reopen_worker_logs(worker_nr)
627
+ logger.info "worker=#{worker_nr} reopening logs..."
628
+ Unicorn::Util.reopen_logs
629
+ logger.info "worker=#{worker_nr} done reopening logs"
630
+ rescue => e
631
+ logger.error(e) rescue nil
632
+ exit!(77) # EX_NOPERM in sysexits.h
633
+ end
634
+
635
+ # runs inside each forked worker, this sits around and waits
636
+ # for connections and doesn't die until the parent dies (or is
637
+ # given a INT, QUIT, or TERM signal)
638
+ def worker_loop(worker)
639
+ ppid = master_pid
640
+ readers = init_worker_process(worker)
641
+ nr = 0 # this becomes negative if we need to reopen logs
642
+
643
+ # this only works immediately if the master sent us the signal
644
+ # (which is the normal case)
645
+ trap(:USR1) { nr = -65536 }
646
+
647
+ ready = readers.dup
648
+ @logger.info "worker=#{worker.nr} ready"
649
+
650
+ begin
651
+ nr < 0 and reopen_worker_logs(worker.nr)
652
+ nr = 0
653
+ worker.tick = time_now.to_i
654
+ tmp = ready.dup
655
+ while sock = tmp.shift
656
+ # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
657
+ # but that will return false
658
+ if client = sock.kgio_tryaccept
659
+ process_client(client)
660
+ nr += 1
661
+ worker.tick = time_now.to_i
662
+ end
663
+ break if nr < 0
664
+ end
665
+
666
+ # make the following bet: if we accepted clients this round,
667
+ # we're probably reasonably busy, so avoid calling select()
668
+ # and do a speculative non-blocking accept() on ready listeners
669
+ # before we sleep again in select().
670
+ unless nr == 0
671
+ tmp = ready.dup
672
+ redo
673
+ end
674
+
675
+ ppid == Process.ppid or return
676
+
677
+ # timeout used so we can detect parent death:
678
+ worker.tick = time_now.to_i
679
+ ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
680
+ rescue => e
681
+ redo if nr < 0 && readers[0]
682
+ Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
683
+ end while readers[0]
684
+ end
685
+
686
+ # delivers a signal to a worker and fails gracefully if the worker
687
+ # is no longer running.
688
+ def kill_worker(signal, wpid)
689
+ Process.kill(signal, wpid)
690
+ rescue Errno::ESRCH
691
+ @before_murder_called.delete(wpid)
692
+ worker = @workers.delete(wpid) and worker.close rescue nil
693
+ end
694
+
695
+ # delivers a signal to each worker
696
+ def kill_each_worker(signal)
697
+ @workers.keys.each { |wpid| kill_worker(signal, wpid) }
698
+ end
699
+
700
+ def soft_kill_each_worker(signal)
701
+ @workers.each_value { |worker| worker.soft_kill(signal) }
702
+ end
703
+
704
+ # unlinks a PID file at given +path+ if it contains the current PID
705
+ # still potentially racy without locking the directory (which is
706
+ # non-portable and may interact badly with other programs), but the
707
+ # window for hitting the race condition is small
708
+ def unlink_pid_safe(path)
709
+ (File.read(path).to_i == $$ and File.unlink(path)) rescue nil
710
+ end
711
+
712
+ # returns a PID if a given path contains a non-stale PID file,
713
+ # nil otherwise.
714
+ def valid_pid?(path)
715
+ wpid = File.read(path).to_i
716
+ wpid <= 0 and return
717
+ Process.kill(0, wpid)
718
+ wpid
719
+ rescue Errno::EPERM
720
+ logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
721
+ nil
722
+ rescue Errno::ESRCH, Errno::ENOENT
723
+ # don't unlink stale pid files, racy without non-portable locking...
724
+ end
725
+
726
+ def load_config!
727
+ loaded_app = app
728
+ logger.info "reloading config_file=#{config.config_file}"
729
+ config[:listeners].replace(@init_listeners)
730
+ config.reload
731
+ config.commit!(self)
732
+ soft_kill_each_worker(:QUIT)
733
+ Unicorn::Util.reopen_logs
734
+ self.app = orig_app
735
+ build_app! if preload_app
736
+ logger.info "done reloading config_file=#{config.config_file}"
737
+ rescue StandardError, LoadError, SyntaxError => e
738
+ Unicorn.log_error(@logger,
739
+ "error reloading config_file=#{config.config_file}", e)
740
+ self.app = loaded_app
741
+ end
742
+
743
+ # returns an array of string names for the given listener array
744
+ def listener_names(listeners = LISTENERS)
745
+ listeners.map { |io| sock_name(io) }
746
+ end
747
+
748
+ def build_app!
749
+ if app.respond_to?(:arity) && app.arity == 0
750
+ if defined?(Gem) && Gem.respond_to?(:refresh)
751
+ logger.info "Refreshing Gem list"
752
+ Gem.refresh
753
+ end
754
+ self.app = app.call
755
+ end
756
+ end
757
+
758
+ def proc_name(tag)
759
+ $0 = ([ File.basename(START_CTX[0]), tag
760
+ ]).concat(START_CTX[:argv]).join(' ')
761
+ end
762
+
763
+ def redirect_io(io, path)
764
+ File.open(path, 'ab') { |fp| io.reopen(fp) } if path
765
+ io.sync = true
766
+ end
767
+
768
+ def inherit_listeners!
769
+ # inherit sockets from parents, they need to be plain Socket objects
770
+ # before they become Kgio::UNIXServer or Kgio::TCPServer
771
+ inherited = ENV['UNICORN_FD'].to_s.split(',').map do |fd|
772
+ io = Socket.for_fd(fd.to_i)
773
+ set_server_sockopt(io, listener_opts[sock_name(io)])
774
+ io.autoclose = false
775
+ logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
776
+ server_cast(io)
777
+ end
778
+
779
+ config_listeners = config[:listeners].dup
780
+ LISTENERS.replace(inherited)
781
+
782
+ # we start out with generic Socket objects that get cast to either
783
+ # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
784
+ # objects share the same OS-level file descriptor as the higher-level
785
+ # *Server objects; we need to prevent Socket objects from being
786
+ # garbage-collected
787
+ config_listeners -= listener_names
788
+ if config_listeners.empty? && LISTENERS.empty?
789
+ config_listeners << Unicorn::Const::DEFAULT_LISTEN
790
+ @init_listeners << Unicorn::Const::DEFAULT_LISTEN
791
+ START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
792
+ end
793
+ NEW_LISTENERS.replace(config_listeners)
794
+ end
795
+
796
+ # call only after calling inherit_listeners!
797
+ # This binds any listeners we did NOT inherit from the parent
798
+ def bind_new_listeners!
799
+ NEW_LISTENERS.each { |addr| listen(addr) }
800
+ raise ArgumentError, "no listeners" if LISTENERS.empty?
801
+ NEW_LISTENERS.clear
802
+ end
803
+
804
+ # try to use the monotonic clock in Ruby >= 2.1, it is immune to clock
805
+ # offset adjustments and generates less garbage (Float vs Time object)
806
+ begin
807
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
808
+ def time_now
809
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
810
+ end
811
+ rescue NameError, NoMethodError
812
+ def time_now # Ruby <= 2.0
813
+ Time.now
814
+ end
815
+ end
816
+ end