puma 6.4.3 → 6.6.1
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.
- checksums.yaml +4 -4
- data/History.md +159 -7
- data/README.md +86 -26
- data/docs/fork_worker.md +11 -1
- data/docs/java_options.md +54 -0
- data/docs/plugins.md +4 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +11 -2
- data/ext/puma_http11/extconf.rb +19 -16
- data/ext/puma_http11/mini_ssl.c +11 -1
- data/ext/puma_http11/org/jruby/puma/Http11.java +29 -8
- data/ext/puma_http11/puma_http11.c +4 -1
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +12 -4
- data/lib/puma/cli.rb +9 -5
- data/lib/puma/client.rb +78 -17
- data/lib/puma/cluster/worker.rb +9 -6
- data/lib/puma/cluster/worker_handle.rb +4 -5
- data/lib/puma/cluster.rb +55 -29
- data/lib/puma/configuration.rb +36 -18
- data/lib/puma/const.rb +14 -3
- data/lib/puma/control_cli.rb +4 -4
- data/lib/puma/dsl.rb +282 -50
- data/lib/puma/error_logger.rb +4 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher.rb +21 -8
- data/lib/puma/log_writer.rb +9 -9
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -0
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/request.rb +19 -11
- data/lib/puma/runner.rb +9 -2
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +33 -22
- data/lib/puma/single.rb +1 -1
- data/lib/puma/thread_pool.rb +14 -2
- data/lib/puma/util.rb +1 -1
- data/lib/rack/handler/puma.rb +8 -5
- data/tools/Dockerfile +3 -1
- metadata +10 -12
data/lib/puma/cluster.rb
CHANGED
@@ -44,10 +44,15 @@ module Puma
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def start_phased_restart
|
47
|
+
def start_phased_restart(refork = false)
|
48
48
|
@events.fire_on_restart!
|
49
|
+
|
49
50
|
@phase += 1
|
50
|
-
|
51
|
+
if refork
|
52
|
+
log "- Starting worker refork, phase: #{@phase}"
|
53
|
+
else
|
54
|
+
log "- Starting phased worker restart, phase: #{@phase}"
|
55
|
+
end
|
51
56
|
|
52
57
|
# Be sure to change the directory again before loading
|
53
58
|
# the app. This way we can pick up new code.
|
@@ -87,6 +92,10 @@ module Puma
|
|
87
92
|
|
88
93
|
if @options[:fork_worker] && all_workers_in_phase?
|
89
94
|
@fork_writer << "0\n"
|
95
|
+
|
96
|
+
if worker_at(0).phase > 0
|
97
|
+
@fork_writer << "-2\n"
|
98
|
+
end
|
90
99
|
end
|
91
100
|
end
|
92
101
|
|
@@ -162,7 +171,7 @@ module Puma
|
|
162
171
|
(@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
|
163
172
|
end
|
164
173
|
|
165
|
-
def check_workers
|
174
|
+
def check_workers(refork = false)
|
166
175
|
return if @next_check >= Time.now
|
167
176
|
|
168
177
|
@next_check = Time.now + @options[:worker_check_interval]
|
@@ -180,7 +189,12 @@ module Puma
|
|
180
189
|
w = @workers.find { |x| x.phase != @phase }
|
181
190
|
|
182
191
|
if w
|
183
|
-
|
192
|
+
if refork
|
193
|
+
log "- Stopping #{w.pid} for refork..."
|
194
|
+
else
|
195
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
196
|
+
end
|
197
|
+
|
184
198
|
unless w.term?
|
185
199
|
w.term
|
186
200
|
log "- #{w.signal} sent to #{w.pid}..."
|
@@ -224,7 +238,7 @@ module Puma
|
|
224
238
|
def phased_restart(refork = false)
|
225
239
|
return false if @options[:preload_app] && !refork
|
226
240
|
|
227
|
-
@phased_restart = true
|
241
|
+
@phased_restart = refork ? :refork : true
|
228
242
|
wakeup!
|
229
243
|
|
230
244
|
true
|
@@ -348,8 +362,6 @@ module Puma
|
|
348
362
|
def run
|
349
363
|
@status = :run
|
350
364
|
|
351
|
-
@idle_workers = {}
|
352
|
-
|
353
365
|
output_header "cluster"
|
354
366
|
|
355
367
|
# This is aligned with the output from Runner, see Runner#output_header
|
@@ -366,7 +378,7 @@ module Puma
|
|
366
378
|
|
367
379
|
before = Thread.list.reject(&fork_safe)
|
368
380
|
|
369
|
-
log "* Restarts: (\u2714) hot (\u2716) phased"
|
381
|
+
log "* Restarts: (\u2714) hot (\u2716) phased (#{@options[:fork_worker] ? "\u2714" : "\u2716"}) refork"
|
370
382
|
log "* Preloading application"
|
371
383
|
load_and_bind
|
372
384
|
|
@@ -384,7 +396,7 @@ module Puma
|
|
384
396
|
end
|
385
397
|
end
|
386
398
|
else
|
387
|
-
log "* Restarts: (\u2714) hot (\u2714) phased"
|
399
|
+
log "* Restarts: (\u2714) hot (\u2714) phased (#{@options[:fork_worker] ? "\u2714" : "\u2716"}) refork"
|
388
400
|
|
389
401
|
unless @config.app_configured?
|
390
402
|
error "No application configured, nothing to run"
|
@@ -440,30 +452,37 @@ module Puma
|
|
440
452
|
|
441
453
|
while @status == :run
|
442
454
|
begin
|
443
|
-
if all_workers_idle_timed_out?
|
455
|
+
if @options[:idle_timeout] && all_workers_idle_timed_out?
|
444
456
|
log "- All workers reached idle timeout"
|
445
457
|
break
|
446
458
|
end
|
447
459
|
|
448
460
|
if @phased_restart
|
449
|
-
start_phased_restart
|
461
|
+
start_phased_restart(@phased_restart == :refork)
|
462
|
+
|
463
|
+
in_phased_restart = @phased_restart
|
450
464
|
@phased_restart = false
|
451
|
-
|
465
|
+
|
452
466
|
workers_not_booted = @options[:workers]
|
467
|
+
# worker 0 is not restarted on refork
|
468
|
+
workers_not_booted -= 1 if in_phased_restart == :refork
|
453
469
|
end
|
454
470
|
|
455
|
-
check_workers
|
471
|
+
check_workers(in_phased_restart == :refork)
|
456
472
|
|
457
473
|
if read.wait_readable([0, @next_check - Time.now].max)
|
458
474
|
req = read.read_nonblock(1)
|
475
|
+
next unless req
|
459
476
|
|
460
|
-
|
461
|
-
|
477
|
+
if req == PIPE_WAKEUP
|
478
|
+
@next_check = Time.now
|
479
|
+
next
|
480
|
+
end
|
462
481
|
|
463
482
|
result = read.gets
|
464
483
|
pid = result.to_i
|
465
484
|
|
466
|
-
if req ==
|
485
|
+
if req == PIPE_BOOT || req == PIPE_FORK
|
467
486
|
pid, idx = result.split(':').map(&:to_i)
|
468
487
|
w = worker_at idx
|
469
488
|
w.pid = pid if w.pid.nil?
|
@@ -471,22 +490,22 @@ module Puma
|
|
471
490
|
|
472
491
|
if w = @workers.find { |x| x.pid == pid }
|
473
492
|
case req
|
474
|
-
when
|
493
|
+
when PIPE_BOOT
|
475
494
|
w.boot!
|
476
495
|
log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
|
477
496
|
@next_check = Time.now
|
478
497
|
workers_not_booted -= 1
|
479
|
-
when
|
498
|
+
when PIPE_EXTERNAL_TERM
|
480
499
|
# external term, see worker method, Signal.trap "SIGTERM"
|
481
500
|
w.term!
|
482
|
-
when
|
501
|
+
when PIPE_TERM
|
483
502
|
w.term unless w.term?
|
484
|
-
when
|
503
|
+
when PIPE_PING
|
485
504
|
status = result.sub(/^\d+/,'').chomp
|
486
505
|
w.ping!(status)
|
487
506
|
@events.fire(:ping!, w)
|
488
507
|
|
489
|
-
if in_phased_restart && workers_not_booted.positive? && w0 = worker_at(0)
|
508
|
+
if in_phased_restart && @options[:fork_worker] && workers_not_booted.positive? && w0 = worker_at(0)
|
490
509
|
w0.ping!(status)
|
491
510
|
@events.fire(:ping!, w0)
|
492
511
|
end
|
@@ -496,11 +515,11 @@ module Puma
|
|
496
515
|
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
497
516
|
booted = true
|
498
517
|
end
|
499
|
-
when
|
500
|
-
if
|
501
|
-
|
518
|
+
when PIPE_IDLE
|
519
|
+
if idle_workers[pid]
|
520
|
+
idle_workers.delete pid
|
502
521
|
else
|
503
|
-
|
522
|
+
idle_workers[pid] = true
|
504
523
|
end
|
505
524
|
end
|
506
525
|
else
|
@@ -560,9 +579,12 @@ module Puma
|
|
560
579
|
@workers.reject! do |w|
|
561
580
|
next false if w.pid.nil?
|
562
581
|
begin
|
563
|
-
#
|
564
|
-
#
|
565
|
-
|
582
|
+
# We may need to check the PID individually because:
|
583
|
+
# 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
|
584
|
+
# `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
|
585
|
+
# 2. When `fork_worker` is enabled, some worker may not be direct children,
|
586
|
+
# but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
|
587
|
+
if reaped_children.delete(w.pid) || Process.wait(w.pid, Process::WNOHANG)
|
566
588
|
true
|
567
589
|
else
|
568
590
|
w.term if w.term?
|
@@ -602,7 +624,11 @@ module Puma
|
|
602
624
|
end
|
603
625
|
|
604
626
|
def idle_timed_out_worker_pids
|
605
|
-
|
627
|
+
idle_workers.keys
|
628
|
+
end
|
629
|
+
|
630
|
+
def idle_workers
|
631
|
+
@idle_workers ||= {}
|
606
632
|
end
|
607
633
|
end
|
608
634
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'rack/builder'
|
4
3
|
require_relative 'plugin'
|
5
4
|
require_relative 'const'
|
6
5
|
require_relative 'dsl'
|
@@ -131,6 +130,7 @@ module Puma
|
|
131
130
|
binds: ['tcp://0.0.0.0:9292'.freeze],
|
132
131
|
clean_thread_locals: false,
|
133
132
|
debug: false,
|
133
|
+
enable_keep_alives: true,
|
134
134
|
early_hints: nil,
|
135
135
|
environment: 'development'.freeze,
|
136
136
|
# Number of seconds to wait until we get the first data for the request.
|
@@ -173,8 +173,8 @@ module Puma
|
|
173
173
|
http_content_length_limit: nil
|
174
174
|
}
|
175
175
|
|
176
|
-
def initialize(user_options={}, default_options = {}, &block)
|
177
|
-
default_options = self.puma_default_options.merge(default_options)
|
176
|
+
def initialize(user_options={}, default_options = {}, env = ENV, &block)
|
177
|
+
default_options = self.puma_default_options(env).merge(default_options)
|
178
178
|
|
179
179
|
@options = UserFileDefaultOptions.new(user_options, default_options)
|
180
180
|
@plugins = PluginLoader.new
|
@@ -186,6 +186,8 @@ module Puma
|
|
186
186
|
default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
|
187
187
|
end
|
188
188
|
|
189
|
+
@puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
|
190
|
+
|
189
191
|
if block
|
190
192
|
configure(&block)
|
191
193
|
end
|
@@ -216,22 +218,27 @@ module Puma
|
|
216
218
|
self
|
217
219
|
end
|
218
220
|
|
219
|
-
def puma_default_options
|
221
|
+
def puma_default_options(env = ENV)
|
220
222
|
defaults = DEFAULTS.dup
|
221
|
-
puma_options_from_env.each { |k,v| defaults[k] = v if v }
|
223
|
+
puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
|
222
224
|
defaults
|
223
225
|
end
|
224
226
|
|
225
|
-
def puma_options_from_env
|
226
|
-
min =
|
227
|
-
max =
|
228
|
-
workers =
|
227
|
+
def puma_options_from_env(env = ENV)
|
228
|
+
min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
|
229
|
+
max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
|
230
|
+
workers = if env['WEB_CONCURRENCY'] == 'auto'
|
231
|
+
require_processor_counter
|
232
|
+
::Concurrent.available_processor_count
|
233
|
+
else
|
234
|
+
env['WEB_CONCURRENCY']
|
235
|
+
end
|
229
236
|
|
230
237
|
{
|
231
|
-
min_threads: min && Integer(min),
|
232
|
-
max_threads: max && Integer(max),
|
233
|
-
workers: workers && Integer(workers),
|
234
|
-
environment:
|
238
|
+
min_threads: min && min != "" && Integer(min),
|
239
|
+
max_threads: max && max != "" && Integer(max),
|
240
|
+
workers: workers && workers != "" && Integer(workers),
|
241
|
+
environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
|
235
242
|
}
|
236
243
|
end
|
237
244
|
|
@@ -311,6 +318,8 @@ module Puma
|
|
311
318
|
# @param arg [Launcher, Int] `:on_restart` passes Launcher
|
312
319
|
#
|
313
320
|
def run_hooks(key, arg, log_writer, hook_data = nil)
|
321
|
+
log_writer.debug "Running #{key} hooks"
|
322
|
+
|
314
323
|
@options.all_of(key).each do |b|
|
315
324
|
begin
|
316
325
|
if Array === b
|
@@ -339,12 +348,22 @@ module Puma
|
|
339
348
|
|
340
349
|
private
|
341
350
|
|
351
|
+
def require_processor_counter
|
352
|
+
require 'concurrent/utility/processor_counter'
|
353
|
+
rescue LoadError
|
354
|
+
warn <<~MESSAGE
|
355
|
+
WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
|
356
|
+
Please add "concurrent-ruby" to your Gemfile.
|
357
|
+
MESSAGE
|
358
|
+
raise
|
359
|
+
end
|
360
|
+
|
342
361
|
# Load and use the normal Rack builder if we can, otherwise
|
343
362
|
# fallback to our minimal version.
|
344
363
|
def rack_builder
|
345
364
|
# Load bundler now if we can so that we can pickup rack from
|
346
365
|
# a Gemfile
|
347
|
-
if
|
366
|
+
if @puma_bundler_pruned
|
348
367
|
begin
|
349
368
|
require 'bundler/setup'
|
350
369
|
rescue LoadError
|
@@ -354,11 +373,10 @@ module Puma
|
|
354
373
|
begin
|
355
374
|
require 'rack'
|
356
375
|
require 'rack/builder'
|
376
|
+
::Rack::Builder
|
357
377
|
rescue LoadError
|
358
|
-
|
359
|
-
|
360
|
-
else
|
361
|
-
return ::Rack::Builder
|
378
|
+
require_relative 'rack/builder'
|
379
|
+
Puma::Rack::Builder
|
362
380
|
end
|
363
381
|
end
|
364
382
|
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "6.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "6.6.1"
|
104
|
+
CODE_NAME = "Return to Forever"
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
107
107
|
|
@@ -137,7 +137,7 @@ module Puma
|
|
137
137
|
}.freeze
|
138
138
|
|
139
139
|
# The basic max request size we'll try to read.
|
140
|
-
CHUNK_SIZE =
|
140
|
+
CHUNK_SIZE = 64 * 1024
|
141
141
|
|
142
142
|
# This is the maximum header that is allowed before a client is booted. The parser detects
|
143
143
|
# this, but we'd also like to do this as well.
|
@@ -293,5 +293,16 @@ module Puma
|
|
293
293
|
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
294
294
|
|
295
295
|
PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
296
|
+
|
297
|
+
# All constants are prefixed with `PIPE_` to avoid name collisions.
|
298
|
+
module PipeRequest
|
299
|
+
PIPE_WAKEUP = "!"
|
300
|
+
PIPE_BOOT = "b"
|
301
|
+
PIPE_FORK = "f"
|
302
|
+
PIPE_EXTERNAL_TERM = "e"
|
303
|
+
PIPE_TERM = "t"
|
304
|
+
PIPE_PING = "p"
|
305
|
+
PIPE_IDLE = "i"
|
306
|
+
end
|
296
307
|
end
|
297
308
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -37,7 +37,7 @@ module Puma
|
|
37
37
|
# @version 5.0.0
|
38
38
|
PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
|
39
39
|
|
40
|
-
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
40
|
+
def initialize(argv, stdout=STDOUT, stderr=STDERR, env: ENV)
|
41
41
|
@state = nil
|
42
42
|
@quiet = false
|
43
43
|
@pidfile = nil
|
@@ -46,7 +46,7 @@ module Puma
|
|
46
46
|
@control_auth_token = nil
|
47
47
|
@config_file = nil
|
48
48
|
@command = nil
|
49
|
-
@environment =
|
49
|
+
@environment = env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV']
|
50
50
|
|
51
51
|
@argv = argv.dup
|
52
52
|
@stdout = stdout
|
@@ -60,7 +60,7 @@ module Puma
|
|
60
60
|
@state = arg
|
61
61
|
end
|
62
62
|
|
63
|
-
o.on "-Q", "--quiet", "
|
63
|
+
o.on "-Q", "--quiet", "Do not display messages" do |arg|
|
64
64
|
@quiet = true
|
65
65
|
end
|
66
66
|
|
@@ -127,7 +127,7 @@ module Puma
|
|
127
127
|
require_relative 'configuration'
|
128
128
|
require_relative 'log_writer'
|
129
129
|
|
130
|
-
config = Puma::Configuration.new({ config_files: [@config_file] }, {})
|
130
|
+
config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env)
|
131
131
|
config.load
|
132
132
|
@state ||= config.options[:state]
|
133
133
|
@control_url ||= config.options[:control_url]
|