puma 8.0.3-java → 8.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +115 -0
  3. data/README.md +20 -5
  4. data/docs/6.0-Upgrade.md +1 -1
  5. data/docs/8.0-Upgrade.md +2 -2
  6. data/docs/deployment.md +3 -3
  7. data/docs/fork_worker.md +34 -4
  8. data/docs/kubernetes.md +3 -3
  9. data/docs/plugins.md +2 -2
  10. data/docs/puma_support.md +78 -0
  11. data/docs/rails_dev_mode.md +2 -2
  12. data/docs/restart.md +4 -3
  13. data/docs/signals.md +2 -2
  14. data/docs/stats.md +5 -5
  15. data/docs/systemd.md +4 -2
  16. data/docs/testing_benchmarks_local_files.md +1 -1
  17. data/docs/testing_test_rackup_ci_files.md +2 -2
  18. data/ext/puma_http11/extconf.rb +2 -0
  19. data/ext/puma_http11/http11_parser.c +6 -2
  20. data/ext/puma_http11/http11_parser_common.rl +4 -1
  21. data/ext/puma_http11/mini_ssl.c +8 -4
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +36 -36
  23. data/ext/puma_http11/puma_http11.c +24 -0
  24. data/lib/puma/binder.rb +29 -0
  25. data/lib/puma/cli.rb +6 -0
  26. data/lib/puma/client.rb +75 -29
  27. data/lib/puma/client_env.rb +32 -18
  28. data/lib/puma/cluster/worker.rb +109 -33
  29. data/lib/puma/cluster/worker_handle.rb +16 -0
  30. data/lib/puma/cluster.rb +173 -44
  31. data/lib/puma/commonlogger.rb +3 -3
  32. data/lib/puma/configuration.rb +22 -3
  33. data/lib/puma/const.rb +11 -9
  34. data/lib/puma/dsl.rb +265 -91
  35. data/lib/puma/launcher.rb +6 -4
  36. data/lib/puma/minissl.rb +1 -1
  37. data/lib/puma/plugin/systemd.rb +1 -1
  38. data/lib/puma/puma_http11.jar +0 -0
  39. data/lib/puma/response.rb +13 -5
  40. data/lib/puma/runner.rb +5 -1
  41. data/lib/puma/sd_notify.rb +4 -1
  42. data/lib/puma/server.rb +45 -20
  43. data/lib/puma/thread_pool.rb +12 -1
  44. data/lib/puma.rb +1 -1
  45. metadata +3 -2
@@ -26,6 +26,7 @@ module Puma
26
26
  @app = app
27
27
  @server = nil
28
28
  @hook_data = {}
29
+ @worker_pids = []
29
30
  end
30
31
 
31
32
  def run
@@ -72,12 +73,7 @@ module Puma
72
73
 
73
74
  if fork_worker
74
75
  restart_server.clear
75
- worker_pids = []
76
- Signal.trap "SIGCHLD" do
77
- wakeup! if worker_pids.reject! do |p|
78
- Process.wait(p, Process::WNOHANG) rescue true
79
- end
80
- end
76
+ trap_sigchld
81
77
 
82
78
  Thread.new do
83
79
  Puma.set_thread_name "wrkr fork"
@@ -94,13 +90,24 @@ module Puma
94
90
  elsif idx == 0 # restart server
95
91
  restart_server << true << false
96
92
  else # fork worker
97
- worker_pids << pid = spawn_worker(idx)
93
+ pid = spawn_worker(idx)
98
94
  @worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n" rescue nil
99
95
  end
100
96
  end
101
97
  end
102
98
  end
103
99
 
100
+ mold = false
101
+
102
+ if @options[:mold_worker]
103
+ Signal.trap "SIGURG" do
104
+ mold = true
105
+ restart_server.clear
106
+ @server.begin_restart
107
+ restart_server << false
108
+ end
109
+ end
110
+
104
111
  Signal.trap "SIGTERM" do
105
112
  @worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
106
113
  restart_server.clear
@@ -117,38 +124,24 @@ module Puma
117
124
 
118
125
  while restart_server.pop
119
126
  server_thread = @server.run
127
+ # A promotion received before the server was running had no effect.
128
+ @server.begin_restart if mold
120
129
 
121
130
  if @log_writer.debug? && index == 0
122
131
  debug_loaded_extensions "Loaded Extensions - worker 0:"
