puma 4.3.6 → 5.0.0.beta1
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 +60 -12
- data/LICENSE +23 -20
- data/README.md +17 -11
- data/docs/deployment.md +3 -1
- 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 +1 -0
- 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 +1 -3
- data/ext/puma_http11/http11_parser.rl +1 -3
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/puma_http11.c +2 -38
- data/lib/puma.rb +4 -0
- data/lib/puma/app/status.rb +16 -5
- data/lib/puma/binder.rb +62 -60
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +30 -15
- data/lib/puma/cluster.rb +179 -74
- 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 +70 -34
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +41 -29
- data/lib/puma/minissl.rb +13 -8
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +6 -1
- data/lib/puma/runner.rb +5 -34
- data/lib/puma/server.rb +62 -177
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +5 -2
- data/lib/puma/thread_pool.rb +85 -47
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +19 -23
- 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/cluster.rb
CHANGED
@@ -5,6 +5,7 @@ require 'puma/util'
|
|
5
5
|
require 'puma/plugin'
|
6
6
|
|
7
7
|
require 'time'
|
8
|
+
require 'json'
|
8
9
|
|
9
10
|
module Puma
|
10
11
|
# This class is instantiated by the `Puma::Launcher` and used
|
@@ -24,9 +25,8 @@ module Puma
|
|
24
25
|
|
25
26
|
@phase = 0
|
26
27
|
@workers = []
|
27
|
-
@next_check =
|
28
|
+
@next_check = Time.now
|
28
29
|
|
29
|
-
@phased_state = :idle
|
30
30
|
@phased_restart = false
|
31
31
|
end
|
32
32
|
|
@@ -37,7 +37,7 @@ module Puma
|
|
37
37
|
begin
|
38
38
|
loop do
|
39
39
|
wait_workers
|
40
|
-
break if @workers.empty?
|
40
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
41
41
|
sleep 0.2
|
42
42
|
end
|
43
43
|
rescue Interrupt
|
@@ -73,11 +73,12 @@ module Puma
|
|
73
73
|
@first_term_sent = nil
|
74
74
|
@started_at = Time.now
|
75
75
|
@last_checkin = Time.now
|
76
|
-
@last_status =
|
76
|
+
@last_status = {}
|
77
77
|
@term = false
|
78
78
|
end
|
79
79
|
|
80
80
|
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
81
|
+
attr_writer :pid, :phase
|
81
82
|
|
82
83
|
def booted?
|
83
84
|
@stage == :booted
|
@@ -94,11 +95,15 @@ module Puma
|
|
94
95
|
|
95
96
|
def ping!(status)
|
96
97
|
@last_checkin = Time.now
|
97
|
-
@last_status = status
|
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!
|
@@ -241,9 +249,14 @@ module Puma
|
|
241
249
|
|
242
250
|
Signal.trap "SIGINT", "IGNORE"
|
243
251
|
|
252
|
+
fork_worker = @options[:fork_worker] && index == 0
|
253
|
+
|
244
254
|
@workers = []
|
245
|
-
|
246
|
-
|
255
|
+
if !@options[:fork_worker] || fork_worker
|
256
|
+
@master_read.close
|
257
|
+
@suicide_pipe.close
|
258
|
+
@fork_writer.close
|
259
|
+
end
|
247
260
|
|
248
261
|
Thread.new do
|
249
262
|
Puma.set_thread_name "worker check pipe"
|
@@ -264,17 +277,47 @@ module Puma
|
|
264
277
|
|
265
278
|
# Invoke any worker boot hooks so they can get
|
266
279
|
# things in shape before booting the app.
|
267
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
280
|
+
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
281
|
+
|
282
|
+
server = @server ||= start_server
|
283
|
+
restart_server = Queue.new << true << false
|
268
284
|
|
269
|
-
|
285
|
+
if fork_worker
|
286
|
+
restart_server.clear
|
287
|
+
Signal.trap "SIGCHLD" do
|
288
|
+
Process.wait(-1, Process::WNOHANG) rescue nil
|
289
|
+
wakeup!
|
290
|
+
end
|
291
|
+
|
292
|
+
Thread.new do
|
293
|
+
Puma.set_thread_name "worker fork pipe"
|
294
|
+
while (idx = @fork_pipe.gets)
|
295
|
+
idx = idx.to_i
|
296
|
+
if idx == -1 # stop server
|
297
|
+
if restart_server.length > 0
|
298
|
+
restart_server.clear
|
299
|
+
server.begin_restart(true)
|
300
|
+
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
301
|
+
nakayoshi_gc
|
302
|
+
end
|
303
|
+
elsif idx == 0 # restart server
|
304
|
+
restart_server << true << false
|
305
|
+
else # fork worker
|
306
|
+
pid = spawn_worker(idx, master)
|
307
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
270
312
|
|
271
313
|
Signal.trap "SIGTERM" do
|
272
314
|
@worker_write << "e#{Process.pid}\n" rescue nil
|
273
315
|
server.stop
|
316
|
+
restart_server << false
|
274
317
|
end
|
275
318
|
|
276
319
|
begin
|
277
|
-
@worker_write << "b#{Process.pid}\n"
|
320
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
278
321
|
rescue SystemCallError, IOError
|
279
322
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
280
323
|
STDERR.puts "Master seems to have exited, exiting."
|
@@ -283,17 +326,11 @@ module Puma
|
|
283
326
|
|
284
327
|
Thread.new(@worker_write) do |io|
|
285
328
|
Puma.set_thread_name "stat payload"
|
286
|
-
base_payload = "p#{Process.pid}"
|
287
329
|
|
288
330
|
while true
|
289
331
|
sleep Const::WORKER_CHECK_INTERVAL
|
290
332
|
begin
|
291
|
-
|
292
|
-
r = server.running || 0
|
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
|
333
|
+
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
297
334
|
rescue IOError
|
298
335
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
299
336
|
break
|
@@ -301,11 +338,11 @@ module Puma
|
|
301
338
|
end
|
302
339
|
end
|
303
340
|
|
304
|
-
server.run.join
|
341
|
+
server.run.join while restart_server.pop
|
305
342
|
|
306
343
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
307
344
|
# exiting until any background operations are completed
|
308
|
-
@launcher.config.run_hooks :before_worker_shutdown, index
|
345
|
+
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
309
346
|
ensure
|
310
347
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
311
348
|
@worker_write.close
|
@@ -352,18 +389,57 @@ module Puma
|
|
352
389
|
# the master process.
|
353
390
|
def stats
|
354
391
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
355
|
-
|
356
|
-
|
357
|
-
|
392
|
+
worker_status = @workers.map do |w|
|
393
|
+
{
|
394
|
+
started_at: w.started_at.utc.iso8601,
|
395
|
+
pid: w.pid,
|
396
|
+
index: w.index,
|
397
|
+
phase: w.phase,
|
398
|
+
booted: w.booted?,
|
399
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
400
|
+
last_status: w.last_status,
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
{
|
405
|
+
started_at: @started_at.utc.iso8601,
|
406
|
+
workers: @workers.size,
|
407
|
+
phase: @phase,
|
408
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
409
|
+
old_workers: old_worker_count,
|
410
|
+
worker_status: worker_status,
|
411
|
+
}
|
358
412
|
end
|
359
413
|
|
360
414
|
def preload?
|
361
415
|
@options[:preload_app]
|
362
416
|
end
|
363
417
|
|
418
|
+
def fork_worker!
|
419
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
420
|
+
worker.phase += 1
|
421
|
+
end
|
422
|
+
phased_restart
|
423
|
+
end
|
424
|
+
|
364
425
|
# We do this in a separate method to keep the lambda scope
|
365
426
|
# of the signals handlers as small as possible.
|
366
427
|
def setup_signals
|
428
|
+
if @options[:fork_worker]
|
429
|
+
Signal.trap "SIGURG" do
|
430
|
+
fork_worker!
|
431
|
+
end
|
432
|
+
|
433
|
+
# Auto-fork after the specified number of requests.
|
434
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
435
|
+
@launcher.events.register(:ping!) do |w|
|
436
|
+
fork_worker! if w.index == 0 &&
|
437
|
+
w.phase == 0 &&
|
438
|
+
w.last_status[:requests_count] >= fork_requests
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
367
443
|
Signal.trap "SIGCHLD" do
|
368
444
|
wakeup!
|
369
445
|
end
|
@@ -447,12 +523,11 @@ module Puma
|
|
447
523
|
#
|
448
524
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
449
525
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
end
|
526
|
+
# Separate pipe used by worker 0 to receive commands to
|
527
|
+
# fork new worker processes.
|
528
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
529
|
+
|
530
|
+
log "Use Ctrl-C to stop"
|
456
531
|
|
457
532
|
redirect_io
|
458
533
|
|
@@ -464,7 +539,8 @@ module Puma
|
|
464
539
|
|
465
540
|
@master_read, @worker_write = read, @wakeup
|
466
541
|
|
467
|
-
@launcher.config.run_hooks :before_fork, nil
|
542
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
543
|
+
nakayoshi_gc
|
468
544
|
|
469
545
|
spawn_workers
|
470
546
|
|
@@ -475,8 +551,6 @@ module Puma
|
|
475
551
|
@launcher.events.fire_on_booted!
|
476
552
|
|
477
553
|
begin
|
478
|
-
force_check = false
|
479
|
-
|
480
554
|
while @status == :run
|
481
555
|
begin
|
482
556
|
if @phased_restart
|
@@ -484,34 +558,39 @@ module Puma
|
|
484
558
|
@phased_restart = false
|
485
559
|
end
|
486
560
|
|
487
|
-
check_workers
|
561
|
+
check_workers
|
488
562
|
|
489
|
-
|
490
|
-
|
491
|
-
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
563
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
492
564
|
|
493
565
|
if res
|
494
566
|
req = read.read_nonblock(1)
|
495
567
|
|
568
|
+
@next_check = Time.now if req == "!"
|
496
569
|
next if !req || req == "!"
|
497
570
|
|
498
571
|
result = read.gets
|
499
572
|
pid = result.to_i
|
500
573
|
|
574
|
+
if req == "b" || req == "f"
|
575
|
+
pid, idx = result.split(':').map(&:to_i)
|
576
|
+
w = @workers.find {|x| x.index == idx}
|
577
|
+
w.pid = pid if w.pid.nil?
|
578
|
+
end
|
579
|
+
|
501
580
|
if w = @workers.find { |x| x.pid == pid }
|
502
581
|
case req
|
503
582
|
when "b"
|
504
583
|
w.boot!
|
505
584
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
506
|
-
|
585
|
+
@next_check = Time.now
|
507
586
|
when "e"
|
508
587
|
# external term, see worker method, Signal.trap "SIGTERM"
|
509
588
|
w.instance_variable_set :@term, true
|
510
589
|
when "t"
|
511
590
|
w.term unless w.term?
|
512
|
-
force_check = true
|
513
591
|
when "p"
|
514
592
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
593
|
+
@launcher.events.fire(:ping!, w)
|
515
594
|
end
|
516
595
|
else
|
517
596
|
log "! Out-of-sync worker list, no #{pid} worker"
|
@@ -538,6 +617,7 @@ module Puma
|
|
538
617
|
# `#term` if needed
|
539
618
|
def wait_workers
|
540
619
|
@workers.reject! do |w|
|
620
|
+
next false if w.pid.nil?
|
541
621
|
begin
|
542
622
|
if Process.wait(w.pid, Process::WNOHANG)
|
543
623
|
true
|
@@ -546,9 +626,34 @@ module Puma
|
|
546
626
|
nil
|
547
627
|
end
|
548
628
|
rescue Errno::ECHILD
|
549
|
-
|
629
|
+
begin
|
630
|
+
Process.kill(0, w.pid)
|
631
|
+
false # child still alive, but has another parent
|
632
|
+
rescue Errno::ESRCH, Errno::EPERM
|
633
|
+
true # child is already terminated
|
634
|
+
end
|
550
635
|
end
|
551
636
|
end
|
552
637
|
end
|
638
|
+
|
639
|
+
def timeout_workers
|
640
|
+
@workers.each do |w|
|
641
|
+
if !w.term? && w.ping_timeout <= Time.now
|
642
|
+
log "! Terminating timed out worker: #{w.pid}"
|
643
|
+
w.kill
|
644
|
+
end
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
def nakayoshi_gc
|
649
|
+
return unless @options[:nakayoshi_fork]
|
650
|
+
log "! Promoting existing objects to old generation..."
|
651
|
+
4.times { GC.start(full_mark: false) }
|
652
|
+
if GC.respond_to?(:compact)
|
653
|
+
log "! Compacting..."
|
654
|
+
GC.compact
|
655
|
+
end
|
656
|
+
log "! Friendly fork preparation complete."
|
657
|
+
end
|
553
658
|
end
|
554
659
|
end
|