puma 4.3.1 → 5.0.0
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 +94 -3
- data/LICENSE +23 -20
- data/README.md +26 -13
- 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 +7 -6
- 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 +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +7 -38
- data/lib/puma.rb +17 -0
- data/lib/puma/app/status.rb +18 -3
- data/lib/puma/binder.rb +88 -68
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +67 -14
- data/lib/puma/cluster.rb +191 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +4 -3
- data/lib/puma/control_cli.rb +29 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +144 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +35 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +49 -31
- data/lib/puma/minissl.rb +60 -18
- data/lib/puma/minissl/context_builder.rb +0 -3
- 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 +9 -4
- data/lib/puma/runner.rb +8 -36
- data/lib/puma/server.rb +149 -186
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +94 -49
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +21 -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
@@ -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,12 +72,15 @@ 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
|
81
80
|
|
81
|
+
# @version 5.0.0
|
82
|
+
attr_writer :pid, :phase
|
83
|
+
|
82
84
|
def booted?
|
83
85
|
@stage == :booted
|
84
86
|
end
|
@@ -94,11 +96,18 @@ module Puma
|
|
94
96
|
|
95
97
|
def ping!(status)
|
96
98
|
@last_checkin = Time.now
|
97
|
-
|
99
|
+
require 'json'
|
100
|
+
@last_status = JSON.parse(status, symbolize_names: true)
|
98
101
|
end
|
99
102
|
|
100
|
-
|
101
|
-
|
103
|
+
# @see Puma::Cluster#check_workers
|
104
|
+
# @version 5.0.0
|
105
|
+
def ping_timeout
|
106
|
+
@last_checkin +
|
107
|
+
(booted? ?
|
108
|
+
@options[:worker_timeout] :
|
109
|
+
@options[:worker_boot_timeout]
|
110
|
+
)
|
102
111
|
end
|
103
112
|
|
104
113
|
def term
|
@@ -109,14 +118,14 @@ module Puma
|
|
109
118
|
@term ||= true
|
110
119
|
@first_term_sent ||= Time.now
|
111
120
|
end
|
112
|
-
Process.kill @signal, @pid
|
121
|
+
Process.kill @signal, @pid if @pid
|
113
122
|
rescue Errno::ESRCH
|
114
123
|
end
|
115
124
|
end
|
116
125
|
|
117
126
|
def kill
|
118
|
-
|
119
|
-
|
127
|
+
@signal = 'KILL'
|
128
|
+
term
|
120
129
|
end
|
121
130
|
|
122
131
|
def hup
|
@@ -130,27 +139,44 @@ module Puma
|
|
130
139
|
return if diff < 1
|
131
140
|
|
132
141
|
master = Process.pid
|
142
|
+
if @options[:fork_worker]
|
143
|
+
@fork_writer << "-1\n"
|
144
|
+
end
|
133
145
|
|
134
146
|
diff.times do
|
135
147
|
idx = next_worker_index
|
136
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
137
148
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
149
|
+
if @options[:fork_worker] && idx != 0
|
150
|
+
@fork_writer << "#{idx}\n"
|
151
|
+
pid = nil
|
152
|
+
else
|
153
|
+
pid = spawn_worker(idx, master)
|
143
154
|
end
|
144
155
|
|
145
156
|
debug "Spawned worker: #{pid}"
|
146
157
|
@workers << Worker.new(idx, pid, @phase, @options)
|
158
|
+
end
|
159
|
+
|
160
|
+
if @options[:fork_worker] &&
|
161
|
+
@workers.all? {|x| x.phase == @phase}
|
147
162
|
|
148
|
-
@
|
163
|
+
@fork_writer << "0\n"
|
149
164
|
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# @version 5.0.0
|
168
|
+
def spawn_worker(idx, master)
|
169
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
150
170
|
|
151
|
-
|
152
|
-
|
171
|
+
pid = fork { worker(idx, master) }
|
172
|
+
if !pid
|
173
|
+
log "! Complete inability to spawn new workers detected"
|
174
|
+
log "! Seppuku is the only choice."
|
175
|
+
exit! 1
|
153
176
|
end
|
177
|
+
|
178
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
179
|
+
pid
|
154
180
|
end
|
155
181
|
|
156
182
|
def cull_workers
|
@@ -179,26 +205,12 @@ module Puma
|
|
179
205
|
@workers.count { |w| !w.booted? } == 0
|
180
206
|
end
|
181
207
|
|
182
|
-
def check_workers
|
183
|
-
return if
|
208
|
+
def check_workers
|
209
|
+
return if @next_check >= Time.now
|
184
210
|
|
185
211
|
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
186
212
|
|
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
|
-
|
213
|
+
timeout_workers
|
202
214
|
wait_workers
|
203
215
|
cull_workers
|
204
216
|
spawn_workers
|
@@ -211,17 +223,18 @@ module Puma
|
|
211
223
|
w = @workers.find { |x| x.phase != @phase }
|
212
224
|
|
213
225
|
if w
|
214
|
-
|
215
|
-
@phased_state = :waiting
|
216
|
-
log "- Stopping #{w.pid} for phased upgrade..."
|
217
|
-
end
|
218
|
-
|
226
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
219
227
|
unless w.term?
|
220
228
|
w.term
|
221
229
|
log "- #{w.signal} sent to #{w.pid}..."
|
222
230
|
end
|
223
231
|
end
|
224
232
|
end
|
233
|
+
|
234
|
+
@next_check = [
|
235
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
236
|
+
@next_check
|
237
|
+
].compact.min
|
225
238
|
end
|
226
239
|
|
227
240
|
def wakeup!
|
@@ -240,10 +253,16 @@ module Puma
|
|
240
253
|
$0 = title
|
241
254
|
|
242
255
|
Signal.trap "SIGINT", "IGNORE"
|
256
|
+
Signal.trap "SIGCHLD", "DEFAULT"
|
257
|
+
|
258
|
+
fork_worker = @options[:fork_worker] && index == 0
|
243
259
|
|
244
260
|
@workers = []
|
245
|
-
|
246
|
-
|
261
|
+
if !@options[:fork_worker] || fork_worker
|
262
|
+
@master_read.close
|
263
|
+
@suicide_pipe.close
|
264
|
+
@fork_writer.close
|
265
|
+
end
|
247
266
|
|
248
267
|
Thread.new do
|
249
268
|
Puma.set_thread_name "worker check pipe"
|
@@ -264,17 +283,49 @@ module Puma
|
|
264
283
|
|
265
284
|
# Invoke any worker boot hooks so they can get
|
266
285
|
# things in shape before booting the app.
|
267
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
286
|
+
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
268
287
|
|
269
|
-
server = start_server
|
288
|
+
server = @server ||= start_server
|
289
|
+
restart_server = Queue.new << true << false
|
290
|
+
|
291
|
+
if fork_worker
|
292
|
+
restart_server.clear
|
293
|
+
worker_pids = []
|
294
|
+
Signal.trap "SIGCHLD" do
|
295
|
+
wakeup! if worker_pids.reject! do |p|
|
296
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
Thread.new do
|
301
|
+
Puma.set_thread_name "worker fork pipe"
|
302
|
+
while (idx = @fork_pipe.gets)
|
303
|
+
idx = idx.to_i
|
304
|
+
if idx == -1 # stop server
|
305
|
+
if restart_server.length > 0
|
306
|
+
restart_server.clear
|
307
|
+
server.begin_restart(true)
|
308
|
+
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
309
|
+
nakayoshi_gc
|
310
|
+
end
|
311
|
+
elsif idx == 0 # restart server
|
312
|
+
restart_server << true << false
|
313
|
+
else # fork worker
|
314
|
+
worker_pids << pid = spawn_worker(idx, master)
|
315
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
270
320
|
|
271
321
|
Signal.trap "SIGTERM" do
|
272
322
|
@worker_write << "e#{Process.pid}\n" rescue nil
|
273
323
|
server.stop
|
324
|
+
restart_server << false
|
274
325
|
end
|
275
326
|
|
276
327
|
begin
|
277
|
-
@worker_write << "b#{Process.pid}\n"
|
328
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
278
329
|
rescue SystemCallError, IOError
|
279
330
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
280
331
|
STDERR.puts "Master seems to have exited, exiting."
|
@@ -283,17 +334,12 @@ module Puma
|
|
283
334
|
|
284
335
|
Thread.new(@worker_write) do |io|
|
285
336
|
Puma.set_thread_name "stat payload"
|
286
|
-
base_payload = "p#{Process.pid}"
|
287
337
|
|
288
338
|
while true
|
289
339
|
sleep Const::WORKER_CHECK_INTERVAL
|
290
340
|
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
|
341
|
+
require 'json'
|
342
|
+
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
297
343
|
rescue IOError
|
298
344
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
299
345
|
break
|
@@ -301,11 +347,11 @@ module Puma
|
|
301
347
|
end
|
302
348
|
end
|
303
349
|
|
304
|
-
server.run.join
|
350
|
+
server.run.join while restart_server.pop
|
305
351
|
|
306
352
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
307
353
|
# exiting until any background operations are completed
|
308
|
-
@launcher.config.run_hooks :before_worker_shutdown, index
|
354
|
+
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
309
355
|
ensure
|
310
356
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
311
357
|
@worker_write.close
|
@@ -352,18 +398,58 @@ module Puma
|
|
352
398
|
# the master process.
|
353
399
|
def stats
|
354
400
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
355
|
-
|
356
|
-
|
357
|
-
|
401
|
+
worker_status = @workers.map do |w|
|
402
|
+
{
|
403
|
+
started_at: w.started_at.utc.iso8601,
|
404
|
+
pid: w.pid,
|
405
|
+
index: w.index,
|
406
|
+
phase: w.phase,
|
407
|
+
booted: w.booted?,
|
408
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
409
|
+
last_status: w.last_status,
|
410
|
+
}
|
411
|
+
end
|
412
|
+
|
413
|
+
{
|
414
|
+
started_at: @started_at.utc.iso8601,
|
415
|
+
workers: @workers.size,
|
416
|
+
phase: @phase,
|
417
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
418
|
+
old_workers: old_worker_count,
|
419
|
+
worker_status: worker_status,
|
420
|
+
}
|
358
421
|
end
|
359
422
|
|
360
423
|
def preload?
|
361
424
|
@options[:preload_app]
|
362
425
|
end
|
363
426
|
|
427
|
+
# @version 5.0.0
|
428
|
+
def fork_worker!
|
429
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
430
|
+
worker.phase += 1
|
431
|
+
end
|
432
|
+
phased_restart
|
433
|
+
end
|
434
|
+
|
364
435
|
# We do this in a separate method to keep the lambda scope
|
365
436
|
# of the signals handlers as small as possible.
|
366
437
|
def setup_signals
|
438
|
+
if @options[:fork_worker]
|
439
|
+
Signal.trap "SIGURG" do
|
440
|
+
fork_worker!
|
441
|
+
end
|
442
|
+
|
443
|
+
# Auto-fork after the specified number of requests.
|
444
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
445
|
+
@launcher.events.register(:ping!) do |w|
|
446
|
+
fork_worker! if w.index == 0 &&
|
447
|
+
w.phase == 0 &&
|
448
|
+
w.last_status[:requests_count] >= fork_requests
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
367
453
|
Signal.trap "SIGCHLD" do
|
368
454
|
wakeup!
|
369
455
|
end
|
@@ -447,12 +533,11 @@ module Puma
|
|
447
533
|
#
|
448
534
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
449
535
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
end
|
536
|
+
# Separate pipe used by worker 0 to receive commands to
|
537
|
+
# fork new worker processes.
|
538
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
539
|
+
|
540
|
+
log "Use Ctrl-C to stop"
|
456
541
|
|
457
542
|
redirect_io
|
458
543
|
|
@@ -464,7 +549,8 @@ module Puma
|
|
464
549
|
|
465
550
|
@master_read, @worker_write = read, @wakeup
|
466
551
|
|
467
|
-
@launcher.config.run_hooks :before_fork, nil
|
552
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
553
|
+
nakayoshi_gc
|
468
554
|
|
469
555
|
spawn_workers
|
470
556
|
|
@@ -475,8 +561,6 @@ module Puma
|
|
475
561
|
@launcher.events.fire_on_booted!
|
476
562
|
|
477
563
|
begin
|
478
|
-
force_check = false
|
479
|
-
|
480
564
|
while @status == :run
|
481
565
|
begin
|
482
566
|
if @phased_restart
|
@@ -484,34 +568,39 @@ module Puma
|
|
484
568
|
@phased_restart = false
|
485
569
|
end
|
486
570
|
|
487
|
-
check_workers
|
571
|
+
check_workers
|
488
572
|
|
489
|
-
|
490
|
-
|
491
|
-
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
573
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
492
574
|
|
493
575
|
if res
|
494
576
|
req = read.read_nonblock(1)
|
495
577
|
|
578
|
+
@next_check = Time.now if req == "!"
|
496
579
|
next if !req || req == "!"
|
497
580
|
|
498
581
|
result = read.gets
|
499
582
|
pid = result.to_i
|
500
583
|
|
584
|
+
if req == "b" || req == "f"
|
585
|
+
pid, idx = result.split(':').map(&:to_i)
|
586
|
+
w = @workers.find {|x| x.index == idx}
|
587
|
+
w.pid = pid if w.pid.nil?
|
588
|
+
end
|
589
|
+
|
501
590
|
if w = @workers.find { |x| x.pid == pid }
|
502
591
|
case req
|
503
592
|
when "b"
|
504
593
|
w.boot!
|
505
594
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
506
|
-
|
595
|
+
@next_check = Time.now
|
507
596
|
when "e"
|
508
597
|
# external term, see worker method, Signal.trap "SIGTERM"
|
509
598
|
w.instance_variable_set :@term, true
|
510
599
|
when "t"
|
511
600
|
w.term unless w.term?
|
512
|
-
force_check = true
|
513
601
|
when "p"
|
514
602
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
603
|
+
@launcher.events.fire(:ping!, w)
|
515
604
|
end
|
516
605
|
else
|
517
606
|
log "! Out-of-sync worker list, no #{pid} worker"
|
@@ -538,6 +627,7 @@ module Puma
|
|
538
627
|
# `#term` if needed
|
539
628
|
def wait_workers
|
540
629
|
@workers.reject! do |w|
|
630
|
+
next false if w.pid.nil?
|
541
631
|
begin
|
542
632
|
if Process.wait(w.pid, Process::WNOHANG)
|
543
633
|
true
|
@@ -546,9 +636,36 @@ module Puma
|
|
546
636
|
nil
|
547
637
|
end
|
548
638
|
rescue Errno::ECHILD
|
549
|
-
|
639
|
+
begin
|
640
|
+
Process.kill(0, w.pid)
|
641
|
+
false # child still alive, but has another parent
|
642
|
+
rescue Errno::ESRCH, Errno::EPERM
|
643
|
+
true # child is already terminated
|
644
|
+
end
|
550
645
|
end
|
551
646
|
end
|
552
647
|
end
|
648
|
+
|
649
|
+
# @version 5.0.0
|
650
|
+
def timeout_workers
|
651
|
+
@workers.each do |w|
|
652
|
+
if !w.term? && w.ping_timeout <= Time.now
|
653
|
+
log "! Terminating timed out worker: #{w.pid}"
|
654
|
+
w.kill
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
# @version 5.0.0
|
660
|
+
def nakayoshi_gc
|
661
|
+
return unless @options[:nakayoshi_fork]
|
662
|
+
log "! Promoting existing objects to old generation..."
|
663
|
+
4.times { GC.start(full_mark: false) }
|
664
|
+
if GC.respond_to?(:compact)
|
665
|
+
log "! Compacting..."
|
666
|
+
GC.compact
|
667
|
+
end
|
668
|
+
log "! Friendly fork preparation complete."
|
669
|
+
end
|
553
670
|
end
|
554
671
|
end
|