123
132
  end
124
133
 
125
- stat_thread ||= Thread.new(@worker_write) do |io|
126
- Puma.set_thread_name "stat pld"
127
- base_payload = "#{PIPE_PING}#{Process.pid}"
128
-
129
- while true
130
- begin
131
- payload = base_payload.dup
132
-
133
- hsh = @server.stats
134
- hsh.each do |k, v|
135
- payload << %Q! "#{k}":#{v || 0},!
136
- end
137
- # sub call properly adds 'closing' string
138
- io << payload.sub(/,\z/, " }\n")
139
- @server.reset_max
140
- rescue IOError
141
- break
142
- end
143
- sleep @options[:worker_check_interval]
144
- end
145
- end
134
+ start_stat_thread
146
135
  server_thread.join
147
136
  end
148
137
 
149
- # Invoke any worker shutdown hooks so they can prevent the worker
150
- # exiting until any background operations are completed
151
- @config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
138
+ if mold
139
+ run_mold
140
+ else
141
+ # Invoke any worker shutdown hooks so they can prevent the worker
142
+ # exiting until any background operations are completed
143
+ @config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
144
+ end
152
145
  ensure
153
146
  @worker_write << "#{PIPE_TERM}#{Process.pid}\n" rescue nil
154
147
  @worker_write.close
@@ -156,15 +149,97 @@ module Puma
156
149
 
157
150
  private
158
151
 
152
+ def start_stat_thread
153
+ @stat_thread ||= Thread.new(@worker_write) do |io|
154
+ Puma.set_thread_name "stat pld"
155
+ base_payload = "#{PIPE_PING}#{Process.pid}"
156
+
157
+ while true
158
+ begin
159
+ payload = base_payload.dup
160
+
161
+ hsh = @server.stats
162
+ hsh.each do |k, v|
163
+ payload << %Q! "#{k}":#{v || 0},!
164
+ end
165
+ # sub call properly adds 'closing' string
166
+ io << payload.sub(/,\z/, " }\n")
167
+ @server.reset_max
168
+ rescue SystemCallError, IOError
169
+ break
170
+ end
171
+ sleep @options[:worker_check_interval]
172
+ end
173
+ end
174
+ end
175
+
176
+ def run_mold
177
+ $0 = $0.sub("cluster worker", "mold")
178
+
179
+ retired = false
180
+ stopping = false
181
+ stop_read, stop_write = @mold_stop_pipe = Puma::Util.pipe
182
+ Signal.trap "SIGURG" do
183
+ retired = true
184
+ stop_write.write_nonblock(".", exception: false)
185
+ end
186
+ Signal.trap "SIGTERM" do
187
+ @worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
188
+ stopping = true
189
+ stop_write.write_nonblock(".", exception: false)
190
+ end
191
+
192
+ trap_sigchld
193
+
194
+ @config.run_hooks(:after_mold_promotion, index, @log_writer, @hook_data)
195
+ start_stat_thread
196
+ @worker_write << "#{PIPE_MOLD}#{Process.pid}\n"
197
+
198
+ loop do
199
+ IO.select([@fork_pipe, stop_read])
200
+ # A retired mold must not read indices meant for its replacement.
201
+ break if retired || stopping
202
+
203
+ idx = @fork_pipe.sysread(MOLD_FORK_BYTES).unpack1(MOLD_FORK_FORMAT)
204
+ pid = spawn_worker(idx)
205
+ @worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n"
206
+ rescue EOFError
207
+ break
208
+ end
209
+
210
+ if retired
211
+ # Stay alive until stopped, so the workers this mold forked are
212
+ # reaped and the master is woken as they exit.
213
+ stop_read.read(1) until stopping
214
+ end
215
+
216
+ @config.run_hooks(:before_mold_shutdown, index, @log_writer, @hook_data)
217
+ end
218
+
219
+ def trap_sigchld
220
+ Signal.trap "SIGCHLD" do
221
+ wakeup! if @worker_pids.reject! do |p|
222
+ Process.wait(p, Process::WNOHANG) rescue true
223
+ end
224
+ end
225
+ end
226
+
159
227
  def spawn_worker(idx)
160
228
  @config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
161
229
 
230
+ pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
231
+ if @options[:mold_worker]
232
+ pipes[:fork_pipe] = @fork_pipe
233
+ pipes[:wakeup] = @wakeup
234
+ end
235
+
162
236
  pid = fork do
