unicorn 4.8.0 → 6.1.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.
- checksums.yaml +7 -0
- data/.document +0 -1
- data/.gitattributes +5 -0
- data/.gitignore +2 -2
- data/.manifest +17 -21
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +7 -7
- data/CONTRIBUTORS +6 -2
- data/DESIGN +2 -4
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +23 -6
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +138 -92
- data/HACKING +10 -32
- data/ISSUES +80 -19
- data/KNOWN_ISSUES +18 -18
- data/LATEST +23 -27
- data/LICENSE +2 -2
- data/Links +24 -22
- data/NEWS +823 -0
- data/PHILOSOPHY +0 -6
- data/README +49 -48
- data/Rakefile +0 -44
- data/SIGNALS +2 -2
- data/Sandbox +11 -10
- data/TODO +0 -2
- data/TUNING +30 -9
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +4 -2
- data/bin/unicorn_rails +3 -3
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +14 -14
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +14 -6
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +22 -5
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -44
- data/ext/unicorn_http/extconf.rb +32 -5
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +3 -2
- data/ext/unicorn_http/unicorn_http.c +926 -638
- data/ext/unicorn_http/unicorn_http.rl +159 -170
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +110 -46
- data/lib/unicorn/const.rb +2 -25
- data/lib/unicorn/http_request.rb +110 -31
- data/lib/unicorn/http_response.rb +17 -31
- data/lib/unicorn/http_server.rb +272 -180
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +16 -6
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +58 -78
- data/lib/unicorn/stream_input.rb +8 -7
- data/lib/unicorn/tee_input.rb +16 -11
- data/lib/unicorn/tmpio.rb +10 -6
- data/lib/unicorn/util.rb +5 -4
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +36 -23
- data/lib/unicorn.rb +64 -44
- data/man/man1/unicorn.1 +124 -122
- data/man/man1/unicorn_rails.1 +113 -127
- data/t/.gitignore +0 -1
- data/t/GNUmakefile +3 -80
- data/t/README +4 -4
- data/t/t0002-parser-error.sh +3 -3
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +4 -3
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +74 -20
- data/test/test_helper.rb +42 -33
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +49 -19
- data/test/unit/test_http_parser_ng.rb +98 -115
- data/test/unit/test_request.rb +11 -11
- data/test/unit/test_response.rb +31 -19
- data/test/unit/test_server.rb +89 -15
- data/test/unit/test_signals.rb +9 -9
- data/test/unit/test_socket_helper.rb +16 -12
- data/test/unit/test_tee_input.rb +10 -0
- data/test/unit/test_upload.rb +10 -15
- data/test/unit/test_util.rb +32 -6
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +28 -23
- data/unicorn_1 +1 -0
- data/unicorn_rails_1 +1 -0
- metadata +68 -134
- data/.wrongdoc.yml +0 -10
- data/ChangeLog +0 -4861
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -178
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/examples/git.ru +0 -13
- data/lib/unicorn/app/exec_cgi.rb +0 -154
- data/lib/unicorn/app/inetd.rb +0 -109
- data/lib/unicorn/ssl_client.rb +0 -11
- data/lib/unicorn/ssl_configurator.rb +0 -104
- data/lib/unicorn/ssl_server.rb +0 -42
- data/local.mk.sample +0 -59
- data/script/isolate_for_tests +0 -32
- data/t/hijack.ru +0 -42
- data/t/sslgen.sh +0 -71
- data/t/t0016-trust-x-forwarded-false.sh +0 -30
- data/t/t0017-trust-x-forwarded-true.sh +0 -30
- data/t/t0200-rack-hijack.sh +0 -27
- data/t/t0600-https-server-basic.sh +0 -48
- data/test/unit/test_http_parser_xftrust.rb +0 -38
- data/test/unit/test_sni_hostnames.rb +0 -47
data/lib/unicorn/http_server.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
|
-
require "unicorn/ssl_server"
|
|
3
2
|
|
|
4
3
|
# This is the process manager of Unicorn. This manages worker
|
|
5
4
|
# processes which in turn handle the I/O and application process.
|
|
@@ -7,51 +6,30 @@ require "unicorn/ssl_server"
|
|
|
7
6
|
# forked worker children.
|
|
8
7
|
#
|
|
9
8
|
# Users do not need to know the internals of this class, but reading the
|
|
10
|
-
# {source}[
|
|
11
|
-
# is education for programmers wishing to learn how
|
|
12
|
-
# See Unicorn::Configurator for information on how to configure
|
|
9
|
+
# {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
|
|
10
|
+
# is education for programmers wishing to learn how unicorn works.
|
|
11
|
+
# See Unicorn::Configurator for information on how to configure unicorn.
|
|
13
12
|
class Unicorn::HttpServer
|
|
14
13
|
# :stopdoc:
|
|
15
|
-
attr_accessor :app, :
|
|
14
|
+
attr_accessor :app, :timeout, :worker_processes,
|
|
16
15
|
:before_fork, :after_fork, :before_exec,
|
|
17
16
|
:listener_opts, :preload_app,
|
|
18
|
-
:
|
|
19
|
-
:
|
|
17
|
+
:orig_app, :config, :ready_pipe, :user,
|
|
18
|
+
:default_middleware, :early_hints
|
|
19
|
+
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
|
20
20
|
|
|
21
21
|
attr_reader :pid, :logger
|
|
22
22
|
include Unicorn::SocketHelper
|
|
23
23
|
include Unicorn::HttpResponse
|
|
24
|
-
include Unicorn::SSLServer
|
|
25
|
-
|
|
26
|
-
# backwards compatibility with 1.x
|
|
27
|
-
Worker = Unicorn::Worker
|
|
28
24
|
|
|
29
25
|
# all bound listener sockets
|
|
26
|
+
# note: this is public used by raindrops, but not recommended for use
|
|
27
|
+
# in new projects
|
|
30
28
|
LISTENERS = []
|
|
31
29
|
|
|
32
30
|
# listeners we have yet to bind
|
|
33
31
|
NEW_LISTENERS = []
|
|
34
32
|
|
|
35
|
-
# This hash maps PIDs to Workers
|
|
36
|
-
WORKERS = {}
|
|
37
|
-
|
|
38
|
-
# We use SELF_PIPE differently in the master and worker processes:
|
|
39
|
-
#
|
|
40
|
-
# * The master process never closes or reinitializes this once
|
|
41
|
-
# initialized. Signal handlers in the master process will write to
|
|
42
|
-
# it to wake up the master from IO.select in exactly the same manner
|
|
43
|
-
# djb describes in http://cr.yp.to/docs/selfpipe.html
|
|
44
|
-
#
|
|
45
|
-
# * The workers immediately close the pipe they inherit. See the
|
|
46
|
-
# Unicorn::Worker class for the pipe workers use.
|
|
47
|
-
SELF_PIPE = []
|
|
48
|
-
|
|
49
|
-
# signal queue used for self-piping
|
|
50
|
-
SIG_QUEUE = []
|
|
51
|
-
|
|
52
|
-
# list of signals we care about and trap in master.
|
|
53
|
-
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
|
54
|
-
|
|
55
33
|
# :startdoc:
|
|
56
34
|
# We populate this at startup so we can figure out how to reexecute
|
|
57
35
|
# and upgrade the currently running instance of Unicorn
|
|
@@ -60,7 +38,7 @@ class Unicorn::HttpServer
|
|
|
60
38
|
# or even different installations of the same applications without
|
|
61
39
|
# downtime. Keys of this constant Hash are described as follows:
|
|
62
40
|
#
|
|
63
|
-
# * 0 - the path to the unicorn
|
|
41
|
+
# * 0 - the path to the unicorn executable
|
|
64
42
|
# * :argv - a deep copy of the ARGV array the executable originally saw
|
|
65
43
|
# * :cwd - the working directory of the application, this is where
|
|
66
44
|
# you originally started Unicorn.
|
|
@@ -69,9 +47,9 @@ class Unicorn::HttpServer
|
|
|
69
47
|
# you can set the following in your Unicorn config file, HUP and then
|
|
70
48
|
# continue with the traditional USR2 + QUIT upgrade steps:
|
|
71
49
|
#
|
|
72
|
-
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/
|
|
50
|
+
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
|
|
73
51
|
START_CTX = {
|
|
74
|
-
:argv => ARGV.map
|
|
52
|
+
:argv => ARGV.map(&:dup),
|
|
75
53
|
0 => $0.dup,
|
|
76
54
|
}
|
|
77
55
|
# We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
|
|
@@ -91,8 +69,8 @@ class Unicorn::HttpServer
|
|
|
91
69
|
# incoming requests on the socket.
|
|
92
70
|
def initialize(app, options = {})
|
|
93
71
|
@app = app
|
|
94
|
-
@
|
|
95
|
-
|
|
72
|
+
@reexec_pid = 0
|
|
73
|
+
@default_middleware = true
|
|
96
74
|
options = options.dup
|
|
97
75
|
@ready_pipe = options.delete(:ready_pipe)
|
|
98
76
|
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
|
@@ -100,6 +78,20 @@ class Unicorn::HttpServer
|
|
|
100
78
|
self.config = Unicorn::Configurator.new(options)
|
|
101
79
|
self.listener_opts = {}
|
|
102
80
|
|
|
81
|
+
# We use @self_pipe differently in the master and worker processes:
|
|
82
|
+
#
|
|
83
|
+
# * The master process never closes or reinitializes this once
|
|
84
|
+
# initialized. Signal handlers in the master process will write to
|
|
85
|
+
# it to wake up the master from IO.select in exactly the same manner
|
|
86
|
+
# djb describes in https://cr.yp.to/docs/selfpipe.html
|
|
87
|
+
#
|
|
88
|
+
# * The workers immediately close the pipe they inherit. See the
|
|
89
|
+
# Unicorn::Worker class for the pipe workers use.
|
|
90
|
+
@self_pipe = []
|
|
91
|
+
@workers = {} # hash maps PIDs to Workers
|
|
92
|
+
@sig_queue = [] # signal queue used for self-piping
|
|
93
|
+
@pid = nil
|
|
94
|
+
|
|
103
95
|
# we try inheriting listeners first, so we bind them later.
|
|
104
96
|
# we don't write the pid file until we've bound listeners in case
|
|
105
97
|
# unicorn was started twice by mistake. Even though our #pid= method
|
|
@@ -111,7 +103,18 @@ class Unicorn::HttpServer
|
|
|
111
103
|
# monitoring tools may also rely on pid files existing before we
|
|
112
104
|
# attempt to connect to the listener(s)
|
|
113
105
|
config.commit!(self, :skip => [:listeners, :pid])
|
|
114
|
-
|
|
106
|
+
@orig_app = app
|
|
107
|
+
# list of signals we care about and trap in master.
|
|
108
|
+
@queue_sigs = [
|
|
109
|
+
:WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
|
110
|
+
|
|
111
|
+
@worker_data = if worker_data = ENV['UNICORN_WORKER']
|
|
112
|
+
worker_data = worker_data.split(',').map!(&:to_i)
|
|
113
|
+
worker_data[1] = worker_data.slice!(1..2).map do |i|
|
|
114
|
+
Kgio::Pipe.for_fd(i)
|
|
115
|
+
end
|
|
116
|
+
worker_data
|
|
117
|
+
end
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
# Runs the thing. Returns self so you can run join on it
|
|
@@ -119,12 +122,13 @@ class Unicorn::HttpServer
|
|
|
119
122
|
inherit_listeners!
|
|
120
123
|
# this pipe is used to wake us up from select(2) in #join when signals
|
|
121
124
|
# are trapped. See trap_deferred.
|
|
122
|
-
|
|
125
|
+
@self_pipe.replace(Unicorn.pipe)
|
|
126
|
+
@master_pid = @worker_data ? Process.ppid : $$
|
|
123
127
|
|
|
124
128
|
# setup signal handlers before writing pid file in case people get
|
|
125
129
|
# trigger happy and send signals as soon as the pid file exists.
|
|
126
130
|
# Note that signals don't actually get handled until the #join method
|
|
127
|
-
|
|
131
|
+
@queue_sigs.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
|
|
128
132
|
trap(:CHLD) { awaken_master }
|
|
129
133
|
|
|
130
134
|
# write pid early for Mongrel compatibility if we're not inheriting sockets
|
|
@@ -133,7 +137,6 @@ class Unicorn::HttpServer
|
|
|
133
137
|
# we upgrade and the upgrade breaks during preload_app==true && build_app!
|
|
134
138
|
self.pid = config[:pid]
|
|
135
139
|
|
|
136
|
-
self.master_pid = $$
|
|
137
140
|
build_app! if preload_app
|
|
138
141
|
bind_new_listeners!
|
|
139
142
|
|
|
@@ -146,7 +149,7 @@ class Unicorn::HttpServer
|
|
|
146
149
|
def listeners=(listeners)
|
|
147
150
|
cur_names, dead_names = [], []
|
|
148
151
|
listener_names.each do |name|
|
|
149
|
-
if
|
|
152
|
+
if name.start_with?('/')
|
|
150
153
|
# mark unlinked sockets as dead so we can rebind them
|
|
151
154
|
(File.socket?(name) ? cur_names : dead_names) << name
|
|
152
155
|
else
|
|
@@ -158,9 +161,6 @@ class Unicorn::HttpServer
|
|
|
158
161
|
|
|
159
162
|
LISTENERS.delete_if do |io|
|
|
160
163
|
if dead_names.include?(sock_name(io))
|
|
161
|
-
IO_PURGATORY.delete_if do |pio|
|
|
162
|
-
pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
|
|
163
|
-
end
|
|
164
164
|
(io.close rescue nil).nil? # true
|
|
165
165
|
else
|
|
166
166
|
set_server_sockopt(io, listener_opts[sock_name(io)])
|
|
@@ -198,7 +198,7 @@ class Unicorn::HttpServer
|
|
|
198
198
|
if path
|
|
199
199
|
if x = valid_pid?(path)
|
|
200
200
|
return path if pid && path == pid && x == $$
|
|
201
|
-
if x == reexec_pid && pid
|
|
201
|
+
if x == @reexec_pid && pid.end_with?('.oldbin')
|
|
202
202
|
logger.warn("will not set pid=#{path} while reexec-ed "\
|
|
203
203
|
"child is running PID:#{x}")
|
|
204
204
|
return
|
|
@@ -241,7 +241,7 @@ class Unicorn::HttpServer
|
|
|
241
241
|
begin
|
|
242
242
|
io = bind_listen(address, opt)
|
|
243
243
|
unless Kgio::TCPServer === io || Kgio::UNIXServer === io
|
|
244
|
-
|
|
244
|
+
io.autoclose = false
|
|
245
245
|
io = server_cast(io)
|
|
246
246
|
end
|
|
247
247
|
logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
|
|
@@ -267,21 +267,25 @@ class Unicorn::HttpServer
|
|
|
267
267
|
# is signalling us too often.
|
|
268
268
|
def join
|
|
269
269
|
respawn = true
|
|
270
|
-
last_check =
|
|
270
|
+
last_check = time_now
|
|
271
271
|
|
|
272
272
|
proc_name 'master'
|
|
273
273
|
logger.info "master process ready" # test_exec.rb relies on this message
|
|
274
274
|
if @ready_pipe
|
|
275
|
-
|
|
275
|
+
begin
|
|
276
|
+
@ready_pipe.syswrite($$.to_s)
|
|
277
|
+
rescue => e
|
|
278
|
+
logger.warn("grandparent died too soon?: #{e.message} (#{e.class})")
|
|
279
|
+
end
|
|
276
280
|
@ready_pipe = @ready_pipe.close rescue nil
|
|
277
281
|
end
|
|
278
282
|
begin
|
|
279
283
|
reap_all_workers
|
|
280
|
-
case
|
|
284
|
+
case @sig_queue.shift
|
|
281
285
|
when nil
|
|
282
286
|
# avoid murdering workers after our master process (or the
|
|
283
287
|
# machine) comes out of suspend/hibernation
|
|
284
|
-
if (last_check + @timeout) >= (last_check =
|
|
288
|
+
if (last_check + @timeout) >= (last_check = time_now)
|
|
285
289
|
sleep_time = murder_lazy_workers
|
|
286
290
|
else
|
|
287
291
|
sleep_time = @timeout/2.0 + 1
|
|
@@ -302,13 +306,13 @@ class Unicorn::HttpServer
|
|
|
302
306
|
when :USR2 # exec binary, stay alive in case something went wrong
|
|
303
307
|
reexec
|
|
304
308
|
when :WINCH
|
|
305
|
-
if
|
|
309
|
+
if $stdin.tty?
|
|
310
|
+
logger.info "SIGWINCH ignored because we're not daemonized"
|
|
311
|
+
else
|
|
306
312
|
respawn = false
|
|
307
313
|
logger.info "gracefully stopping all workers"
|
|
308
314
|
soft_kill_each_worker(:QUIT)
|
|
309
315
|
self.worker_processes = 0
|
|
310
|
-
else
|
|
311
|
-
logger.info "SIGWINCH ignored because we're not daemonized"
|
|
312
316
|
end
|
|
313
317
|
when :TTIN
|
|
314
318
|
respawn = true
|
|
@@ -335,8 +339,8 @@ class Unicorn::HttpServer
|
|
|
335
339
|
# Terminates all workers, but does not exit master process
|
|
336
340
|
def stop(graceful = true)
|
|
337
341
|
self.listeners = []
|
|
338
|
-
limit =
|
|
339
|
-
until
|
|
342
|
+
limit = time_now + timeout
|
|
343
|
+
until @workers.empty? || time_now > limit
|
|
340
344
|
if graceful
|
|
341
345
|
soft_kill_each_worker(:QUIT)
|
|
342
346
|
else
|
|
@@ -365,14 +369,6 @@ class Unicorn::HttpServer
|
|
|
365
369
|
Unicorn::TeeInput.client_body_buffer_size = bytes
|
|
366
370
|
end
|
|
367
371
|
|
|
368
|
-
def trust_x_forwarded
|
|
369
|
-
Unicorn::HttpParser.trust_x_forwarded?
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
def trust_x_forwarded=(bool)
|
|
373
|
-
Unicorn::HttpParser.trust_x_forwarded = bool
|
|
374
|
-
end
|
|
375
|
-
|
|
376
372
|
def check_client_connection
|
|
377
373
|
Unicorn::HttpRequest.check_client_connection
|
|
378
374
|
end
|
|
@@ -385,12 +381,17 @@ class Unicorn::HttpServer
|
|
|
385
381
|
|
|
386
382
|
# wait for a signal hander to wake us up and then consume the pipe
|
|
387
383
|
def master_sleep(sec)
|
|
388
|
-
|
|
389
|
-
|
|
384
|
+
@self_pipe[0].wait(sec) or return
|
|
385
|
+
# 11 bytes is the maximum string length which can be embedded within
|
|
386
|
+
# the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
|
|
387
|
+
# Most reads are only one byte here and uncommon, so it's not worth a
|
|
388
|
+
# persistent buffer, either:
|
|
389
|
+
@self_pipe[0].kgio_tryread(11)
|
|
390
390
|
end
|
|
391
391
|
|
|
392
392
|
def awaken_master
|
|
393
|
-
|
|
393
|
+
return if $$ != @master_pid
|
|
394
|
+
@self_pipe[1].kgio_trywrite('.') # wakeup master process from select
|
|
394
395
|
end
|
|
395
396
|
|
|
396
397
|
# reaps all unreaped workers
|
|
@@ -398,15 +399,14 @@ class Unicorn::HttpServer
|
|
|
398
399
|
begin
|
|
399
400
|
wpid, status = Process.waitpid2(-1, Process::WNOHANG)
|
|
400
401
|
wpid or return
|
|
401
|
-
if reexec_pid == wpid
|
|
402
|
+
if @reexec_pid == wpid
|
|
402
403
|
logger.error "reaped #{status.inspect} exec()-ed"
|
|
403
|
-
|
|
404
|
+
@reexec_pid = 0
|
|
404
405
|
self.pid = pid.chomp('.oldbin') if pid
|
|
405
406
|
proc_name 'master'
|
|
406
407
|
else
|
|
407
|
-
worker =
|
|
408
|
-
|
|
409
|
-
status.success? ? logger.info(m) : logger.error(m)
|
|
408
|
+
worker = @workers.delete(wpid) and worker.close rescue nil
|
|
409
|
+
@after_worker_exit.call(self, worker, status)
|
|
410
410
|
end
|
|
411
411
|
rescue Errno::ECHILD
|
|
412
412
|
break
|
|
@@ -415,13 +415,13 @@ class Unicorn::HttpServer
|
|
|
415
415
|
|
|
416
416
|
# reexecutes the START_CTX with a new binary
|
|
417
417
|
def reexec
|
|
418
|
-
if reexec_pid > 0
|
|
418
|
+
if @reexec_pid > 0
|
|
419
419
|
begin
|
|
420
|
-
Process.kill(0, reexec_pid)
|
|
421
|
-
logger.error "reexec-ed child already running PID
|
|
420
|
+
Process.kill(0, @reexec_pid)
|
|
421
|
+
logger.error "reexec-ed child already running PID:#@reexec_pid"
|
|
422
422
|
return
|
|
423
423
|
rescue Errno::ESRCH
|
|
424
|
-
|
|
424
|
+
@reexec_pid = 0
|
|
425
425
|
end
|
|
426
426
|
end
|
|
427
427
|
|
|
@@ -439,15 +439,8 @@ class Unicorn::HttpServer
|
|
|
439
439
|
end
|
|
440
440
|
end
|
|
441
441
|
|
|
442
|
-
|
|
443
|
-
listener_fds =
|
|
444
|
-
LISTENERS.each do |sock|
|
|
445
|
-
# IO#close_on_exec= will be available on any future version of
|
|
446
|
-
# Ruby that sets FD_CLOEXEC by default on new file descriptors
|
|
447
|
-
# ref: http://redmine.ruby-lang.org/issues/5041
|
|
448
|
-
sock.close_on_exec = false if sock.respond_to?(:close_on_exec=)
|
|
449
|
-
listener_fds[sock.fileno] = sock
|
|
450
|
-
end
|
|
442
|
+
@reexec_pid = fork do
|
|
443
|
+
listener_fds = listener_sockets
|
|
451
444
|
ENV['UNICORN_FD'] = listener_fds.keys.join(',')
|
|
452
445
|
Dir.chdir(START_CTX[:cwd])
|
|
453
446
|
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
|
@@ -455,16 +448,11 @@ class Unicorn::HttpServer
|
|
|
455
448
|
# avoid leaking FDs we don't know about, but let before_exec
|
|
456
449
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
|
457
450
|
# relies on FD inheritence.
|
|
458
|
-
(
|
|
459
|
-
next if listener_fds.include?(io)
|
|
460
|
-
io = IO.for_fd(io) rescue next
|
|
461
|
-
prevent_autoclose(io)
|
|
462
|
-
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
|
463
|
-
end
|
|
451
|
+
close_sockets_on_exec(listener_fds)
|
|
464
452
|
|
|
465
453
|
# exec(command, hash) works in at least 1.9.1+, but will only be
|
|
466
454
|
# required in 1.9.4/2.0.0 at earliest.
|
|
467
|
-
cmd << listener_fds
|
|
455
|
+
cmd << listener_fds
|
|
468
456
|
logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
|
469
457
|
before_exec.call(self)
|
|
470
458
|
exec(*cmd)
|
|
@@ -472,11 +460,45 @@ class Unicorn::HttpServer
|
|
|
472
460
|
proc_name 'master (old)'
|
|
473
461
|
end
|
|
474
462
|
|
|
463
|
+
def worker_spawn(worker)
|
|
464
|
+
listener_fds = listener_sockets
|
|
465
|
+
env = {}
|
|
466
|
+
env['UNICORN_FD'] = listener_fds.keys.join(',')
|
|
467
|
+
|
|
468
|
+
listener_fds[worker.to_io.fileno] = worker.to_io
|
|
469
|
+
listener_fds[worker.master.fileno] = worker.master
|
|
470
|
+
|
|
471
|
+
worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
|
|
472
|
+
env['UNICORN_WORKER'] = worker_info.join(',')
|
|
473
|
+
|
|
474
|
+
close_sockets_on_exec(listener_fds)
|
|
475
|
+
|
|
476
|
+
Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def listener_sockets
|
|
480
|
+
listener_fds = {}
|
|
481
|
+
LISTENERS.each do |sock|
|
|
482
|
+
sock.close_on_exec = false
|
|
483
|
+
listener_fds[sock.fileno] = sock
|
|
484
|
+
end
|
|
485
|
+
listener_fds
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def close_sockets_on_exec(sockets)
|
|
489
|
+
(3..1024).each do |io|
|
|
490
|
+
next if sockets.include?(io)
|
|
491
|
+
io = IO.for_fd(io) rescue next
|
|
492
|
+
io.autoclose = false
|
|
493
|
+
io.close_on_exec = true
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
475
497
|
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
|
476
498
|
def murder_lazy_workers
|
|
477
499
|
next_sleep = @timeout - 1
|
|
478
|
-
now =
|
|
479
|
-
|
|
500
|
+
now = time_now.to_i
|
|
501
|
+
@workers.dup.each_pair do |wpid, worker|
|
|
480
502
|
tick = worker.tick
|
|
481
503
|
0 == tick and next # skip workers that haven't processed any clients
|
|
482
504
|
diff = now - tick
|
|
@@ -494,42 +516,50 @@ class Unicorn::HttpServer
|
|
|
494
516
|
end
|
|
495
517
|
|
|
496
518
|
def after_fork_internal
|
|
497
|
-
|
|
519
|
+
@self_pipe.each(&:close).clear # this is master-only, now
|
|
498
520
|
@ready_pipe.close if @ready_pipe
|
|
499
521
|
Unicorn::Configurator::RACKUP.clear
|
|
500
522
|
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
|
501
523
|
|
|
502
|
-
srand # http://redmine.ruby-lang.org/issues/4338
|
|
503
|
-
|
|
504
524
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
|
505
525
|
# dying workers can recycle pids
|
|
506
526
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
|
507
527
|
end
|
|
508
528
|
|
|
509
529
|
def spawn_missing_workers
|
|
530
|
+
if @worker_data
|
|
531
|
+
worker = Unicorn::Worker.new(*@worker_data)
|
|
532
|
+
after_fork_internal
|
|
533
|
+
worker_loop(worker)
|
|
534
|
+
exit
|
|
535
|
+
end
|
|
536
|
+
|
|
510
537
|
worker_nr = -1
|
|
511
538
|
until (worker_nr += 1) == @worker_processes
|
|
512
|
-
|
|
513
|
-
worker = Worker.new(worker_nr)
|
|
539
|
+
@workers.value?(worker_nr) and next
|
|
540
|
+
worker = Unicorn::Worker.new(worker_nr)
|
|
514
541
|
before_fork.call(self, worker)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
542
|
+
|
|
543
|
+
pid = @worker_exec ? worker_spawn(worker) : fork
|
|
544
|
+
|
|
545
|
+
unless pid
|
|
519
546
|
after_fork_internal
|
|
520
547
|
worker_loop(worker)
|
|
521
548
|
exit
|
|
522
549
|
end
|
|
550
|
+
|
|
551
|
+
@workers[pid] = worker
|
|
552
|
+
worker.atfork_parent
|
|
523
553
|
end
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
554
|
+
rescue => e
|
|
555
|
+
@logger.error(e) rescue nil
|
|
556
|
+
exit!
|
|
527
557
|
end
|
|
528
558
|
|
|
529
559
|
def maintain_worker_count
|
|
530
|
-
(off =
|
|
560
|
+
(off = @workers.size - worker_processes) == 0 and return
|
|
531
561
|
off < 0 and return spawn_missing_workers
|
|
532
|
-
|
|
562
|
+
@workers.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
|
|
533
563
|
end
|
|
534
564
|
|
|
535
565
|
# if we get any error, try to write something back to the client
|
|
@@ -554,42 +584,85 @@ class Unicorn::HttpServer
|
|
|
554
584
|
client.kgio_trywrite(err_response(code, @request.response_start_sent))
|
|
555
585
|
end
|
|
556
586
|
client.close
|
|
557
|
-
|
|
587
|
+
rescue
|
|
558
588
|
end
|
|
559
589
|
|
|
560
|
-
def
|
|
561
|
-
if @request.response_start_sent
|
|
562
|
-
|
|
590
|
+
def e103_response_write(client, headers)
|
|
591
|
+
response = if @request.response_start_sent
|
|
592
|
+
"103 Early Hints\r\n"
|
|
563
593
|
else
|
|
564
|
-
|
|
594
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
headers.each_pair do |k, vs|
|
|
598
|
+
next if !vs || vs.empty?
|
|
599
|
+
values = vs.to_s.split("\n".freeze)
|
|
600
|
+
values.each do |v|
|
|
601
|
+
response << "#{k}: #{v}\r\n"
|
|
602
|
+
end
|
|
565
603
|
end
|
|
604
|
+
response << "\r\n".freeze
|
|
605
|
+
response << "HTTP/1.1 ".freeze if @request.response_start_sent
|
|
606
|
+
client.write(response)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def e100_response_write(client, env)
|
|
610
|
+
# We use String#freeze to avoid allocations under Ruby 2.1+
|
|
611
|
+
# Not many users hit this code path, so it's better to reduce the
|
|
612
|
+
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
|
613
|
+
# allocations here.
|
|
614
|
+
client.write(@request.response_start_sent ?
|
|
615
|
+
"100 Continue\r\n\r\nHTTP/1.1 ".freeze :
|
|
616
|
+
"HTTP/1.1 100 Continue\r\n\r\n".freeze)
|
|
617
|
+
env.delete('HTTP_EXPECT'.freeze)
|
|
566
618
|
end
|
|
567
619
|
|
|
568
620
|
# once a client is accepted, it is processed in its entirety here
|
|
569
621
|
# in 3 easy steps: read request, call app, write app response
|
|
570
622
|
def process_client(client)
|
|
571
|
-
|
|
572
|
-
|
|
623
|
+
@request = Unicorn::HttpRequest.new
|
|
624
|
+
env = @request.read(client)
|
|
625
|
+
|
|
626
|
+
if early_hints
|
|
627
|
+
env["rack.early_hints"] = lambda do |headers|
|
|
628
|
+
e103_response_write(client, headers)
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
env["rack.after_reply"] = []
|
|
633
|
+
|
|
634
|
+
status, headers, body = @app.call(env)
|
|
573
635
|
|
|
574
|
-
|
|
575
|
-
client.write(expect_100_response)
|
|
576
|
-
env.delete(Unicorn::Const::HTTP_EXPECT)
|
|
577
|
-
status, headers, body = @app.call(env)
|
|
636
|
+
begin
|
|
578
637
|
return if @request.hijacked?
|
|
638
|
+
|
|
639
|
+
if 100 == status.to_i
|
|
640
|
+
e100_response_write(client, env)
|
|
641
|
+
status, headers, body = @app.call(env)
|
|
642
|
+
return if @request.hijacked?
|
|
643
|
+
end
|
|
644
|
+
@request.headers? or headers = nil
|
|
645
|
+
http_response_write(client, status, headers, body, @request)
|
|
646
|
+
ensure
|
|
647
|
+
body.respond_to?(:close) and body.close
|
|
579
648
|
end
|
|
580
|
-
|
|
581
|
-
http_response_write(client, status, headers, body,
|
|
582
|
-
@request.response_start_sent)
|
|
649
|
+
|
|
583
650
|
unless client.closed? # rack.hijack may've close this for us
|
|
584
651
|
client.shutdown # in case of fork() in Rack app
|
|
585
652
|
client.close # flush and uncork socket immediately, no keepalive
|
|
586
653
|
end
|
|
587
654
|
rescue => e
|
|
588
655
|
handle_error(client, e)
|
|
656
|
+
ensure
|
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
|
589
658
|
end
|
|
590
659
|
|
|
591
|
-
|
|
592
|
-
|
|
660
|
+
def nuke_listeners!(readers)
|
|
661
|
+
# only called from the worker, ordering is important here
|
|
662
|
+
tmp = readers.dup
|
|
663
|
+
readers.replace([false]) # ensure worker does not continue ASAP
|
|
664
|
+
tmp.each { |io| io.close rescue nil } # break out of IO.select
|
|
665
|
+
end
|
|
593
666
|
|
|
594
667
|
# gets rid of stuff the worker has no business keeping track of
|
|
595
668
|
# to free some resources and drops all sig handlers.
|
|
@@ -598,27 +671,26 @@ class Unicorn::HttpServer
|
|
|
598
671
|
def init_worker_process(worker)
|
|
599
672
|
worker.atfork_child
|
|
600
673
|
# we'll re-trap :QUIT later for graceful shutdown iff we accept clients
|
|
601
|
-
|
|
602
|
-
exit!(0)
|
|
603
|
-
|
|
674
|
+
exit_sigs = [ :QUIT, :TERM, :INT ]
|
|
675
|
+
exit_sigs.each { |sig| trap(sig) { exit!(0) } }
|
|
676
|
+
exit!(0) if (@sig_queue & exit_sigs)[0]
|
|
677
|
+
(@queue_sigs - exit_sigs).each { |sig| trap(sig, nil) }
|
|
604
678
|
trap(:CHLD, 'DEFAULT')
|
|
605
|
-
|
|
679
|
+
@sig_queue.clear
|
|
606
680
|
proc_name "worker[#{worker.nr}]"
|
|
607
681
|
START_CTX.clear
|
|
608
|
-
|
|
682
|
+
@workers.clear
|
|
609
683
|
|
|
610
684
|
after_fork.call(self, worker) # can drop perms and create listeners
|
|
611
|
-
LISTENERS.each { |sock| sock.
|
|
685
|
+
LISTENERS.each { |sock| sock.close_on_exec = true }
|
|
612
686
|
|
|
613
687
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
|
614
|
-
self.timeout /= 2.0 # halve it for select()
|
|
615
688
|
@config = nil
|
|
616
689
|
build_app! unless preload_app
|
|
617
|
-
ssl_enable!
|
|
618
690
|
@after_fork = @listener_opts = @orig_app = nil
|
|
619
691
|
readers = LISTENERS.dup
|
|
620
692
|
readers << worker
|
|
621
|
-
trap(:QUIT) { readers
|
|
693
|
+
trap(:QUIT) { nuke_listeners!(readers) }
|
|
622
694
|
readers
|
|
623
695
|
end
|
|
624
696
|
|
|
@@ -626,58 +698,55 @@ class Unicorn::HttpServer
|
|
|
626
698
|
logger.info "worker=#{worker_nr} reopening logs..."
|
|
627
699
|
Unicorn::Util.reopen_logs
|
|
628
700
|
logger.info "worker=#{worker_nr} done reopening logs"
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
701
|
+
false
|
|
702
|
+
rescue => e
|
|
703
|
+
logger.error(e) rescue nil
|
|
704
|
+
exit!(77) # EX_NOPERM in sysexits.h
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def prep_readers(readers)
|
|
708
|
+
wtr = Unicorn::Waiter.prep_readers(readers)
|
|
709
|
+
@timeout *= 500 # to milliseconds for epoll, but halved
|
|
710
|
+
wtr
|
|
711
|
+
rescue
|
|
712
|
+
require_relative 'select_waiter'
|
|
713
|
+
@timeout /= 2.0 # halved for IO.select
|
|
714
|
+
Unicorn::SelectWaiter.new
|
|
632
715
|
end
|
|
633
716
|
|
|
634
717
|
# runs inside each forked worker, this sits around and waits
|
|
635
718
|
# for connections and doesn't die until the parent dies (or is
|
|
636
719
|
# given a INT, QUIT, or TERM signal)
|
|
637
720
|
def worker_loop(worker)
|
|
638
|
-
ppid = master_pid
|
|
639
721
|
readers = init_worker_process(worker)
|
|
640
|
-
|
|
722
|
+
waiter = prep_readers(readers)
|
|
723
|
+
reopen = false
|
|
641
724
|
|
|
642
725
|
# this only works immediately if the master sent us the signal
|
|
643
726
|
# (which is the normal case)
|
|
644
|
-
trap(:USR1) {
|
|
727
|
+
trap(:USR1) { reopen = true }
|
|
645
728
|
|
|
646
729
|
ready = readers.dup
|
|
647
|
-
@
|
|
730
|
+
@after_worker_ready.call(self, worker)
|
|
648
731
|
|
|
649
732
|
begin
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
tmp = ready.dup
|
|
654
|
-
while sock = tmp.shift
|
|
733
|
+
reopen = reopen_worker_logs(worker.nr) if reopen
|
|
734
|
+
worker.tick = time_now.to_i
|
|
735
|
+
while sock = ready.shift
|
|
655
736
|
# Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
|
|
656
737
|
# but that will return false
|
|
657
738
|
if client = sock.kgio_tryaccept
|
|
658
739
|
process_client(client)
|
|
659
|
-
|
|
660
|
-
worker.tick = Time.now.to_i
|
|
740
|
+
worker.tick = time_now.to_i
|
|
661
741
|
end
|
|
662
|
-
break if
|
|
663
|
-
end
|
|
664
|
-
|
|
665
|
-
# make the following bet: if we accepted clients this round,
|
|
666
|
-
# we're probably reasonably busy, so avoid calling select()
|
|
667
|
-
# and do a speculative non-blocking accept() on ready listeners
|
|
668
|
-
# before we sleep again in select().
|
|
669
|
-
unless nr == 0
|
|
670
|
-
tmp = ready.dup
|
|
671
|
-
redo
|
|
742
|
+
break if reopen
|
|
672
743
|
end
|
|
673
744
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
worker.tick = Time.now.to_i
|
|
678
|
-
ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
|
|
745
|
+
# timeout so we can .tick and keep parent from SIGKILL-ing us
|
|
746
|
+
worker.tick = time_now.to_i
|
|
747
|
+
waiter.get_readers(ready, readers, @timeout)
|
|
679
748
|
rescue => e
|
|
680
|
-
redo if
|
|
749
|
+
redo if reopen && readers[0]
|
|
681
750
|
Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
|
|
682
751
|
end while readers[0]
|
|
683
752
|
end
|
|
@@ -686,17 +755,17 @@ class Unicorn::HttpServer
|
|
|
686
755
|
# is no longer running.
|
|
687
756
|
def kill_worker(signal, wpid)
|
|
688
757
|
Process.kill(signal, wpid)
|
|
689
|
-
|
|
690
|
-
|
|
758
|
+
rescue Errno::ESRCH
|
|
759
|
+
worker = @workers.delete(wpid) and worker.close rescue nil
|
|
691
760
|
end
|
|
692
761
|
|
|
693
762
|
# delivers a signal to each worker
|
|
694
763
|
def kill_each_worker(signal)
|
|
695
|
-
|
|
764
|
+
@workers.keys.each { |wpid| kill_worker(signal, wpid) }
|
|
696
765
|
end
|
|
697
766
|
|
|
698
767
|
def soft_kill_each_worker(signal)
|
|
699
|
-
|
|
768
|
+
@workers.each_value { |worker| worker.soft_kill(signal) }
|
|
700
769
|
end
|
|
701
770
|
|
|
702
771
|
# unlinks a PID file at given +path+ if it contains the current PID
|
|
@@ -714,11 +783,11 @@ class Unicorn::HttpServer
|
|
|
714
783
|
wpid <= 0 and return
|
|
715
784
|
Process.kill(0, wpid)
|
|
716
785
|
wpid
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
786
|
+
rescue Errno::EPERM
|
|
787
|
+
logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
|
|
788
|
+
nil
|
|
789
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
|
790
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
|
722
791
|
end
|
|
723
792
|
|
|
724
793
|
def load_config!
|
|
@@ -729,7 +798,7 @@ class Unicorn::HttpServer
|
|
|
729
798
|
config.commit!(self)
|
|
730
799
|
soft_kill_each_worker(:QUIT)
|
|
731
800
|
Unicorn::Util.reopen_logs
|
|
732
|
-
self.app = orig_app
|
|
801
|
+
self.app = @orig_app
|
|
733
802
|
build_app! if preload_app
|
|
734
803
|
logger.info "done reloading config_file=#{config.config_file}"
|
|
735
804
|
rescue StandardError, LoadError, SyntaxError => e
|
|
@@ -744,12 +813,12 @@ class Unicorn::HttpServer
|
|
|
744
813
|
end
|
|
745
814
|
|
|
746
815
|
def build_app!
|
|
747
|
-
if app.respond_to?(:arity) && app.arity == 0
|
|
816
|
+
if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
|
|
748
817
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
|
749
818
|
logger.info "Refreshing Gem list"
|
|
750
819
|
Gem.refresh
|
|
751
820
|
end
|
|
752
|
-
self.app = app.call
|
|
821
|
+
self.app = app.arity == 0 ? app.call : app.call(nil, self)
|
|
753
822
|
end
|
|
754
823
|
end
|
|
755
824
|
|
|
@@ -766,12 +835,23 @@ class Unicorn::HttpServer
|
|
|
766
835
|
def inherit_listeners!
|
|
767
836
|
# inherit sockets from parents, they need to be plain Socket objects
|
|
768
837
|
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
|
769
|
-
inherited = ENV['UNICORN_FD'].to_s.split(
|
|
838
|
+
inherited = ENV['UNICORN_FD'].to_s.split(',')
|
|
839
|
+
|
|
840
|
+
# emulate sd_listen_fds() for systemd
|
|
841
|
+
sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS')
|
|
842
|
+
if sd_pid.to_i == $$ # n.b. $$ can never be zero
|
|
843
|
+
# 3 = SD_LISTEN_FDS_START
|
|
844
|
+
inherited.concat((3...(3 + sd_fds.to_i)).to_a)
|
|
845
|
+
end
|
|
846
|
+
# to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS
|
|
847
|
+
|
|
848
|
+
inherited.map! do |fd|
|
|
770
849
|
io = Socket.for_fd(fd.to_i)
|
|
850
|
+
io.autoclose = false
|
|
851
|
+
io = server_cast(io)
|
|
771
852
|
set_server_sockopt(io, listener_opts[sock_name(io)])
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
server_cast(io)
|
|
853
|
+
logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
|
|
854
|
+
io
|
|
775
855
|
end
|
|
776
856
|
|
|
777
857
|
config_listeners = config[:listeners].dup
|
|
@@ -794,8 +874,20 @@ class Unicorn::HttpServer
|
|
|
794
874
|
# call only after calling inherit_listeners!
|
|
795
875
|
# This binds any listeners we did NOT inherit from the parent
|
|
796
876
|
def bind_new_listeners!
|
|
797
|
-
NEW_LISTENERS.each { |addr| listen(addr) }
|
|
877
|
+
NEW_LISTENERS.each { |addr| listen(addr) }.clear
|
|
798
878
|
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
|
799
|
-
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# try to use the monotonic clock in Ruby >= 2.1, it is immune to clock
|
|
882
|
+
# offset adjustments and generates less garbage (Float vs Time object)
|
|
883
|
+
begin
|
|
884
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
885
|
+
def time_now
|
|
886
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
887
|
+
end
|
|
888
|
+
rescue NameError, NoMethodError
|
|
889
|
+
def time_now # Ruby <= 2.0
|
|
890
|
+
Time.now
|
|
891
|
+
end
|
|
800
892
|
end
|
|
801
893
|
end
|