puma 3.12.0 → 5.3.1

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.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1413 -439
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +262 -87
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma/app/status.rb +68 -49
  37. data/lib/puma/binder.rb +197 -144
  38. data/lib/puma/cli.rb +17 -15
  39. data/lib/puma/client.rb +257 -226
  40. data/lib/puma/cluster/worker.rb +176 -0
  41. data/lib/puma/cluster/worker_handle.rb +90 -0
  42. data/lib/puma/cluster.rb +223 -212
  43. data/lib/puma/commonlogger.rb +4 -2
  44. data/lib/puma/configuration.rb +58 -51
  45. data/lib/puma/const.rb +41 -19
  46. data/lib/puma/control_cli.rb +117 -73
  47. data/lib/puma/detect.rb +26 -3
  48. data/lib/puma/dsl.rb +531 -123
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +57 -31
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +2 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +182 -70
  55. data/lib/puma/minissl/context_builder.rb +79 -0
  56. data/lib/puma/minissl.rb +149 -48
  57. data/lib/puma/null_io.rb +15 -1
  58. data/lib/puma/plugin/tmp_restart.rb +2 -0
  59. data/lib/puma/plugin.rb +8 -12
  60. data/lib/puma/queue_close.rb +26 -0
  61. data/lib/puma/rack/builder.rb +4 -5
  62. data/lib/puma/rack/urlmap.rb +2 -0
  63. data/lib/puma/rack_default.rb +2 -0
  64. data/lib/puma/reactor.rb +87 -316
  65. data/lib/puma/request.rb +456 -0
  66. data/lib/puma/runner.rb +33 -52
  67. data/lib/puma/server.rb +288 -679
  68. data/lib/puma/single.rb +13 -67
  69. data/lib/puma/state_file.rb +10 -3
  70. data/lib/puma/systemd.rb +46 -0
  71. data/lib/puma/thread_pool.rb +131 -81
  72. data/lib/puma/util.rb +14 -6
  73. data/lib/puma.rb +54 -0
  74. data/lib/rack/handler/puma.rb +8 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +45 -29
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -23
  82. data/lib/puma/daemon_ext.rb +0 -31
  83. data/lib/puma/delegation.rb +0 -11
  84. data/lib/puma/java_io_buffer.rb +0 -45
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -39
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/cluster.rb CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/runner'
2
4
  require 'puma/util'
3
5
  require 'puma/plugin'
6
+ require 'puma/cluster/worker_handle'
7
+ require 'puma/cluster/worker'
4
8
 
5
9
  require 'time'
6
10
 
@@ -9,24 +13,17 @@ module Puma
9
13
  # to boot and serve a Ruby application when puma "workers" are needed
10
14
  # i.e. when using multi-processes. For example `$ puma -w 5`
11
15
  #
12
- # At the core of this class is running an instance of `Puma::Server` which
13
- # gets created via the `start_server` method from the `Puma::Runner` class
14
- # that this inherits from.
15
- #
16
16
  # An instance of this class will spawn the number of processes passed in
17
17
  # via the `spawn_workers` method call. Each worker will have it's own
18
18
  # instance of a `Puma::Server`.
19
19
  class Cluster < Runner
20
- WORKER_CHECK_INTERVAL = 5
21
-
22
20
  def initialize(cli, events)
23
21
  super cli, events
24
22
 
25
23
  @phase = 0
26
24
  @workers = []
27
- @next_check = nil
25
+ @next_check = Time.now
28
26
 
29
- @phased_state = :idle
30
27
  @phased_restart = false
31
28
  end
32
29
 
@@ -35,13 +32,18 @@ module Puma
35
32
  @workers.each { |x| x.term }
36
33
 
37
34
  begin
38
- @workers.each { |w| Process.waitpid(w.pid) }
35
+ loop do
36
+ wait_workers
37
+ break if @workers.reject {|w| w.pid.nil?}.empty?
38
+ sleep 0.2
39
+ end
39
40
  rescue Interrupt
40
41
  log "! Cancelled waiting for workers"
41
42
  end
42
43
  end
43
44
 
44
45
  def start_phased_restart
46
+ @events.fire_on_restart!
45
47
  @phase += 1
46
48
  log "- Starting phased worker restart, phase: #{@phase}"
47
49
 
@@ -58,98 +60,49 @@ module Puma
58
60
  @workers.each { |x| x.hup }
59
61
  end
60
62
 