237
+ @mold_stop_pipe&.each(&:close)
238
+
163
239
  new_worker = Worker.new index: idx,
164
240
  master: master,
165
241
  launcher: @launcher,
166
- pipes: { check_pipe: @check_pipe,
167
- worker_write: @worker_write },
242
+ pipes: pipes,
168
243
  app: @app
169
244
  new_worker.run
170
245
  end
@@ -175,6 +250,7 @@ module Puma
175
250
  exit! 1
176
251
  end
177
252
 
253
+ @worker_pids << pid
178
254
  @config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
179
255
  pid
180
256
  end
@@ -25,6 +25,7 @@ module Puma
25
25
  @last_checkin = Time.now
26
26
  @last_status = {}
27
27
  @term = false
28
+ @mold = false
28
29
  @worker_max = Array.new WORKER_MAX_KEYS.length, 0
29
30
  end
30
31
 
@@ -54,6 +55,21 @@ module Puma
54
55
  @term
55
56
  end
56
57
 
58
+ def mold?
59
+ @mold
60
+ end
61
+
62
+ def mold!
63
+ @mold = true
64
+ Process.kill "URG", @pid
65
+ rescue Errno::ESRCH
66
+ end
67
+
68
+ def retire!
69
+ Process.kill "URG", @pid
70
+ rescue Errno::ESRCH
71
+ end
72
+
57
73
  def ping!(status)
58
74
  hsh = {}
59
75
  k, v = nil, nil
data/lib/puma/cluster.rb CHANGED
@@ -15,6 +15,9 @@ module Puma
15
15
  # via the `spawn_workers` method call. Each worker will have it's own
16
16
  # instance of a `Puma::Server`.
17
17
  class Cluster < Runner
18
+ MOLD_FORK_FORMAT = "L"
19
+ MOLD_FORK_BYTES = 4
20
+
18
21
  def initialize(launcher)
19
22
  super(launcher)
20
23
 
@@ -24,6 +27,11 @@ module Puma
24
27
 
25
28
  @worker_max = [] # keeps track of 'max' stat values
26
29
  @pending_phased_restart = false
30
+ @promoted_mold = nil
31
+ @mold = nil
32
+ @retired_molds = []
33
+ @mold_ready = false
34
+ @mold_threshold_index = 0
27
35
  end
28
36
 
29
37
  # Returns the list of cluster worker handles.
@@ -32,12 +40,12 @@ module Puma
32
40
 
33
41
  def stop_workers
34
42
  log "- Gracefully shutting down workers..."
35
- @workers.each { |x| x.term }
43
+ (@workers + molds).each { |x| x.term }
36
44
 
37
45
  begin
38
46
  loop do
39
47
  wait_workers
40
- break if @workers.reject {|w| w.pid.nil?}.empty?
48
+ break if @workers.reject {|w| w.pid.nil?}.empty? && molds.empty?
41
49
  sleep 0.2
42
50
  end
43
51
  rescue Interrupt
@@ -69,7 +77,7 @@ module Puma
69
77
 
70
78
  def spawn_workers
71
79
  diff = @options[:workers] - @workers.size
72
- return if diff < 1
80
+ return if diff < 1 || @promoted_mold
73
81
 
74
82
  master = Process.pid
75
83
  if @options[:fork_worker]
@@ -82,6 +90,9 @@ module Puma
82
90
  if @options[:fork_worker] && idx != 0
83
91
  @fork_writer << "#{idx}\n"
84
92
  pid = nil
93
+ elsif @mold
94
+ @fork_writer << [idx].pack(MOLD_FORK_FORMAT)
95
+ pid = nil
85
96
  else
86
97
  pid = spawn_worker(idx, master)
87
98
  end
@@ -168,6 +179,8 @@ module Puma
168
179
  end
169
180
 
170
181
  def all_workers_idle_timed_out?
