puma 4.3.3-java → 5.0.0.beta2-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +79 -8
- data/LICENSE +23 -20
- data/README.md +18 -12
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/signals.md +5 -4
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +4 -3
- data/ext/puma_http11/http11_parser.c +3 -1
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/mini_ssl.c +12 -2
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +37 -6
- data/ext/puma_http11/puma_http11.c +3 -38
- data/lib/puma.rb +5 -0
- data/lib/puma/app/status.rb +18 -3
- data/lib/puma/binder.rb +66 -63
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +64 -14
- data/lib/puma/cluster.rb +183 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +30 -42
- data/lib/puma/const.rb +2 -3
- data/lib/puma/control_cli.rb +27 -17
- data/lib/puma/detect.rb +8 -0
- data/lib/puma/dsl.rb +72 -36
- data/lib/puma/error_logger.rb +96 -0
- data/lib/puma/events.rb +33 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +46 -31
- data/lib/puma/minissl.rb +47 -10
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +8 -3
- data/lib/puma/runner.rb +6 -35
- data/lib/puma/server.rb +138 -182
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +90 -49
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +18 -21
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
data/lib/puma/cli.rb
CHANGED
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
@launcher.run
|
81
81
|
end
|
82
82
|
|
83
|
-
|
83
|
+
private
|
84
84
|
def unsupported(str)
|
85
85
|
@events.error(str)
|
86
86
|
raise UnsupportedOption
|
@@ -112,21 +112,11 @@ module Puma
|
|
112
112
|
configure_control_url(arg)
|
113
113
|
end
|
114
114
|
|
115
|
-
# alias --control-url for backwards-compatibility
|
116
|
-
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
117
|
-
configure_control_url(arg)
|
118
|
-
end
|
119
|
-
|
120
115
|
o.on "--control-token TOKEN",
|
121
116
|
"The token to use as authentication for the control server" do |arg|
|
122
117
|
@control_options[:auth_token] = arg
|
123
118
|
end
|
124
119
|
|
125
|
-
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
126
|
-
user_config.daemonize
|
127
|
-
user_config.quiet
|
128
|
-
end
|
129
|
-
|
130
120
|
o.on "--debug", "Log lowlevel debugging information" do
|
131
121
|
user_config.debug
|
132
122
|
end
|
@@ -140,6 +130,12 @@ module Puma
|
|
140
130
|
user_config.environment arg
|
141
131
|
end
|
142
132
|
|
133
|
+
o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
|
134
|
+
"Fork new workers from existing worker. Cluster mode only",
|
135
|
+
"Auto-refork after REQUESTS (default 1000)" do |*args|
|
136
|
+
user_config.fork_worker(*args.compact)
|
137
|
+
end
|
138
|
+
|
143
139
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
144
140
|
$LOAD_PATH.unshift(*arg.split(':'))
|
145
141
|
end
|
@@ -192,10 +188,6 @@ module Puma
|
|
192
188
|
end
|
193
189
|
end
|
194
190
|
|
195
|
-
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
196
|
-
user_config.tcp_mode!
|
197
|
-
end
|
198
|
-
|
199
191
|
o.on "--early-hints", "Enable early hints support" do
|
200
192
|
user_config.early_hints
|
201
193
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -153,7 +153,7 @@ module Puma
|
|
153
153
|
|
154
154
|
begin
|
155
155
|
data = @io.read_nonblock(CHUNK_SIZE)
|
156
|
-
rescue
|
156
|
+
rescue IO::WaitReadable
|
157
157
|
return false
|
158
158
|
rescue SystemCallError, IOError, EOFError
|
159
159
|
raise ConnectionError, "Connection error detected during read"
|
@@ -238,12 +238,23 @@ module Puma
|
|
238
238
|
return false unless IO.select([@to_io], nil, nil, 0)
|
239
239
|
try_to_finish
|
240
240
|
end
|
241
|
+
|
242
|
+
# For documentation, see https://github.com/puma/puma/issues/1754
|
243
|
+
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
241
244
|
end # IS_JRUBY
|
242
245
|
|
243
|
-
def finish
|
246
|
+
def finish(timeout)
|
244
247
|
return true if @ready
|
245
248
|
until try_to_finish
|
246
|
-
|
249
|
+
can_read = begin
|
250
|
+
IO.select([@to_io], nil, nil, timeout)
|
251
|
+
rescue ThreadPool::ForceShutdown
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
unless can_read
|
255
|
+
write_error(408) if in_data_phase
|
256
|
+
raise ConnectionError
|
257
|
+
end
|
247
258
|
end
|
248
259
|
true
|
249
260
|
end
|
@@ -259,7 +270,7 @@ module Puma
|
|
259
270
|
return @peerip if @peerip
|
260
271
|
|
261
272
|
if @remote_addr_header
|
262
|
-
hdr = (@env[@remote_addr_header] ||
|
273
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
263
274
|
@peerip = hdr
|
264
275
|
return hdr
|
265
276
|
end
|
@@ -267,6 +278,18 @@ module Puma
|
|
267
278
|
@peerip ||= @io.peeraddr.last
|
268
279
|
end
|
269
280
|
|
281
|
+
# Returns true if the persistent connection can be closed immediately
|
282
|
+
# without waiting for the configured idle/shutdown timeout.
|
283
|
+
def can_close?
|
284
|
+
# Allow connection to close if it's received at least one full request
|
285
|
+
# and hasn't received any data for a future request.
|
286
|
+
#
|
287
|
+
# From RFC 2616 section 8.1.4:
|
288
|
+
# Servers SHOULD always respond to at least one request per connection,
|
289
|
+
# if at all possible.
|
290
|
+
@requests_served > 0 && @parsed_bytes == 0
|
291
|
+
end
|
292
|
+
|
270
293
|
private
|
271
294
|
|
272
295
|
def setup_body
|
@@ -285,8 +308,16 @@ module Puma
|
|
285
308
|
|
286
309
|
te = @env[TRANSFER_ENCODING2]
|
287
310
|
|
288
|
-
if te
|
289
|
-
|
311
|
+
if te
|
312
|
+
if te.include?(",")
|
313
|
+
te.split(",").each do |part|
|
314
|
+
if CHUNKED.casecmp(part.strip) == 0
|
315
|
+
return setup_chunked_body(body)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
elsif CHUNKED.casecmp(te) == 0
|
319
|
+
return setup_chunked_body(body)
|
320
|
+
end
|
290
321
|
end
|
291
322
|
|
292
323
|
@chunked_body = false
|
@@ -343,7 +374,7 @@ module Puma
|
|
343
374
|
|
344
375
|
begin
|
345
376
|
chunk = @io.read_nonblock(want)
|
346
|
-
rescue
|
377
|
+
rescue IO::WaitReadable
|
347
378
|
return false
|
348
379
|
rescue SystemCallError, IOError
|
349
380
|
raise ConnectionError, "Connection error detected during read"
|
@@ -389,7 +420,10 @@ module Puma
|
|
389
420
|
raise EOFError
|
390
421
|
end
|
391
422
|
|
392
|
-
|
423
|
+
if decode_chunk(chunk)
|
424
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
425
|
+
return true
|
426
|
+
end
|
393
427
|
end
|
394
428
|
end
|
395
429
|
|
@@ -401,20 +435,36 @@ module Puma
|
|
401
435
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
402
436
|
@body.binmode
|
403
437
|
@tempfile = @body
|
438
|
+
@chunked_content_length = 0
|
404
439
|
|
405
|
-
|
440
|
+
if decode_chunk(body)
|
441
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
442
|
+
return true
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def write_chunk(str)
|
447
|
+
@chunked_content_length += @body.write(str)
|
406
448
|
end
|
407
449
|
|
408
450
|
def decode_chunk(chunk)
|
409
451
|
if @partial_part_left > 0
|
410
452
|
if @partial_part_left <= chunk.size
|
411
453
|
if @partial_part_left > 2
|
412
|
-
|
454
|
+
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
413
455
|
end
|
414
456
|
chunk = chunk[@partial_part_left..-1]
|
415
457
|
@partial_part_left = 0
|
416
458
|
else
|
417
|
-
|
459
|
+
if @partial_part_left > 2
|
460
|
+
if @partial_part_left == chunk.size + 1
|
461
|
+
# Don't include the last \r
|
462
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
463
|
+
else
|
464
|
+
# don't include the last \r\n
|
465
|
+
write_chunk(chunk)
|
466
|
+
end
|
467
|
+
end
|
418
468
|
@partial_part_left -= chunk.size
|
419
469
|
return false
|
420
470
|
end
|
@@ -461,12 +511,12 @@ module Puma
|
|
461
511
|
|
462
512
|
case
|
463
513
|
when got == len
|
464
|
-
|
514
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
465
515
|
when got <= len - 2
|
466
|
-
|
516
|
+
write_chunk(part)
|
467
517
|
@partial_part_left = len - part.size
|
468
518
|
when got == len - 1 # edge where we get just \r but not \n
|
469
|
-
|
519
|
+
write_chunk(part[0..-2])
|
470
520
|
@partial_part_left = len - part.size
|
471
521
|
end
|
472
522
|
else
|
data/lib/puma/cluster.rb
CHANGED
@@ -24,9 +24,8 @@ module Puma
|
|
24
24
|
|
25
25
|
@phase = 0
|
26
26
|
@workers = []
|
27
|
-
@next_check =
|
27
|
+
@next_check = Time.now
|
28
28
|
|
29
|
-
@phased_state = :idle
|
30
29
|
@phased_restart = false
|
31
30
|
end
|
32
31
|
|
@@ -37,7 +36,7 @@ module Puma
|
|
37
36
|
begin
|
38
37
|
loop do
|
39
38
|
wait_workers
|
40
|
-
break if @workers.empty?
|
39
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
41
40
|
sleep 0.2
|
42
41
|
end
|
43
42
|
rescue Interrupt
|
@@ -73,11 +72,12 @@ module Puma
|
|
73
72
|
@first_term_sent = nil
|
74
73
|
@started_at = Time.now
|
75
74
|
@last_checkin = Time.now
|
76
|
-
@last_status =
|
75
|
+
@last_status = {}
|
77
76
|
@term = false
|
78
77
|
end
|
79
78
|
|
80
79
|
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
80
|
+
attr_writer :pid, :phase
|
81
81
|
|
82
82
|
def booted?
|
83
83
|
@stage == :booted
|
@@ -94,11 +94,16 @@ module Puma
|
|
94
94
|
|
95
95
|
def ping!(status)
|
96
96
|
@last_checkin = Time.now
|
97
|
-
|
97
|
+
require 'json'
|
98
|
+
@last_status = JSON.parse(status, symbolize_names: true)
|
98
99
|
end
|
99
100
|
|
100
|
-
def ping_timeout
|
101
|
-
|
101
|
+
def ping_timeout
|
102
|
+
@last_checkin +
|
103
|
+
(booted? ?
|
104
|
+
@options[:worker_timeout] :
|
105
|
+
@options[:worker_boot_timeout]
|
106
|
+
)
|
102
107
|
end
|
103
108
|
|
104
109
|
def term
|
@@ -109,14 +114,14 @@ module Puma
|
|
109
114
|
@term ||= true
|
110
115
|
@first_term_sent ||= Time.now
|
111
116
|
end
|
112
|
-
Process.kill @signal, @pid
|
117
|
+
Process.kill @signal, @pid if @pid
|
113
118
|
rescue Errno::ESRCH
|
114
119
|
end
|
115
120
|
end
|
116
121
|
|
117
122
|
def kill
|
118
|
-
|
119
|
-
|
123
|
+
@signal = 'KILL'
|
124
|
+
term
|
120
125
|
end
|
121
126
|
|
122
127
|
def hup
|
@@ -130,27 +135,43 @@ module Puma
|
|
130
135
|
return if diff < 1
|
131
136
|
|
132
137
|
master = Process.pid
|
138
|
+
if @options[:fork_worker]
|
139
|
+
@fork_writer << "-1\n"
|
140
|
+
end
|
133
141
|
|
134
142
|
diff.times do
|
135
143
|
idx = next_worker_index
|
136
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
137
144
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
145
|
+
if @options[:fork_worker] && idx != 0
|
146
|
+
@fork_writer << "#{idx}\n"
|
147
|
+
pid = nil
|
148
|
+
else
|
149
|
+
pid = spawn_worker(idx, master)
|
143
150
|
end
|
144
151
|
|
145
152
|
debug "Spawned worker: #{pid}"
|
146
153
|
@workers << Worker.new(idx, pid, @phase, @options)
|
154
|
+
end
|
147
155
|
|
148
|
-
|
156
|
+
if @options[:fork_worker] &&
|
157
|
+
@workers.all? {|x| x.phase == @phase}
|
158
|
+
|
159
|
+
@fork_writer << "0\n"
|
149
160
|
end
|
161
|
+
end
|
150
162
|
|
151
|
-
|
152
|
-
|
163
|
+
def spawn_worker(idx, master)
|
164
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
165
|
+
|
166
|
+
pid = fork { worker(idx, master) }
|
167
|
+
if !pid
|
168
|
+
log "! Complete inability to spawn new workers detected"
|
169
|
+
log "! Seppuku is the only choice."
|
170
|
+
exit! 1
|
153
171
|
end
|
172
|
+
|
173
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
174
|
+
pid
|
154
175
|
end
|
155
176
|
|
156
177
|
def cull_workers
|
@@ -179,26 +200,12 @@ module Puma
|
|
179
200
|
@workers.count { |w| !w.booted? } == 0
|
180
201
|
end
|
181
202
|
|
182
|
-
def check_workers
|
183
|
-
return if
|
203
|
+
def check_workers
|
204
|
+
return if @next_check >= Time.now
|
184
205
|
|
185
206
|
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
186
207
|
|
187
|
-
|
188
|
-
|
189
|
-
@workers.each do |w|
|
190
|
-
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
191
|
-
if w.ping_timeout?(@options[:worker_timeout])
|
192
|
-
log "! Terminating timed out worker: #{w.pid}"
|
193
|
-
w.kill
|
194
|
-
any = true
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# If we killed any timed out workers, try to catch them
|
199
|
-
# during this loop by giving the kernel time to kill them.
|
200
|
-
sleep 1 if any
|
201
|
-
|
208
|
+
timeout_workers
|
202
209
|
wait_workers
|
203
210
|
cull_workers
|
204
211
|
spawn_workers
|
@@ -211,17 +218,18 @@ module Puma
|
|
211
218
|
w = @workers.find { |x| x.phase != @phase }
|
212
219
|
|
213
220
|
if w
|
214
|
-
|
215
|
-
@phased_state = :waiting
|
216
|
-
log "- Stopping #{w.pid} for phased upgrade..."
|
217
|
-
end
|
218
|
-
|
221
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
219
222
|
unless w.term?
|
220
223
|
w.term
|
221
224
|
log "- #{w.signal} sent to #{w.pid}..."
|
222
225
|
end
|
223
226
|
end
|
224
227
|
end
|
228
|
+
|
229
|
+
@next_check = [
|
230
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
231
|
+
@next_check
|
232
|
+
].compact.min
|
225
233
|
end
|
226
234
|
|
227
235
|
def wakeup!
|
@@ -240,10 +248,16 @@ module Puma
|
|
240
248
|
$0 = title
|
241
249
|
|
242
250
|
Signal.trap "SIGINT", "IGNORE"
|
251
|
+
Signal.trap "SIGCHLD", "DEFAULT"
|
252
|
+
|
253
|
+
fork_worker = @options[:fork_worker] && index == 0
|
243
254
|
|
244
255
|
@workers = []
|
245
|
-
|
246
|
-
|
256
|
+
if !@options[:fork_worker] || fork_worker
|
257
|
+
@master_read.close
|
258
|
+
@suicide_pipe.close
|
259
|
+
@fork_writer.close
|
260
|
+
end
|
247
261
|
|
248
262
|
Thread.new do
|
249
263
|
Puma.set_thread_name "worker check pipe"
|
@@ -264,17 +278,49 @@ module Puma
|
|
264
278
|
|
265
279
|
# Invoke any worker boot hooks so they can get
|
266
280
|
# things in shape before booting the app.
|
267
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
281
|
+
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
282
|
+
|
283
|
+
server = @server ||= start_server
|
284
|
+
restart_server = Queue.new << true << false
|
268
285
|
|
269
|
-
|
286
|
+
if fork_worker
|
287
|
+
restart_server.clear
|
288
|
+
worker_pids = []
|
289
|
+
Signal.trap "SIGCHLD" do
|
290
|
+
wakeup! if worker_pids.reject! do |p|
|
291
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
Thread.new do
|
296
|
+
Puma.set_thread_name "worker fork pipe"
|
297
|
+
while (idx = @fork_pipe.gets)
|
298
|
+
idx = idx.to_i
|
299
|
+
if idx == -1 # stop server
|
300
|
+
if restart_server.length > 0
|
301
|
+
restart_server.clear
|
302
|
+
server.begin_restart(true)
|
303
|
+
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
304
|
+
nakayoshi_gc
|
305
|
+
end
|
306
|
+
elsif idx == 0 # restart server
|
307
|
+
restart_server << true << false
|
308
|
+
else # fork worker
|
309
|
+
worker_pids << pid = spawn_worker(idx, master)
|
310
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
270
315
|
|
271
316
|
Signal.trap "SIGTERM" do
|
272
317
|
@worker_write << "e#{Process.pid}\n" rescue nil
|
273
318
|
server.stop
|
319
|
+
restart_server << false
|
274
320
|
end
|
275
321
|
|
276
322
|
begin
|
277
|
-
@worker_write << "b#{Process.pid}\n"
|
323
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
278
324
|
rescue SystemCallError, IOError
|
279
325
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
280
326
|
STDERR.puts "Master seems to have exited, exiting."
|
@@ -283,17 +329,12 @@ module Puma
|
|
283
329
|
|
284
330
|
Thread.new(@worker_write) do |io|
|
285
331
|
Puma.set_thread_name "stat payload"
|
286
|
-
base_payload = "p#{Process.pid}"
|
287
332
|
|
288
333
|
while true
|
289
334
|
sleep Const::WORKER_CHECK_INTERVAL
|
290
335
|
begin
|
291
|
-
|
292
|
-
|
293
|
-
t = server.pool_capacity || 0
|
294
|
-
m = server.max_threads || 0
|
295
|
-
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
296
|
-
io << payload
|
336
|
+
require 'json'
|
337
|
+
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
297
338
|
rescue IOError
|
298
339
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
299
340
|
break
|
@@ -301,11 +342,11 @@ module Puma
|
|
301
342
|
end
|
302
343
|
end
|
303
344
|
|
304
|
-
server.run.join
|
345
|
+
server.run.join while restart_server.pop
|
305
346
|
|
306
347
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
307
348
|
# exiting until any background operations are completed
|
308
|
-
@launcher.config.run_hooks :before_worker_shutdown, index
|
349
|
+
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
309
350
|
ensure
|
310
351
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
311
352
|
@worker_write.close
|
@@ -352,18 +393,57 @@ module Puma
|
|
352
393
|
# the master process.
|
353
394
|
def stats
|
354
395
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
355
|
-
|
356
|
-
|
357
|
-
|
396
|
+
worker_status = @workers.map do |w|
|
397
|
+
{
|
398
|
+
started_at: w.started_at.utc.iso8601,
|
399
|
+
pid: w.pid,
|
400
|
+
index: w.index,
|
401
|
+
phase: w.phase,
|
402
|
+
booted: w.booted?,
|
403
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
404
|
+
last_status: w.last_status,
|
405
|
+
}
|
406
|
+
end
|
407
|
+
|
408
|
+
{
|
409
|
+
started_at: @started_at.utc.iso8601,
|
410
|
+
workers: @workers.size,
|
411
|
+
phase: @phase,
|
412
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
413
|
+
old_workers: old_worker_count,
|
414
|
+
worker_status: worker_status,
|
415
|
+
}
|
358
416
|
end
|
359
417
|
|
360
418
|
def preload?
|
361
419
|
@options[:preload_app]
|
362
420
|
end
|
363
421
|
|
422
|
+
def fork_worker!
|
423
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
424
|
+
worker.phase += 1
|
425
|
+
end
|
426
|
+
phased_restart
|
427
|
+
end
|
428
|
+
|
364
429
|
# We do this in a separate method to keep the lambda scope
|
365
430
|
# of the signals handlers as small as possible.
|
366
431
|
def setup_signals
|
432
|
+
if @options[:fork_worker]
|
433
|
+
Signal.trap "SIGURG" do
|
434
|
+
fork_worker!
|
435
|
+
end
|
436
|
+
|
437
|
+
# Auto-fork after the specified number of requests.
|
438
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
439
|
+
@launcher.events.register(:ping!) do |w|
|
440
|
+
fork_worker! if w.index == 0 &&
|
441
|
+
w.phase == 0 &&
|
442
|
+
w.last_status[:requests_count] >= fork_requests
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
367
447
|
Signal.trap "SIGCHLD" do
|
368
448
|
wakeup!
|
369
449
|
end
|
@@ -447,12 +527,11 @@ module Puma
|
|
447
527
|
#
|
448
528
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
449
529
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
end
|
530
|
+
# Separate pipe used by worker 0 to receive commands to
|
531
|
+
# fork new worker processes.
|
532
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
533
|
+
|
534
|
+
log "Use Ctrl-C to stop"
|
456
535
|
|
457
536
|
redirect_io
|
458
537
|
|
@@ -464,7 +543,8 @@ module Puma
|
|
464
543
|
|
465
544
|
@master_read, @worker_write = read, @wakeup
|
466
545
|
|
467
|
-
@launcher.config.run_hooks :before_fork, nil
|
546
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
547
|
+
nakayoshi_gc
|
468
548
|
|
469
549
|
spawn_workers
|
470
550
|
|
@@ -475,8 +555,6 @@ module Puma
|
|
475
555
|
@launcher.events.fire_on_booted!
|
476
556
|
|
477
557
|
begin
|
478
|
-
force_check = false
|
479
|
-
|
480
558
|
while @status == :run
|
481
559
|
begin
|
482
560
|
if @phased_restart
|
@@ -484,34 +562,39 @@ module Puma
|
|
484
562
|
@phased_restart = false
|
485
563
|
end
|
486
564
|
|
487
|
-
check_workers
|
565
|
+
check_workers
|
488
566
|
|
489
|
-
|
490
|
-
|
491
|
-
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
567
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
492
568
|
|
493
569
|
if res
|
494
570
|
req = read.read_nonblock(1)
|
495
571
|
|
572
|
+
@next_check = Time.now if req == "!"
|
496
573
|
next if !req || req == "!"
|
497
574
|
|
498
575
|
result = read.gets
|
499
576
|
pid = result.to_i
|
500
577
|
|
578
|
+
if req == "b" || req == "f"
|
579
|
+
pid, idx = result.split(':').map(&:to_i)
|
580
|
+
w = @workers.find {|x| x.index == idx}
|
581
|
+
w.pid = pid if w.pid.nil?
|
582
|
+
end
|
583
|
+
|
501
584
|
if w = @workers.find { |x| x.pid == pid }
|
502
585
|
case req
|
503
586
|
when "b"
|
504
587
|
w.boot!
|
505
588
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
506
|
-
|
589
|
+
@next_check = Time.now
|
507
590
|
when "e"
|
508
591
|
# external term, see worker method, Signal.trap "SIGTERM"
|
509
592
|
w.instance_variable_set :@term, true
|
510
593
|
when "t"
|
511
594
|
w.term unless w.term?
|
512
|
-
force_check = true
|
513
595
|
when "p"
|
514
596
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
597
|
+
@launcher.events.fire(:ping!, w)
|
515
598
|
end
|
516
599
|
else
|
517
600
|
log "! Out-of-sync worker list, no #{pid} worker"
|
@@ -538,6 +621,7 @@ module Puma
|
|
538
621
|
# `#term` if needed
|
539
622
|
def wait_workers
|
540
623
|
@workers.reject! do |w|
|
624
|
+
next false if w.pid.nil?
|
541
625
|
begin
|
542
626
|
if Process.wait(w.pid, Process::WNOHANG)
|
543
627
|
true
|
@@ -546,9 +630,34 @@ module Puma
|
|
546
630
|
nil
|
547
631
|
end
|
548
632
|
rescue Errno::ECHILD
|
549
|
-
|
633
|
+
begin
|
634
|
+
Process.kill(0, w.pid)
|
635
|
+
false # child still alive, but has another parent
|
636
|
+
rescue Errno::ESRCH, Errno::EPERM
|
637
|
+
true # child is already terminated
|
638
|
+
end
|
550
639
|
end
|
551
640
|
end
|
552
641
|
end
|
642
|
+
|
643
|
+
def timeout_workers
|
644
|
+
@workers.each do |w|
|
645
|
+
if !w.term? && w.ping_timeout <= Time.now
|
646
|
+
log "! Terminating timed out worker: #{w.pid}"
|
647
|
+
w.kill
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
def nakayoshi_gc
|
653
|
+
return unless @options[:nakayoshi_fork]
|
654
|
+
log "! Promoting existing objects to old generation..."
|
655
|
+
4.times { GC.start(full_mark: false) }
|
656
|
+
if GC.respond_to?(:compact)
|
657
|
+
log "! Compacting..."
|
658
|
+
GC.compact
|
659
|
+
end
|
660
|
+
log "! Friendly fork preparation complete."
|
661
|
+
end
|
553
662
|
end
|
554
663
|
end
|