61
- class Worker
62
- def initialize(idx, pid, phase, options)
63
- @index = idx
64
- @pid = pid
65
- @phase = phase
66
- @stage = :started
67
- @signal = "TERM"
68
- @options = options
69
- @first_term_sent = nil
70
- @last_checkin = Time.now
71
- @last_status = '{}'
72
- @dead = false
73
- end
74
-
75
- attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
76
-
77
- def booted?
78
- @stage == :booted
79
- end
80
-
81
- def boot!
82
- @last_checkin = Time.now
83
- @stage = :booted
84
- end
85
-
86
- def dead?
87
- @dead
88
- end
89
-
90
- def dead!
91
- @dead = true
92
- end
93
-
94
- def ping!(status)
95
- @last_checkin = Time.now
96
- @last_status = status
97
- end
98
-
99
- def ping_timeout?(which)
100
- Time.now - @last_checkin > which
101
- end
102
-
103
- def term
104
- begin
105
- if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
106
- @signal = "KILL"
107
- else
108
- @first_term_sent ||= Time.now
109
- end
110
-
111
- Process.kill @signal, @pid
112
- rescue Errno::ESRCH
113
- end
114
- end
115
-
116
- def kill
117
- Process.kill "KILL", @pid
118
- rescue Errno::ESRCH
119
- end
120
-
121
- def hup
122
- Process.kill "HUP", @pid
123
- rescue Errno::ESRCH
124
- end
125
- end
126
-
127
63
  def spawn_workers
128
64
  diff = @options[:workers] - @workers.size
129
65
  return if diff < 1
130
66
 
131
67
  master = Process.pid
68
+ if @options[:fork_worker]
69
+ @fork_writer << "-1\n"
70
+ end
132
71
 
133
72
  diff.times do
134
73
  idx = next_worker_index
135
- @launcher.config.run_hooks :before_worker_fork, idx
136
74
 
137
- pid = fork { worker(idx, master) }
138
- if !pid
139
- log "! Complete inability to spawn new workers detected"
140
- log "! Seppuku is the only choice."
141
- exit! 1
75
+ if @options[:fork_worker] && idx != 0
76
+ @fork_writer << "#{idx}\n"
77
+ pid = nil
78
+ else
79
+ pid = spawn_worker(idx, master)
142
80
  end
143
81
 
144
82
  debug "Spawned worker: #{pid}"
145
- @workers << Worker.new(idx, pid, @phase, @options)
83
+ @workers << WorkerHandle.new(idx, pid, @phase, @options)
84
+ end
146
85
 
147
- @launcher.config.run_hooks :after_worker_fork, idx
86
+ if @options[:fork_worker] &&
87
+ @workers.all? {|x| x.phase == @phase}
88
+
89
+ @fork_writer << "0\n"
148
90
  end
91
+ end
149
92
 
150
- if diff > 0
151
- @phased_state = :idle
93
+ # @version 5.0.0
94
+ def spawn_worker(idx, master)
95
+ @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
96
+
97
+ pid = fork { worker(idx, master) }
98
+ if !pid
99
+ log "! Complete inability to spawn new workers detected"
100
+ log "! Seppuku is the only choice."
101
+ exit! 1
152
102
  end
103
+
104
+ @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
105
+ pid
153
106
  end
154
107
 
155
108
  def cull_workers
@@ -162,11 +115,12 @@ module Puma
162
115
  debug "Workers to cull: #{workers_to_cull.inspect}"
163
116
 
164
117
  workers_to_cull.each do |worker|
165
- log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
118
+ log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
166
119
  worker.term
167
120
  end
168
121
  end
169
122
 
123
+ # @!attribute [r] next_worker_index
170
124
  def next_worker_index
171
125
  all_positions = 0...@options[:workers]
172
126
  occupied_positions = @workers.map { |w| w.index }
@@ -178,35 +132,13 @@ module Puma
178
132
  @workers.count { |w| !w.booted? } == 0
179
133
  end
180
134
 
181
- def check_workers(force=false)
182
- return if !force && @next_check && @next_check >= Time.now
183
-
184
- @next_check = Time.now + WORKER_CHECK_INTERVAL
185
-
186
- any = false
187
-
188
- @workers.each do |w|
189
- next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
190
- if w.ping_timeout?(@options[:worker_timeout])
191
- log "! Terminating timed out worker: #{w.pid}"
192
- w.kill
193
- any = true
194
- end
195
- end
196
-
197
- # If we killed any timed out workers, try to catch them
198
- # during this loop by giving the kernel time to kill them.
199
- sleep 1 if any
200
-
201
- while @workers.any?
202
- pid = Process.waitpid(-1, Process::WNOHANG)
203
- break unless pid
204
-
205
- @workers.delete_if { |w| w.pid == pid }
206
- end
135
+ def check_workers
136
+ return if @next_check >= Time.now
207
137
 
