puma 7.2.0 → 8.0.2
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 +68 -0
- data/README.md +1 -2
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/signals.md +1 -1
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/lib/puma/binder.rb +2 -2
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +117 -77
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster.rb +1 -1
- data/lib/puma/configuration.rb +70 -8
- data/lib/puma/const.rb +4 -3
- data/lib/puma/control_cli.rb +1 -1
- data/lib/puma/detect.rb +11 -0
- data/lib/puma/dsl.rb +74 -8
- data/lib/puma/launcher/bundle_pruner.rb +2 -4
- data/lib/puma/launcher.rb +3 -4
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/{request.rb → response.rb} +15 -186
- data/lib/puma/server.rb +70 -35
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/thread_pool.rb +129 -23
- data/lib/rack/handler/puma.rb +1 -1
- metadata +14 -3
data/lib/puma/configuration.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
3
6
|
require_relative 'plugin'
|
|
4
7
|
require_relative 'const'
|
|
5
8
|
require_relative 'dsl'
|
|
@@ -131,14 +134,16 @@ module Puma
|
|
|
131
134
|
|
|
132
135
|
DEFAULTS = {
|
|
133
136
|
auto_trim_time: 30,
|
|
134
|
-
binds: ['tcp://
|
|
135
|
-
fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
|
|
137
|
+
binds: ['tcp://[::]:9292'.freeze],
|
|
136
138
|
debug: false,
|
|
137
|
-
enable_keep_alives: true,
|
|
138
139
|
early_hints: nil,
|
|
140
|
+
enable_keep_alives: true,
|
|
139
141
|
environment: 'development'.freeze,
|
|
142
|
+
fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
|
|
140
143
|
# Number of seconds to wait until we get the first data for the request.
|
|
141
144
|
first_data_timeout: 30,
|
|
145
|
+
force_shutdown_after: -1,
|
|
146
|
+
http_content_length_limit: nil,
|
|
142
147
|
# Number of seconds to wait until the next request before shutting down.
|
|
143
148
|
idle_timeout: nil,
|
|
144
149
|
io_selector_backend: :auto,
|
|
@@ -147,6 +152,7 @@ module Puma
|
|
|
147
152
|
# Limits how many requests a keep alive connection can make.
|
|
148
153
|
# The connection will be closed after it reaches `max_keep_alive`
|
|
149
154
|
# requests.
|
|
155
|
+
max_io_threads: 0,
|
|
150
156
|
max_keep_alive: 999,
|
|
151
157
|
max_threads: Puma.mri? ? 5 : 16,
|
|
152
158
|
min_threads: 0,
|
|
@@ -161,10 +167,10 @@ module Puma
|
|
|
161
167
|
raise_exception_on_sigterm: true,
|
|
162
168
|
reaping_time: 1,
|
|
163
169
|
remote_address: :socket,
|
|
164
|
-
silence_single_worker_warning: false,
|
|
165
170
|
silence_fork_callback_warning: false,
|
|
171
|
+
silence_single_worker_warning: false,
|
|
166
172
|
tag: File.basename(Dir.getwd),
|
|
167
|
-
tcp_host: '
|
|
173
|
+
tcp_host: '::'.freeze,
|
|
168
174
|
tcp_port: 9292,
|
|
169
175
|
wait_for_less_busy_worker: 0.005,
|
|
170
176
|
worker_boot_timeout: 60,
|
|
@@ -173,11 +179,10 @@ module Puma
|
|
|
173
179
|
worker_shutdown_timeout: 30,
|
|
174
180
|
worker_timeout: 60,
|
|
175
181
|
workers: 0,
|
|
176
|
-
http_content_length_limit: nil
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
def initialize(user_options={}, default_options = {}, env = ENV, &block)
|
|
180
|
-
default_options = self.puma_default_options(env).merge(default_options)
|
|
185
|
+
default_options = self.puma_default_options(env).merge(events: Events.new).merge(default_options)
|
|
181
186
|
|
|
182
187
|
@_options = UserFileDefaultOptions.new(user_options, default_options)
|
|
183
188
|
@plugins = PluginLoader.new
|
|
@@ -230,6 +235,8 @@ module Puma
|
|
|
230
235
|
|
|
231
236
|
def puma_default_options(env = ENV)
|
|
232
237
|
defaults = DEFAULTS.dup
|
|
238
|
+
defaults[:tcp_host] = self.class.default_tcp_host
|
|
239
|
+
defaults[:binds] = [self.class.default_tcp_bind]
|
|
233
240
|
puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
|
|
234
241
|
defaults
|
|
235
242
|
end
|
|
@@ -277,8 +284,10 @@ module Puma
|
|
|
277
284
|
# This also calls load if it hasn't been called yet.
|
|
278
285
|
def clamp
|
|
279
286
|
load unless @loaded
|
|
287
|
+
run_mode_hooks
|
|
280
288
|
set_conditional_default_options
|
|
281
289
|
@_options.finalize_values
|
|
290
|
+
rewrite_unavailable_ipv6_binds!
|
|
282
291
|
@clamped = true
|
|
283
292
|
warn_hooks
|
|
284
293
|
options
|
|
@@ -335,7 +344,7 @@ module Puma
|
|
|
335
344
|
# @param arg [Launcher, Int] `:before_restart` passes Launcher
|
|
336
345
|
#
|
|
337
346
|
def run_hooks(key, arg, log_writer, hook_data = nil)
|
|
338
|
-
log_writer.debug "Running #{key} hooks"
|
|
347
|
+
log_writer.debug { "Running #{key} hooks" }
|
|
339
348
|
|
|
340
349
|
options.all_of(key).each do |hook_options|
|
|
341
350
|
begin
|
|
@@ -357,6 +366,23 @@ module Puma
|
|
|
357
366
|
options.final_options
|
|
358
367
|
end
|
|
359
368
|
|
|
369
|
+
def self.default_tcp_host
|
|
370
|
+
ipv6_interface_available? ? Const::UNSPECIFIED_IPV6 : Const::UNSPECIFIED_IPV4
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def self.default_tcp_bind(port = DEFAULTS[:tcp_port])
|
|
374
|
+
URI::Generic.build(scheme: 'tcp', host: default_tcp_host, port: Integer(port)).to_s
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def self.ipv6_interface_available?
|
|
378
|
+
Socket.getifaddrs.any? do |ifaddr|
|
|
379
|
+
addr = ifaddr.addr
|
|
380
|
+
addr&.ipv6? && !addr&.ipv6_loopback?
|
|
381
|
+
end
|
|
382
|
+
rescue StandardError
|
|
383
|
+
false
|
|
384
|
+
end
|
|
385
|
+
|
|
360
386
|
def self.temp_path
|
|
361
387
|
require 'tmpdir'
|
|
362
388
|
|
|
@@ -372,6 +398,31 @@ module Puma
|
|
|
372
398
|
|
|
373
399
|
private
|
|
374
400
|
|
|
401
|
+
def rewrite_unavailable_ipv6_binds!
|
|
402
|
+
return if self.class.ipv6_interface_available?
|
|
403
|
+
|
|
404
|
+
tried_ipv6_bind = false
|
|
405
|
+
bind_schemes = ['tcp', 'ssl']
|
|
406
|
+
|
|
407
|
+
@_options[:binds] = Array(@_options[:binds]).map do |bind|
|
|
408
|
+
uri = URI.parse(bind)
|
|
409
|
+
next bind unless bind_schemes.include?(uri.scheme)
|
|
410
|
+
|
|
411
|
+
host = uri.host&.delete_prefix('[')&.delete_suffix(']')
|
|
412
|
+
next bind unless host&.include?(':')
|
|
413
|
+
|
|
414
|
+
tried_ipv6_bind = true
|
|
415
|
+
next bind unless host == Const::UNSPECIFIED_IPV6
|
|
416
|
+
|
|
417
|
+
uri.host = Const::UNSPECIFIED_IPV4
|
|
418
|
+
uri.to_s
|
|
419
|
+
rescue URI::InvalidURIError
|
|
420
|
+
bind
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
warn "WARNING: IPv6 bind requested but no non-loopback IPv6 interface was detected" if tried_ipv6_bind
|
|
424
|
+
end
|
|
425
|
+
|
|
375
426
|
def require_processor_counter
|
|
376
427
|
require 'concurrent/utility/processor_counter'
|
|
377
428
|
rescue LoadError
|
|
@@ -433,6 +484,17 @@ module Puma
|
|
|
433
484
|
rack_app
|
|
434
485
|
end
|
|
435
486
|
|
|
487
|
+
def run_mode_hooks
|
|
488
|
+
workers_before = @_options[:workers]
|
|
489
|
+
key = workers_before > 0 ? :cluster : :single
|
|
490
|
+
|
|
491
|
+
@_options.all_of(key).each(&:call)
|
|
492
|
+
|
|
493
|
+
unless @_options[:workers] == workers_before
|
|
494
|
+
raise "cannot change the number of workers inside a #{key} configuration hook"
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
436
498
|
def set_conditional_default_options
|
|
437
499
|
@_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
|
|
438
500
|
(@_options[:workers] > 1) && Puma.forkable?
|
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 = "
|
|
104
|
-
CODE_NAME = "
|
|
103
|
+
PUMA_VERSION = VERSION = "8.0.2"
|
|
104
|
+
CODE_NAME = "Into the Arena"
|
|
105
105
|
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
|
107
107
|
|
|
@@ -291,7 +291,8 @@ module Puma
|
|
|
291
291
|
# Banned keys of response header
|
|
292
292
|
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
|
293
293
|
|
|
294
|
-
PROXY_PROTOCOL_V1_REGEX =
|
|
294
|
+
PROXY_PROTOCOL_V1_REGEX = /\APROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
|
295
|
+
PROXY_PROTOCOL_V1_MAX_LENGTH = 107
|
|
295
296
|
|
|
296
297
|
# All constants are prefixed with `PIPE_` to avoid name collisions.
|
|
297
298
|
module PipeRequest
|
data/lib/puma/control_cli.rb
CHANGED
data/lib/puma/detect.rb
CHANGED
|
@@ -35,6 +35,13 @@ module Puma
|
|
|
35
35
|
IS_WINDOWS
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
BACKTRACE_SIGNAL =
|
|
39
|
+
if Signal.list.key?("INFO")
|
|
40
|
+
"SIGINFO"
|
|
41
|
+
elsif Signal.list.key?("PWR")
|
|
42
|
+
"SIGPWR"
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
# @version 5.0.0
|
|
39
46
|
def self.mri?
|
|
40
47
|
IS_MRI
|
|
@@ -44,4 +51,8 @@ module Puma
|
|
|
44
51
|
def self.forkable?
|
|
45
52
|
HAS_FORK
|
|
46
53
|
end
|
|
54
|
+
|
|
55
|
+
def self.backtrace_signal
|
|
56
|
+
BACKTRACE_SIGNAL
|
|
57
|
+
end
|
|
47
58
|
end
|
data/lib/puma/dsl.rb
CHANGED
|
@@ -155,7 +155,7 @@ module Puma
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
def default_host
|
|
158
|
-
@options[:default_host] || Configuration
|
|
158
|
+
@options[:default_host] || Configuration.default_tcp_host
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def inject(&blk)
|
|
@@ -260,7 +260,8 @@ module Puma
|
|
|
260
260
|
# accepted protocols. Multiple urls can be bound to, calling +bind+ does
|
|
261
261
|
# not overwrite previous bindings.
|
|
262
262
|
#
|
|
263
|
-
# The default is "tcp://
|
|
263
|
+
# The default is "tcp://[::]:9292" when IPv6 interfaces are available,
|
|
264
|
+
# otherwise "tcp://0.0.0.0:9292".
|
|
264
265
|
#
|
|
265
266
|
# You can use query parameters within the url to specify options:
|
|
266
267
|
#
|
|
@@ -279,7 +280,7 @@ module Puma
|
|
|
279
280
|
# @example SSL cert for mutual TLS (mTLS)
|
|
280
281
|
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
|
|
281
282
|
# @example Disable optimization for low latency
|
|
282
|
-
# bind 'tcp://
|
|
283
|
+
# bind 'tcp://[::]:9292?low_latency=false'
|
|
283
284
|
# @example Socket permissions
|
|
284
285
|
# bind 'unix:///var/run/puma.sock?umask=0111'
|
|
285
286
|
#
|
|
@@ -349,7 +350,7 @@ module Puma
|
|
|
349
350
|
|
|
350
351
|
# Define how long persistent connections can be idle before Puma closes them.
|
|
351
352
|
#
|
|
352
|
-
# The default is
|
|
353
|
+
# The default is 65 seconds.
|
|
353
354
|
#
|
|
354
355
|
# @example
|
|
355
356
|
# persistent_timeout 30
|
|
@@ -595,6 +596,29 @@ module Puma
|
|
|
595
596
|
@options[:max_threads] = max
|
|
596
597
|
end
|
|
597
598
|
|
|
599
|
+
# Configure the max number of IO threads.
|
|
600
|
+
#
|
|
601
|
+
# When request handlers know the current requests will no longer use a significant amount
|
|
602
|
+
# of CPU, they can mark the current request as IO bound using <tt>env["puma.mark_as_io_bound"]</tt>.
|
|
603
|
+
#
|
|
604
|
+
# Threads marked as IO bound are allowed to go over the max thread limit.
|
|
605
|
+
#
|
|
606
|
+
# @example
|
|
607
|
+
# threads 5
|
|
608
|
+
# max_io_threads 5
|
|
609
|
+
#
|
|
610
|
+
# The above example allows for 5 regular threads and 5 IO threads to process requests concurrently.
|
|
611
|
+
# Any IO thread over the limit is counted as a regular thread, hence the above configuration also
|
|
612
|
+
# allows for 3 regular threads and 7 IO threads for example.
|
|
613
|
+
def max_io_threads(max)
|
|
614
|
+
max = Integer(max)
|
|
615
|
+
if max < 0
|
|
616
|
+
raise "The maximum number of IO threads (#{max}) must be a positive number"
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
@options[:max_io_threads] = max
|
|
620
|
+
end
|
|
621
|
+
|
|
598
622
|
# Instead of using +bind+ and manually constructing a URI like:
|
|
599
623
|
#
|
|
600
624
|
# bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
|
|
@@ -727,6 +751,44 @@ module Puma
|
|
|
727
751
|
@options[:silence_fork_callback_warning] = true
|
|
728
752
|
end
|
|
729
753
|
|
|
754
|
+
# Code to run only in single mode.
|
|
755
|
+
# Runs after all config files are loaded.
|
|
756
|
+
#
|
|
757
|
+
# This can be called multiple times.
|
|
758
|
+
#
|
|
759
|
+
# @note Single mode only.
|
|
760
|
+
#
|
|
761
|
+
# @example
|
|
762
|
+
# single do
|
|
763
|
+
# silence_fork_callback_warning
|
|
764
|
+
# end
|
|
765
|
+
#
|
|
766
|
+
def single(&block)
|
|
767
|
+
raise ArgumentError, "A block must be provided to `single`" unless block
|
|
768
|
+
|
|
769
|
+
@options[:single] ||= []
|
|
770
|
+
@options[:single] << block
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Code to run only in cluster mode.
|
|
774
|
+
# Runs after all config files are loaded.
|
|
775
|
+
#
|
|
776
|
+
# This can be called multiple times.
|
|
777
|
+
#
|
|
778
|
+
# @note Cluster mode only.
|
|
779
|
+
#
|
|
780
|
+
# @example
|
|
781
|
+
# cluster do
|
|
782
|
+
# prune_bundler
|
|
783
|
+
# end
|
|
784
|
+
#
|
|
785
|
+
def cluster(&block)
|
|
786
|
+
raise ArgumentError, "A block must be provided to `cluster`" unless block
|
|
787
|
+
|
|
788
|
+
@options[:cluster] ||= []
|
|
789
|
+
@options[:cluster] << block
|
|
790
|
+
end
|
|
791
|
+
|
|
730
792
|
# Code to run immediately before master process
|
|
731
793
|
# forks workers (once on boot). These hooks can block if necessary
|
|
732
794
|
# to wait for background operations unknown to Puma to finish before
|
|
@@ -1040,6 +1102,7 @@ module Puma
|
|
|
1040
1102
|
# new Bundler context and thus can float around as the release
|
|
1041
1103
|
# dictates.
|
|
1042
1104
|
#
|
|
1105
|
+
# @note Cluster mode only.
|
|
1043
1106
|
# @note This is incompatible with +preload_app!+.
|
|
1044
1107
|
# @note This is only supported for RubyGems 2.2+
|
|
1045
1108
|
#
|
|
@@ -1145,7 +1208,7 @@ module Puma
|
|
|
1145
1208
|
|
|
1146
1209
|
# Change the default worker timeout for booting.
|
|
1147
1210
|
#
|
|
1148
|
-
# The default is
|
|
1211
|
+
# The default is 60 seconds.
|
|
1149
1212
|
#
|
|
1150
1213
|
# @note Cluster mode only.
|
|
1151
1214
|
#
|
|
@@ -1227,11 +1290,14 @@ module Puma
|
|
|
1227
1290
|
# threads will be written to $stdout. This can help figure
|
|
1228
1291
|
# out why shutdown is hanging.
|
|
1229
1292
|
#
|
|
1230
|
-
|
|
1231
|
-
|
|
1293
|
+
# If `on_force` is true, the backtraces will be written only
|
|
1294
|
+
# when the shutdown is forced i.e. not graceful.
|
|
1295
|
+
#
|
|
1296
|
+
# @see force_shutdown_after
|
|
1297
|
+
def shutdown_debug(val = true, on_force: false)
|
|
1298
|
+
@options[:shutdown_debug] = val && on_force ? :on_force : val
|
|
1232
1299
|
end
|
|
1233
1300
|
|
|
1234
|
-
|
|
1235
1301
|
# Maximum delay of worker accept loop.
|
|
1236
1302
|
#
|
|
1237
1303
|
# Attempts to route traffic to less-busy workers by causing a busy worker to delay
|
|
@@ -28,14 +28,12 @@ module Puma
|
|
|
28
28
|
|
|
29
29
|
log '* Pruning Bundler environment'
|
|
30
30
|
home = ENV['GEM_HOME']
|
|
31
|
-
|
|
32
|
-
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
|
31
|
+
original_bundle_env = Bundler.original_env.select { |k, v| k.start_with?('BUNDLE_') && v }
|
|
33
32
|
|
|
34
33
|
with_unbundled_env do
|
|
35
34
|
ENV['GEM_HOME'] = home
|
|
36
|
-
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
|
37
35
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
|
38
|
-
ENV[
|
|
36
|
+
original_bundle_env.each { |k, v| ENV[k] = v }
|
|
39
37
|
args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
|
|
40
38
|
# Defaults to true which breaks socket activation
|
|
41
39
|
args += [{:close_others => false}]
|
data/lib/puma/launcher.rb
CHANGED
|
@@ -476,8 +476,8 @@ module Puma
|
|
|
476
476
|
end
|
|
477
477
|
|
|
478
478
|
begin
|
|
479
|
-
|
|
480
|
-
Signal.trap
|
|
479
|
+
if Puma.backtrace_signal
|
|
480
|
+
Signal.trap Puma.backtrace_signal do
|
|
481
481
|
thread_status do |name, backtrace|
|
|
482
482
|
@log_writer.log(name)
|
|
483
483
|
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
|
@@ -485,8 +485,7 @@ module Puma
|
|
|
485
485
|
end
|
|
486
486
|
end
|
|
487
487
|
rescue Exception
|
|
488
|
-
|
|
489
|
-
# to see this constantly on Linux.
|
|
488
|
+
log "*** SIGINFO/SIGPWR not implemented, signal based backtrace unavailable!"
|
|
490
489
|
end
|
|
491
490
|
end
|
|
492
491
|
|
data/lib/puma/log_writer.rb
CHANGED
|
@@ -90,8 +90,14 @@ module Puma
|
|
|
90
90
|
@debug
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
-
def debug(str)
|
|
94
|
-
|
|
93
|
+
def debug(str=nil, &block)
|
|
94
|
+
if @debug
|
|
95
|
+
if block_given?
|
|
96
|
+
log("% #{yield}")
|
|
97
|
+
else
|
|
98
|
+
log("% #{str}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
# Write +str+ to +@stderr+
|