182
+ return false if @pending_phased_restart || @workers.empty?
183
+
171
184
  (@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
172
185
  end
173
186
 
@@ -206,6 +219,10 @@ module Puma
206
219
  t.map!(&:ping_timeout)
207
220
 
208
221
  @next_check = [t.min, @next_check].compact.min
222
+ if @options[:fork_worker] && @workers.any?(&:term?)
223
+ # Orphaned workers cannot wake the master through worker 0 as they exit.
224
+ @next_check = [@next_check, Time.now + 0.1].min
225
+ end
209
226
  end
210
227
 
211
228
  def worker(index, master)
@@ -216,7 +233,7 @@ module Puma
216
233
  @fork_writer.close
217
234
 
218
235
  pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
219
- if @options[:fork_worker]
236
+ if refork?
220
237
  pipes[:fork_pipe] = @fork_pipe
221
238
  pipes[:wakeup] = @wakeup
222
239
  end
@@ -299,14 +316,22 @@ module Puma
299
316
  @options[:preload_app]
300
317
  end
301
318
 
319
+ def refork?
320
+ @options[:fork_worker] || @options[:mold_worker]
321
+ end
322
+
302
323
  # @version 5.0.0
303
324
  def fork_worker!
304
325
  if (worker = worker_at 0)
305
- worker.phase += 1
326
+ worker.phase = @phase + 1
306
327
  end
307
328
  phased_restart(true)
308
329
  end
309
330
 
331
+ def mold_worker!
332
+ phased_restart(true)
333
+ end
334
+
310
335
  # We do this in a separate method to keep the lambda scope
311
336
  # of the signals handlers as small as possible.
312
337
  def setup_signals
@@ -325,6 +350,24 @@ module Puma
325
350
  end
326
351
  end
327
352
 
353
+ if @options[:mold_worker]
354
+ Signal.trap "SIGURG" do
355
+ mold_worker!
356
+ end
357
+
358
+ @events.register(:ping!) do |w|
359
+ threshold = @options[:mold_worker][@mold_threshold_index]
360
+ next unless threshold &&
361
+ !w.mold? &&
362
+ !@pending_phased_restart &&
363
+ w.last_status[:requests_count] >= threshold &&
364
+ all_workers_in_phase?
365
+
366
+ @mold_threshold_index += 1
367
+ mold_worker!
368
+ end
369
+ end
370
+
328
371
  Signal.trap "SIGCHLD" do
329
372
  wakeup!
330
373
  end
@@ -375,7 +418,7 @@ module Puma
375
418
 
376
419
  before = Thread.list.reject(&fork_safe)
377
420
 
378
- log "* Restarts: (\u2714) hot (\u2716) phased (#{@options[:fork_worker] ? "\u2714" : "\u2716"}) refork"
421
+ log "* Restarts: (\u2714) hot (\u2716) phased (#{refork? ? "\u2714" : "\u2716"}) refork"
379
422
  log "* Preloading application"
380
423
  load_and_bind
381
424
 
@@ -393,7 +436,7 @@ module Puma
393
436
  end
394
437
  end
395
438
  else
396
- log "* Restarts: (\u2714) hot (\u2714) phased (#{@options[:fork_worker] ? "\u2714" : "\u2716"}) refork"
439
+ log "* Restarts: (\u2714) hot (\u2714) phased (#{refork? ? "\u2714" : "\u2716"}) refork"
397
440
 
398
441
  unless @config.app_configured?
399
442
  error "No application configured, nothing to run"
@@ -437,6 +480,9 @@ module Puma
437
480
 
438
481
  @config.run_hooks(:before_fork, nil, @log_writer)
439
482
 
483
+ # Reparent workers to the master if their mold exits.
484
+ Puma.enable_child_subreaper if @options[:mold_worker]
485
+
440
486
  spawn_workers
441
487
 
442
488
  Signal.trap "SIGINT" do
@@ -446,7 +492,6 @@ module Puma
446
492
  begin
447
493
  booted = false
448
494
  in_phased_restart = false
449
- workers_not_booted = @options[:workers]
450
495
 
451
496
  while @status == :run
452
497
  begin
@@ -455,15 +500,11 @@ module Puma
455
500
  break
456
501
  end
457
502
 
458
- if @pending_phased_restart
503
+ if @pending_phased_restart && prepare_phased_restart
459
504
  start_phased_restart(@pending_phased_restart == :refork)
460
505
 
461
506
  in_phased_restart = @pending_phased_restart
462
507
  @pending_phased_restart = false
463
-
464
- workers_not_booted = @options[:workers]
465
- # worker 0 is not restarted on refork
466
- workers_not_booted -= 1 if in_phased_restart == :refork
467
508
  end
468
509
 
469
510
  check_workers(in_phased_restart == :refork)
@@ -479,20 +520,28 @@ module Puma
479
520
 
480
521
  result = read.gets
481
522
  pid = result.to_i
523
+ w = (@workers + molds).find { |x| x.pid == pid }
482
524
 
483
- if req == PIPE_BOOT || req == PIPE_FORK
525
+ if !w && (req == PIPE_BOOT || req == PIPE_FORK)
484
526
  pid, idx = result.split(':').map(&:to_i)
485
527
  w = worker_at idx
486
- w.pid = pid if w.pid.nil?
528
+ if w && w.pid.nil?
529
+ w.pid = pid
530
+ else
531
+ w = nil
532
+ begin
533
+ Process.kill "KILL", pid
534
+ rescue Errno::ESRCH
535
+ end
536
+ end
487
537
  end
488
538
 
489
- if w = @workers.find { |x| x.pid == pid }
539
+ if w
490
540
  case req
491
541
  when PIPE_BOOT
492
542
  w.boot!
493
543
  log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
494
544
  @next_check = Time.now
495
- workers_not_booted -= 1
496
545
  when PIPE_EXTERNAL_TERM
497
546
  # external term, see worker method, Signal.trap "SIGTERM"
498
547
  w.term!
@@ -503,7 +552,7 @@ module Puma
503
552
  w.ping!(status)
504
553
  @events.fire(:ping!, w)
505
554
 
506
- if in_phased_restart && @options[:fork_worker] && workers_not_booted.positive? && w0 = worker_at(0)
555
+ if in_phased_restart && @options[:fork_worker] && w0 = worker_at(0)
507
556
  w0.ping!(status)
508
557
  @events.fire(:ping!, w0)
509
558
  end
@@ -519,13 +568,17 @@ module Puma
519
568
  else
520
569
  idle_workers[pid] = true
521
570
  end
571
+ when PIPE_MOLD
572
+ activate_mold(w)
573
+ @next_check = Time.now
522
574
  end
523
575
  else
524
576
  log "! Out-of-sync worker list, no #{pid} worker"
525
577
  end
526
578
  end
527
579
 
528
- if in_phased_restart && workers_not_booted.zero?
580
+ if in_phased_restart && @workers.size == @options[:workers] && all_workers_booted? && all_workers_in_phase?
581
+ @retired_molds.each(&:term)
529
582
  @events.fire_after_booted!
530
583
  debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
531
584
  in_phased_restart = false
@@ -574,43 +627,119 @@ module Puma
574
627
  end
575
628
  end
576
629
 
630
+ fork_worker_exited = false
577
631
  @workers.reject! do |w|
578
- next false if w.pid.nil?
579
- begin
580
- # We may need to check the PID individually because:
581
- # 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
582
- # `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
583
- # 2. When `fork_worker` is enabled, some worker may not be direct children,
584
- # but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
585
- if (status = reaped_children.delete(w.pid) || Process.wait2(w.pid, Process::WNOHANG)&.last)
586
- w.process_status = status
587
- @config.run_hooks(:after_worker_shutdown, w, @log_writer)
588
- true
589
- else
590
- w.term if w.term?
591
- nil
592
- end
593
- rescue Errno::ECHILD
594
- begin
595
- Process.kill(0, w.pid)
596
- # child still alive but has another parent (e.g., using fork_worker)
597
- w.term if w.term?
598
- false
599
- rescue Errno::ESRCH, Errno::EPERM
600
- true # child is already terminated
601
- end
632
+ next w.term? if w.pid.nil?
633
+
634
+ status = reap(w, reaped_children)
635
+ fork_worker_exited = true if status && @options[:fork_worker] && w.index == 0
636
+ if status.is_a?(Process::Status)
637
+ w.process_status = status
638
+ @config.run_hooks(:after_worker_shutdown, w, @log_writer)
602
639
  end
640
+ status
603
641
  end
604
642
 
643
+ clear_pending_workers if fork_worker_exited
644
+ @promoted_mold = nil if @promoted_mold && reap(@promoted_mold, reaped_children)
645
+ if @mold && reap(@mold, reaped_children)
646
+ @mold = nil
647
+ clear_pending_workers
648
+ end
649
+ @retired_molds.reject! { |m| reap(m, reaped_children) }
650
+
605
651
  # Log unknown children
606
652
  reaped_children.each do |pid, status|
607
653
  log "! reaped unknown child process pid=#{pid} status=#{status}"
608
654
  end
609
655
  end
610
656
 
657
+ def clear_pending_workers
658
+ @workers.reject! { |w| w.pid.nil? }
659
+ while @fork_pipe.read_nonblock(4096, exception: false).is_a?(String)
660
+ end
661
+ end
662
+
663
+ # Returns the process status once the process has exited, true if it has
664
+ # exited but its status cannot be collected, or nil if it is still running.
665
+ def reap(handle, reaped_children)
666
+ # We may need to check the PID individually because:
667
+ # 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
668
+ # `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
669
+ # 2. When `fork_worker` or `mold_worker` is enabled, some workers may not be direct children,
670
+ # but grandchildren. Because of this they won't be reaped by `Process.wait2(-1)`.
671
+ status = reaped_children.delete(handle.pid) || Process.wait2(handle.pid, Process::WNOHANG)&.last
672
+ handle.term if !status && handle.term?
673
+ status
674
+ rescue Errno::ECHILD
675
+ begin
676
+ Process.kill(0, handle.pid)
677
+ # child still alive but has another parent (e.g., using fork_worker)
678
+ handle.term if handle.term?
679
+ nil
680
+ rescue Errno::ESRCH, Errno::EPERM
681
+ true # child is already terminated
682
+ end
683
+ end
684
+
685
+ def molds
686
+ [@promoted_mold, @mold, *@retired_molds].compact
687
+ end
688
+
689
+ # Returns true once workers can be restarted. With `mold_worker`, a refork
690
+ # first promotes a new mold and waits for it, while a phased restart
691
+ # retires all molds so workers are forked from the master.
692
+ def prepare_phased_restart
693
+ return true unless @options[:mold_worker]
694
+
695
+ if @pending_phased_restart != :refork
696
+ retire_molds
697
+ @mold_ready = false
698
+ @mold_threshold_index = 0
699
+ true
700
+ elsif @mold_ready
701
+ @mold_ready = false
702
+ true
703
+ else
704
+ promote_mold unless @promoted_mold
705
+ false
706
+ end
707
+ end
708
+
709
+ def promote_mold
710
+ candidate = @workers.select { |w| w.booted? && !w.term? }
711
+ .max_by { |w| w.last_status[:requests_count].to_i }
712
+ return unless candidate
713
+
714
+ log "- Promoting worker #{candidate.index} (PID: #{candidate.pid}) to mold"
715
+ @workers.delete candidate
716
+ @promoted_mold = candidate
717
+ candidate.mold!
718
+ end
719
+
720
+ def activate_mold(mold)
721
+ return unless mold == @promoted_mold
722
+
723
+ log "- Mold (PID: #{mold.pid}) ready"
724
+ @promoted_mold = nil
725
+ retire_molds
726
+ @mold = mold
727
+ @mold_ready = true
728
+ end
729
+
730
+ # A promoted mold has forked no workers, so it is stopped now. An active
731
+ # mold stops forking, and is stopped once the restart completes.
732
+ def retire_molds
733
+ @promoted_mold&.term
734
+ @mold&.retire!
735
+ @retired_molds.concat [@promoted_mold, @mold].compact
736
+ @promoted_mold = nil
737
+ @mold = nil
738
+ end
739
+
611
740
  # @version 5.0.0
612
741
  def timeout_workers
613
- @workers.each do |w|
742
+ (@workers + molds).each do |w|
614
743
  if !w.term? && w.ping_timeout <= Time.now
615
744
  details = if w.booted?
616
745
  "(Worker #{w.index} failed to check in within #{@options[:worker_timeout]} seconds)"
@@ -73,7 +73,7 @@ module Puma
73
73
  env[SERVER_PROTOCOL],
74
74
  now - began_at ]
75
75
 
76
- write(msg)
76
+ write(env, msg)
77
77
  end
78
78
 
79
79
  def log(env, status, header, began_at)
@@ -92,10 +92,10 @@ module Puma
92
92
  length,
93
93
  now - began_at ]
94
94
 
95
- write(msg)
95
+ write(env, msg)
96
96
  end
97
97
 
98
- def write(msg)
98
+ def write(env, msg)
99
99
  logger = @logger || env['rack.errors']
100
100
 
101
101
  # Standard library logger doesn't support write but it supports << which actually