208
- @workers.delete_if(&:dead?)
138
+ @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
209
139
 
140
+ timeout_workers
141
+ wait_workers
210
142
  cull_workers
211
143
  spawn_workers
212
144
 
@@ -218,15 +150,18 @@ module Puma
218
150
  w = @workers.find { |x| x.phase != @phase }
219
151
 
220
152
  if w
221
- if @phased_state == :idle
222
- @phased_state = :waiting
223
- log "- Stopping #{w.pid} for phased upgrade..."
153
+ log "- Stopping #{w.pid} for phased upgrade..."
154
+ unless w.term?
155
+ w.term
156
+ log "- #{w.signal} sent to #{w.pid}..."
224
157
  end
225
-
226
- w.term
227
- log "- #{w.signal} sent to #{w.pid}..."
228
158
  end
229
159
  end
160
+
161
+ @next_check = [
162
+ @workers.reject(&:term?).map(&:ping_timeout).min,
163
+ @next_check
164
+ ].compact.min
230
165
  end
231
166
 
232
167
  def wakeup!
@@ -240,77 +175,25 @@ module Puma
240
175
  end
241
176
 
242
177
  def worker(index, master)
243
- title = "puma: cluster worker #{index}: #{master}"
244
- title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
245
- $0 = title
246
-
247
- Signal.trap "SIGINT", "IGNORE"
248
-
249
178
  @workers = []
179
+
250
180
  @master_read.close
251
181
  @suicide_pipe.close
182
+ @fork_writer.close
252
183
 
253
- Thread.new do
254
- IO.select [@check_pipe]
255
- log "! Detected parent died, dying"
256
- exit! 1
257
- end
258
-
259
- # If we're not running under a Bundler context, then
260
- # report the info about the context we will be using
261
- if !ENV['BUNDLE_GEMFILE']
262
- if File.exist?("Gemfile")
263
- log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
264
- elsif File.exist?("gems.rb")
265
- log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
266
- end
267
- end
268
-
269
- # Invoke any worker boot hooks so they can get
270
- # things in shape before booting the app.
271
- @launcher.config.run_hooks :before_worker_boot, index
272
-
273
- server = start_server
274
-
275
- Signal.trap "SIGTERM" do
276
- server.stop
277
- end
278
-
279
- begin
280
- @worker_write << "b#{Process.pid}\n"
281
- rescue SystemCallError, IOError
282
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
283
- STDERR.puts "Master seems to have exited, exiting."
284
- return
285
- end
286
-
287
- Thread.new(@worker_write) do |io|
288
- base_payload = "p#{Process.pid}"
289
-
290
- while true
291
- sleep WORKER_CHECK_INTERVAL
292
- begin
293
- b = server.backlog || 0
294
- r = server.running || 0
295
- t = server.pool_capacity || 0
296
- m = server.max_threads || 0
297
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
298
- io << payload
299
- rescue IOError
300
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
301
- break
302
- end
303
- end
184
+ pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
185
+ if @options[:fork_worker]
186
+ pipes[:fork_pipe] = @fork_pipe
187
+ pipes[:wakeup] = @wakeup
304
188
  end
305
189
 
306
- server.run.join
307
-
308
- # Invoke any worker shutdown hooks so they can prevent the worker
309
- # exiting until any background operations are completed
310
- @launcher.config.run_hooks :before_worker_shutdown, index
311
- ensure
312
- @worker_write << "t#{Process.pid}\n" rescue nil
313
- @worker_write.close
190
+ server = start_server if preload?
191
+ new_worker = Worker.new index: index,
192
+ master: master,
193
+ launcher: @launcher,
194
+ pipes: pipes,
195
+ server: server
196
+ new_worker.run
314
197
  end
315
198
 
316
199
  def restart
@@ -350,20 +233,63 @@ module Puma
350
233
  Dir.chdir dir
351
234
  end
352
235
 
236
+ # Inside of a child process, this will return all zeroes, as @workers is only populated in
237
+ # the master process.
238
+ # @!attribute [r] stats
353
239
  def stats
354
240
  old_worker_count = @workers.count { |w| w.phase != @phase }
355
- booted_worker_count = @workers.count { |w| w.booted? }
356
- worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
357
- %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
241
+ worker_status = @workers.map do |w|
242
+ {
243
+ started_at: w.started_at.utc.iso8601,
244
+ pid: w.pid,
245
+ index: w.index,
246
+ phase: w.phase,
247
+ booted: w.booted?,
248
+ last_checkin: w.last_checkin.utc.iso8601,
249
+ last_status: w.last_status,
250
+ }
251
+ end
252
+
253
+ {
254
+ started_at: @started_at.utc.iso8601,
255
+ workers: @workers.size,
256
+ phase: @phase,
257
+ booted_workers: worker_status.count { |w| w[:booted] },
258
+ old_workers: old_worker_count,
259
+ worker_status: worker_status,
260
+ }
358
261
  end
359
262
 
360
263
  def preload?
361
264
  @options[:preload_app]
362
265
  end
363
266
 
267
+ # @version 5.0.0
268
+ def fork_worker!
269
+ if (worker = @workers.find { |w| w.index == 0 })
270
+ worker.phase += 1
271
+ end
272
+ phased_restart
273
+ end
274
+
364
275
  # We do this in a separate method to keep the lambda scope
365
276
  # of the signals handlers as small as possible.
366
277
  def setup_signals
278
+ if @options[:fork_worker]
279
+ Signal.trap "SIGURG" do
280
+ fork_worker!
281
+ end
282
+
283
+ # Auto-fork after the specified number of requests.
284
+ if (fork_requests = @options[:fork_worker].to_i) > 0
285
+ @launcher.events.register(:ping!) do |w|
286
+ fork_worker! if w.index == 0 &&
287
+ w.phase == 0 &&
288
+ w.last_status[:requests_count] >= fork_requests
289
+ end
290
+ end
291
+ end
292
+
367
293
  Signal.trap "SIGCHLD" do
368
294
  wakeup!
369
295
  end
@@ -388,10 +314,13 @@ module Puma
388
314
  log "Early termination of worker"
389
315
  exit! 0
390
316
  else
317
+ @launcher.close_binder_listeners
318
+
391
319
  stop_workers
392
320
  stop
393
-
394
- raise SignalException, "SIGTERM"
321
+ @events.fire_on_stopped!
322
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
323
+ exit 0 # Clean exit, workers were stopped
395
324
  end
396
325
  end
397
326
  end
@@ -401,15 +330,25 @@ module Puma
401
330
 
402
331
  output_header "cluster"
403
332
 
404
- log "* Process workers: #{@options[:workers]}"
405
-
406
- before = Thread.list
333
+ # This is aligned with the output from Runner, see Runner#output_header
334
+ log "* Workers: #{@options[:workers]}"
407
335
 
408
336
  if preload?
337
+ # Threads explicitly marked as fork safe will be ignored. Used in Rails,
338
+ # but may be used by anyone. Note that we need to explicit
339
+ # Process::Waiter check here because there's a bug in Ruby 2.6 and below
340
+ # where calling thread_variable_get on a Process::Waiter will segfault.
341
+ # We can drop that clause once those versions of Ruby are no longer
342
+ # supported.
343
+ fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
344
+
345
+ before = Thread.list.reject(&fork_safe)
346
+
347
+ log "* Restarts: (\u2714) hot (\u2716) phased"
409
348
  log "* Preloading application"
410
349
  load_and_bind
411
350
 
412
- after = Thread.list
351
+ after = Thread.list.reject(&fork_safe)
413
352
 
414
353
  if after.size > before.size
415
354
  threads = (after - before)
@@ -423,7 +362,7 @@ module Puma
423
362
  end
424
363
  end
425
364
  else
426
- log "* Phased restart available"
365
+ log "* Restarts: (\u2714) hot (\u2714) phased"
427
366
 
428
367
  unless @launcher.config.app_configured?
429
368
  error "No application configured, nothing to run"
@@ -444,12 +383,13 @@ module Puma
444
383
  #
445
384
  @check_pipe, @suicide_pipe = Puma::Util.pipe
446
385
 
447
- if daemon?
448
- log "* Daemonizing..."
449
- Process.daemon(true)
450
- else
451
- log "Use Ctrl-C to stop"
452
- end
386
+ # Separate pipe used by worker 0 to receive commands to
387
+ # fork new worker processes.
388
+ @fork_pipe, @fork_writer = Puma::Util.pipe
389
+
390
+ log "Use Ctrl-C to stop"
391
+
392
+ single_worker_warning
453
393
 
454
394
  redirect_io
455
395
 
@@ -461,7 +401,8 @@ module Puma
461
401
 
462
402
  @master_read, @worker_write = read, @wakeup
463
403
 
464
- @launcher.config.run_hooks :before_fork, nil
404
+ @launcher.config.run_hooks :before_fork, nil, @launcher.events
405
+ Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
465
406
 
466
407
  spawn_workers
467
408
 
@@ -469,48 +410,67 @@ module Puma
469
410
  stop
470
411
  end
471
412
 
472
- @launcher.events.fire_on_booted!
473
-
474
413
  begin
475
- force_check = false
414
+ booted = false
415
+ in_phased_restart = false
416
+ workers_not_booted = @options[:workers]
476
417
 
477
418
  while @status == :run
478
419
  begin
479
420
  if @phased_restart
480
421
  start_phased_restart
481
422
  @phased_restart = false
423
+ in_phased_restart = true
424
+ workers_not_booted = @options[:workers]
482
425
  end
483
426
 
484
- check_workers force_check
485
-
486
- force_check = false
427
+ check_workers
487
428
 
488
- res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
429
+ res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
489
430
 
490
431
  if res
491
432
  req = read.read_nonblock(1)
492
433
 
434
+ @next_check = Time.now if req == "!"
493
435
  next if !req || req == "!"
494
436
 
495
437
  result = read.gets
496
438
  pid = result.to_i
497
439
 
440
+ if req == "b" || req == "f"
441
+ pid, idx = result.split(':').map(&:to_i)
442
+ w = @workers.find {|x| x.index == idx}
443
+ w.pid = pid if w.pid.nil?
444
+ end
445
+
498
446
  if w = @workers.find { |x| x.pid == pid }
499
447
  case req
500
448
  when "b"
501
449
  w.boot!
502
- log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
503
- force_check = true
450
+ log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
451
+ @next_check = Time.now
452
+ workers_not_booted -= 1
453
+ when "e"
454
+ # external term, see worker method, Signal.trap "SIGTERM"
455
+ w.instance_variable_set :@term, true
504
456
  when "t"
505
- w.dead!
506
- force_check = true
457
+ w.term unless w.term?
507
458
  when "p"
508
459
  w.ping!(result.sub(/^\d+/,'').chomp)
460
+ @launcher.events.fire(:ping!, w)
461
+ if !booted && @workers.none? {|worker| worker.last_status.empty?}
462
+ @launcher.events.fire_on_booted!
463
+ booted = true
464
+ end
509
465
  end
510
466
  else
511
467
  log "! Out-of-sync worker list, no #{pid} worker"
512
468
  end
513
469
  end
470
+ if in_phased_restart && workers_not_booted.zero?
471
+ @events.fire_on_booted!
472
+ in_phased_restart = false
473
+ end
514
474
 
515
475
  rescue Interrupt
516
476
  @status = :stop
@@ -525,5 +485,56 @@ module Puma
525
485
  @wakeup.close
526
486
  end
527
487
  end
488
+
489
+ private
490
+
491
+ def single_worker_warning
492
+ return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
493
+
494
+ log "! WARNING: Detected running cluster mode with 1 worker."
495
+ log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
496
+ log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
497
+ log "! Set the `silence_single_worker_warning` option to silence this warning message."
498
+ end
499
+
500
+ # loops thru @workers, removing workers that exited, and calling
501
+ # `#term` if needed
502
+ def wait_workers
503
+ @workers.reject! do |w|
504
+ next false if w.pid.nil?
505
+ begin
506
+ if Process.wait(w.pid, Process::WNOHANG)
507
+ true
508
+ else
509
+ w.term if w.term?
510
+ nil
511
+ end
512
+ rescue Errno::ECHILD
513
+ begin
514
+ Process.kill(0, w.pid)
515
+ # child still alive but has another parent (e.g., using fork_worker)
516
+ w.term if w.term?
517
+ false
518
+ rescue Errno::ESRCH, Errno::EPERM
519
+ true # child is already terminated
520
+ end
521
+ end
522
+ end
523
+ end
524
+
525
+ # @version 5.0.0
526
+ def timeout_workers
527
+ @workers.each do |w|
528
+ if !w.term? && w.ping_timeout <= Time.now
529
+ details = if w.booted?
530
+ "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
531
+ else
532
+ "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
533
+ end
534
+ log "! Terminating timed out worker #{details}: #{w.pid}"
535
+ w.kill
536
+ end
537
+ end
538
+ end
528
539
  end
529
540
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Rack::CommonLogger forwards every request to the given +app+, and
3
5
  # logs a line in the
4
- # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
6
+ # {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
5
7
  # to the +logger+.
6
8
  #
7
9
  # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
@@ -14,7 +16,7 @@ module Puma
14
16
  # (which is called without arguments in order to make the error appear for
15
17
  # sure)
16
18
  class CommonLogger
17
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
19
+ # Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
18
20
  #
19
21
  # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
20
22
  #