puma 7.0.4 → 7.2.0
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 +94 -0
- data/README.md +24 -11
- data/docs/deployment.md +58 -23
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +5 -12
- data/docs/plugins.md +2 -2
- data/docs/signals.md +10 -10
- data/docs/stats.md +2 -2
- data/docs/systemd.md +3 -3
- data/ext/puma_http11/puma_http11.c +101 -109
- data/lib/puma/app/status.rb +10 -2
- data/lib/puma/client.rb +21 -25
- data/lib/puma/cluster/worker.rb +10 -9
- data/lib/puma/cluster/worker_handle.rb +2 -2
- data/lib/puma/cluster.rb +11 -10
- data/lib/puma/cluster_accept_loop_delay.rb +17 -18
- data/lib/puma/configuration.rb +17 -9
- data/lib/puma/const.rb +2 -2
- data/lib/puma/dsl.rb +41 -10
- data/lib/puma/launcher.rb +32 -26
- data/lib/puma/reactor.rb +3 -12
- data/lib/puma/request.rb +10 -8
- data/lib/puma/runner.rb +1 -1
- data/lib/puma/server.rb +35 -31
- data/lib/puma/single.rb +2 -2
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +10 -1
- data/tools/Dockerfile +13 -5
- metadata +4 -4
- data/ext/puma_http11/ext_help.h +0 -15
|
@@ -20,48 +20,47 @@ module Puma
|
|
|
20
20
|
# already https://github.com/puma/puma/pull/3678/files/2736ebddb3fc8528e5150b5913fba251c37a8bf7#diff-a95f46e7ce116caddc9b9a9aa81004246d5210d5da5f4df90a818c780630166bL251-L291
|
|
21
21
|
#
|
|
22
22
|
# With the introduction of true keepalive support, there are two ways a request can come in:
|
|
23
|
-
# - A new request from a new client comes into the socket and it must be "accept"-
|
|
23
|
+
# - A new request from a new client comes into the socket and it must be "accept"-ed
|
|
24
24
|
# - A keepalive request is served and the connection is retained. Another request is then accepted
|
|
25
25
|
#
|
|
26
26
|
# Ideally the server handles requests in the order they come in, and ideally it doesn't accept more requests than it can handle.
|
|
27
27
|
# These goals are contradictory, because when the server is at maximum capacity due to keepalive connections, it could mean we
|
|
28
28
|
# block all new requests, even if those came in before the new request on the older keepalive connection.
|
|
29
29
|
#
|
|
30
|
-
# ## Distribute CPU resources across all workers
|
|
30
|
+
# ## Goal: Distribute CPU resources across all workers
|
|
31
31
|
#
|
|
32
32
|
# - This issue was opened https://github.com/puma/puma/issues/2078
|
|
33
33
|
#
|
|
34
|
-
# There are several entangled issues and it's not exactly clear the root cause, but the observable outcome
|
|
34
|
+
# There are several entangled issues and it's not exactly clear what the root cause is, but the observable outcome
|
|
35
35
|
# was that performance was better with a small sleep, and that eventually became the default.
|
|
36
36
|
#
|
|
37
37
|
# An attempt to describe why this works is here: https://github.com/puma/puma/issues/2078#issuecomment-3287032470.
|
|
38
38
|
#
|
|
39
39
|
# Summarizing: The delay is for tuning the rate at which "accept" is called on the socket.
|
|
40
|
-
# Puma works by calling "accept" nonblock on the socket in a loop. When there are multiple workers
|
|
41
|
-
# (processes)
|
|
40
|
+
# Puma works by calling "accept" nonblock on the socket in a loop. When there are multiple workers
|
|
41
|
+
# (processes), they will "race" to accept a request at roughly the same rate. However, if one
|
|
42
42
|
# worker has all threads busy processing requests, then accepting a new request might "steal" it from
|
|
43
43
|
# a less busy worker. If a worker has no work to do, it should loop as fast as possible.
|
|
44
44
|
#
|
|
45
|
-
# ## Solution
|
|
45
|
+
# ## Solution: Distribute requests across workers at start
|
|
46
46
|
#
|
|
47
47
|
# For now, both goals are framed as "load balancing" across workers (processes) and achieved through
|
|
48
48
|
# the same mechanism of sleeping longer to delay busier workers. Rather than the prior Puma 6.x
|
|
49
|
-
# and earlier behavior of using a binary on/off sleep value, we increase it an
|
|
50
|
-
# to the load the server is under
|
|
49
|
+
# and earlier behavior of using a binary on/off sleep value, we increase it an amount proportional
|
|
50
|
+
# to the load the server is under, capping the maximum delay to the scenario where all threads are busy
|
|
51
51
|
# and the todo list has reached a multiplier of the maximum number of threads.
|
|
52
52
|
#
|
|
53
53
|
# Private: API may change unexpectedly
|
|
54
54
|
class ClusterAcceptLoopDelay
|
|
55
|
-
attr_reader :
|
|
55
|
+
attr_reader :max_delay
|
|
56
56
|
|
|
57
|
-
# Initialize happens once, `call` happens often.
|
|
57
|
+
# Initialize happens once, `call` happens often. Perform global calculations here.
|
|
58
58
|
def initialize(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
)
|
|
59
|
+
# Number of workers in the cluster
|
|
60
|
+
workers: ,
|
|
61
|
+
# Maximum delay in seconds i.e. 0.005 is 5 milliseconds
|
|
62
|
+
max_delay:
|
|
63
|
+
)
|
|
65
64
|
@on = max_delay > 0 && workers >= 2
|
|
66
65
|
@max_delay = max_delay.to_f
|
|
67
66
|
|
|
@@ -76,12 +75,12 @@ module Puma
|
|
|
76
75
|
# We want the extreme values of this delay to be known (minimum and maximum) as well as
|
|
77
76
|
# a predictable curve between the two. i.e. no step functions or hard cliffs.
|
|
78
77
|
#
|
|
79
|
-
# Return value is always numeric. Returns 0 if there should be no delay
|
|
78
|
+
# Return value is always numeric. Returns 0 if there should be no delay.
|
|
80
79
|
def calculate(
|
|
81
80
|
# Number of threads working right now, plus number of requests in the todo list
|
|
82
81
|
busy_threads_plus_todo:,
|
|
83
82
|
# Maximum number of threads in the pool, note that the busy threads (alone) may go over this value at times
|
|
84
|
-
# if the pool needs to be reaped. The busy thread plus todo count may go over this value by a large amount
|
|
83
|
+
# if the pool needs to be reaped. The busy thread plus todo count may go over this value by a large amount.
|
|
85
84
|
max_threads:
|
|
86
85
|
)
|
|
87
86
|
max_value = @overload_multiplier * max_threads
|
data/lib/puma/configuration.rb
CHANGED
|
@@ -155,6 +155,7 @@ module Puma
|
|
|
155
155
|
out_of_band: [],
|
|
156
156
|
# Number of seconds for another request within a persistent session.
|
|
157
157
|
persistent_timeout: 65, # PUMA_PERSISTENT_TIMEOUT
|
|
158
|
+
prune_bundler: false,
|
|
158
159
|
queue_requests: true,
|
|
159
160
|
rackup: 'config.ru'.freeze,
|
|
160
161
|
raise_exception_on_sigterm: true,
|
|
@@ -196,7 +197,7 @@ module Puma
|
|
|
196
197
|
@clamped = false
|
|
197
198
|
end
|
|
198
199
|
|
|
199
|
-
attr_reader :plugins, :events, :hooks
|
|
200
|
+
attr_reader :plugins, :events, :hooks, :_options
|
|
200
201
|
|
|
201
202
|
def options
|
|
202
203
|
raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
|
|
@@ -237,18 +238,14 @@ module Puma
|
|
|
237
238
|
min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
|
|
238
239
|
max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
|
|
239
240
|
persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
::Concurrent.available_processor_count
|
|
243
|
-
else
|
|
244
|
-
env['WEB_CONCURRENCY']
|
|
245
|
-
end
|
|
241
|
+
workers_env = env['WEB_CONCURRENCY']
|
|
242
|
+
workers = workers_env && workers_env.strip != "" ? parse_workers(workers_env.strip) : nil
|
|
246
243
|
|
|
247
244
|
{
|
|
248
245
|
min_threads: min && min != "" && Integer(min),
|
|
249
246
|
max_threads: max && max != "" && Integer(max),
|
|
250
247
|
persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
|
|
251
|
-
workers: workers
|
|
248
|
+
workers: workers,
|
|
252
249
|
environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
|
|
253
250
|
}
|
|
254
251
|
end
|
|
@@ -379,12 +376,23 @@ module Puma
|
|
|
379
376
|
require 'concurrent/utility/processor_counter'
|
|
380
377
|
rescue LoadError
|
|
381
378
|
warn <<~MESSAGE
|
|
382
|
-
WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
|
|
379
|
+
WEB_CONCURRENCY=auto or workers(:auto) requires the "concurrent-ruby" gem to be installed.
|
|
383
380
|
Please add "concurrent-ruby" to your Gemfile.
|
|
384
381
|
MESSAGE
|
|
385
382
|
raise
|
|
386
383
|
end
|
|
387
384
|
|
|
385
|
+
def parse_workers(value)
|
|
386
|
+
if value == :auto || value == 'auto'
|
|
387
|
+
require_processor_counter
|
|
388
|
+
Integer(::Concurrent.available_processor_count)
|
|
389
|
+
else
|
|
390
|
+
Integer(value)
|
|
391
|
+
end
|
|
392
|
+
rescue ArgumentError, TypeError
|
|
393
|
+
raise ArgumentError, "workers must be an Integer or :auto"
|
|
394
|
+
end
|
|
395
|
+
|
|
388
396
|
# Load and use the normal Rack builder if we can, otherwise
|
|
389
397
|
# fallback to our minimal version.
|
|
390
398
|
def rack_builder
|
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 = "7.0
|
|
104
|
-
CODE_NAME = "
|
|
103
|
+
PUMA_VERSION = VERSION = "7.2.0"
|
|
104
|
+
CODE_NAME = "On The Corner"
|
|
105
105
|
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
|
107
107
|
|
data/lib/puma/dsl.rb
CHANGED
|
@@ -216,6 +216,8 @@ module Puma
|
|
|
216
216
|
# activate_control_app 'unix:///var/run/pumactl.sock', { auth_token: '12345' }
|
|
217
217
|
# @example
|
|
218
218
|
# activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true }
|
|
219
|
+
# @example
|
|
220
|
+
# activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true, data_only: true}
|
|
219
221
|
#
|
|
220
222
|
def activate_control_app(url="auto", opts={})
|
|
221
223
|
if url == "auto"
|
|
@@ -240,6 +242,7 @@ module Puma
|
|
|
240
242
|
|
|
241
243
|
@options[:control_auth_token] = auth_token
|
|
242
244
|
@options[:control_url_umask] = opts[:umask] if opts[:umask]
|
|
245
|
+
@options[:control_data_only] = opts[:data_only] if opts[:data_only]
|
|
243
246
|
end
|
|
244
247
|
|
|
245
248
|
# Load additional configuration from a file.
|
|
@@ -656,7 +659,8 @@ module Puma
|
|
|
656
659
|
@options[:state] = path.to_s
|
|
657
660
|
end
|
|
658
661
|
|
|
659
|
-
# Use +permission+ to restrict permissions for the state file.
|
|
662
|
+
# Use +permission+ to restrict permissions for the state file. By convention,
|
|
663
|
+
# +permission+ is an octal number (e.g. `0640` or `0o640`).
|
|
660
664
|
#
|
|
661
665
|
# @example
|
|
662
666
|
# state_permission 0600
|
|
@@ -665,21 +669,27 @@ module Puma
|
|
|
665
669
|
@options[:state_permission] = permission
|
|
666
670
|
end
|
|
667
671
|
|
|
668
|
-
# How many worker processes to run.
|
|
669
|
-
#
|
|
672
|
+
# How many worker processes to run. Typically this is set to the number of
|
|
673
|
+
# available cores.
|
|
670
674
|
#
|
|
671
675
|
# The default is the value of the environment variable +WEB_CONCURRENCY+ if
|
|
672
|
-
# set, otherwise 0.
|
|
676
|
+
# set, otherwise 0. Passing +:auto+ will set the value to
|
|
677
|
+
# +Concurrent.available_processor_count+ (requires the concurrent-ruby gem).
|
|
678
|
+
# On some platforms (e.g. under CPU quotas) this may be fractional, and Puma
|
|
679
|
+
# will round down. If it rounds down to 0, Puma will run in single mode and
|
|
680
|
+
# cluster-only hooks like +before_worker_boot+ will not execute.
|
|
681
|
+
# If you rely on cluster-only hooks, set an explicit worker count.
|
|
673
682
|
#
|
|
674
|
-
#
|
|
683
|
+
# A value of 0 or nil means run in single mode.
|
|
675
684
|
#
|
|
676
685
|
# @example
|
|
677
686
|
# workers 2
|
|
687
|
+
# workers :auto
|
|
678
688
|
#
|
|
679
689
|
# @see Puma::Cluster
|
|
680
690
|
#
|
|
681
691
|
def workers(count)
|
|
682
|
-
@options[:workers] = count.
|
|
692
|
+
@options[:workers] = count.nil? ? 0 : @config.send(:parse_workers, count)
|
|
683
693
|
end
|
|
684
694
|
|
|
685
695
|
# Disable warning message when running in cluster mode with a single worker.
|
|
@@ -818,6 +828,20 @@ module Puma
|
|
|
818
828
|
|
|
819
829
|
alias_method :after_worker_boot, :after_worker_fork
|
|
820
830
|
|
|
831
|
+
# Code to run in the master right after a worker has stopped. The worker's
|
|
832
|
+
# index and Process::Status are passed as arguments.
|
|
833
|
+
#
|
|
834
|
+
# @note Cluster mode only.
|
|
835
|
+
#
|
|
836
|
+
# @example
|
|
837
|
+
# after_worker_shutdown do |worker_handle|
|
|
838
|
+
# puts 'Worker crashed' unless worker_handle.process_status.success?
|
|
839
|
+
# end
|
|
840
|
+
#
|
|
841
|
+
def after_worker_shutdown(&block)
|
|
842
|
+
process_hook :after_worker_shutdown, nil, block, cluster_only: true
|
|
843
|
+
end
|
|
844
|
+
|
|
821
845
|
# Code to run after puma is booted (works for both single and cluster modes).
|
|
822
846
|
#
|
|
823
847
|
# @example
|
|
@@ -980,6 +1004,7 @@ module Puma
|
|
|
980
1004
|
# The default is +true+ if your app uses more than 1 worker.
|
|
981
1005
|
#
|
|
982
1006
|
# @note Cluster mode only.
|
|
1007
|
+
# @note When using `fork_worker`, this only applies to worker 0.
|
|
983
1008
|
#
|
|
984
1009
|
# @example
|
|
985
1010
|
# preload_app!
|
|
@@ -1207,13 +1232,19 @@ module Puma
|
|
|
1207
1232
|
end
|
|
1208
1233
|
|
|
1209
1234
|
|
|
1210
|
-
#
|
|
1211
|
-
#
|
|
1235
|
+
# Maximum delay of worker accept loop.
|
|
1236
|
+
#
|
|
1237
|
+
# Attempts to route traffic to less-busy workers by causing a busy worker to delay
|
|
1238
|
+
# listening on the socket, allowing workers which are not processing as many
|
|
1212
1239
|
# requests to pick up new requests first.
|
|
1213
1240
|
#
|
|
1214
1241
|
# The default is 0.005 seconds.
|
|
1215
1242
|
#
|
|
1216
|
-
#
|
|
1243
|
+
# To turn off this feature, set the value to 0.
|
|
1244
|
+
#
|
|
1245
|
+
# @note Cluster mode with >= 2 workers only.
|
|
1246
|
+
#
|
|
1247
|
+
# @note Interpreters with forking support only.
|
|
1217
1248
|
#
|
|
1218
1249
|
# @see Puma::Server#handle_servers
|
|
1219
1250
|
# @see Puma::ThreadPool#wait_for_less_busy_worker
|
|
@@ -1357,7 +1388,7 @@ module Puma
|
|
|
1357
1388
|
#
|
|
1358
1389
|
# The default is +:auto+.
|
|
1359
1390
|
#
|
|
1360
|
-
# @see https://github.com/socketry/nio4r/blob/
|
|
1391
|
+
# @see https://github.com/socketry/nio4r/blob/main/lib/nio/selector.rb
|
|
1361
1392
|
#
|
|
1362
1393
|
def io_selector_backend(backend)
|
|
1363
1394
|
@options[:io_selector_backend] = backend.to_sym
|
data/lib/puma/launcher.rb
CHANGED
|
@@ -42,26 +42,40 @@ module Puma
|
|
|
42
42
|
# end
|
|
43
43
|
# Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
|
|
44
44
|
def initialize(conf, launcher_args={})
|
|
45
|
-
|
|
46
|
-
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
|
47
|
-
@events = launcher_args[:events] || Events.new
|
|
48
|
-
@argv = launcher_args[:argv] || []
|
|
49
|
-
@original_argv = @argv.dup
|
|
50
|
-
@config = conf
|
|
51
|
-
|
|
52
|
-
env = launcher_args.delete(:env) || ENV
|
|
45
|
+
## Minimal initialization before potential early restart (e.g. from bundle pruning)
|
|
53
46
|
|
|
47
|
+
@config = conf
|
|
48
|
+
# Advertise the CLI Configuration before config files are loaded
|
|
49
|
+
Puma.cli_config = @config if defined?(Puma.cli_config)
|
|
54
50
|
@config.clamp
|
|
51
|
+
|
|
55
52
|
@options = @config.options
|
|
56
53
|
|
|
54
|
+
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
|
55
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
|
56
|
+
@log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
|
|
57
|
+
@log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
|
|
57
58
|
@options[:log_writer] = @log_writer
|
|
58
59
|
@options[:logger] = @log_writer if clustered?
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
@events = launcher_args[:events] || Events.new
|
|
62
|
+
|
|
63
|
+
@argv = launcher_args[:argv] || []
|
|
64
|
+
@original_argv = @argv.dup
|
|
65
|
+
|
|
66
|
+
## End minimal initialization
|
|
67
|
+
|
|
68
|
+
generate_restart_data
|
|
69
|
+
Dir.chdir(@restart_dir)
|
|
70
|
+
|
|
71
|
+
prune_bundler!
|
|
72
|
+
|
|
73
|
+
env = launcher_args.delete(:env) || ENV
|
|
74
|
+
|
|
75
|
+
# Log after prune_bundler! to avoid duplicate logging if a restart occurs
|
|
62
76
|
log_config if env['PUMA_LOG_CONFIG']
|
|
63
77
|
|
|
64
|
-
@binder
|
|
78
|
+
@binder = Binder.new(@log_writer, @options)
|
|
65
79
|
@binder.create_inherited_fds(env).each { |k| env.delete k }
|
|
66
80
|
@binder.create_activated_fds(env).each { |k| env.delete k }
|
|
67
81
|
|
|
@@ -81,21 +95,10 @@ module Puma
|
|
|
81
95
|
)
|
|
82
96
|
end
|
|
83
97
|
|
|
84
|
-
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
|
85
|
-
@log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
|
|
86
|
-
|
|
87
|
-
@log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
|
|
88
|
-
|
|
89
|
-
generate_restart_data
|
|
90
|
-
|
|
91
98
|
if clustered? && !Puma.forkable?
|
|
92
99
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
|
93
100
|
end
|
|
94
101
|
|
|
95
|
-
Dir.chdir(@restart_dir)
|
|
96
|
-
|
|
97
|
-
prune_bundler!
|
|
98
|
-
|
|
99
102
|
@environment = @options[:environment] if @options[:environment]
|
|
100
103
|
set_rack_environment
|
|
101
104
|
|
|
@@ -139,7 +142,10 @@ module Puma
|
|
|
139
142
|
# Delete the configured pidfile
|
|
140
143
|
def delete_pidfile
|
|
141
144
|
path = @options[:pidfile]
|
|
142
|
-
|
|
145
|
+
begin
|
|
146
|
+
File.unlink(path) if path
|
|
147
|
+
rescue Errno::ENOENT
|
|
148
|
+
end
|
|
143
149
|
end
|
|
144
150
|
|
|
145
151
|
# Begin async shutdown of the server
|
|
@@ -381,9 +387,9 @@ module Puma
|
|
|
381
387
|
# using it.
|
|
382
388
|
@restart_dir = Dir.pwd
|
|
383
389
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
390
|
+
# Use the same trick as unicorn, namely favor PWD because
|
|
391
|
+
# it will contain an unresolved symlink, useful for when
|
|
392
|
+
# the pwd is /data/releases/current.
|
|
387
393
|
elsif dir = ENV['PWD']
|
|
388
394
|
s_env = File.stat(dir)
|
|
389
395
|
s_pwd = File.stat(Dir.pwd)
|
data/lib/puma/reactor.rb
CHANGED
|
@@ -75,15 +75,12 @@ module Puma
|
|
|
75
75
|
private
|
|
76
76
|
|
|
77
77
|
def select_loop
|
|
78
|
-
close_selector = true
|
|
79
78
|
begin
|
|
80
79
|
until @input.closed? && @input.empty?
|
|
81
80
|
# Wakeup any registered object that receives incoming data.
|
|
82
81
|
# Block until the earliest timeout or Selector#wakeup is called.
|
|
83
82
|
timeout = (earliest = @timeouts.first) && earliest.timeout
|
|
84
|
-
monitor_wake_up = false
|
|
85
83
|
@selector.select(timeout) do |monitor|
|
|
86
|
-
monitor_wake_up = true
|
|
87
84
|
wakeup!(monitor.value)
|
|
88
85
|
end
|
|
89
86
|
|
|
@@ -103,18 +100,12 @@ module Puma
|
|
|
103
100
|
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
|
104
101
|
STDERR.puts e.backtrace
|
|
105
102
|
|
|
106
|
-
|
|
107
|
-
# is odd. Regardless, it may continue for thousands of calls if retried.
|
|
108
|
-
# Also, when it raises, @selector.close also raises an error.
|
|
109
|
-
if !monitor_wake_up && NoMethodError === e
|
|
110
|
-
close_selector = false
|
|
111
|
-
else
|
|
112
|
-
retry
|
|
113
|
-
end
|
|
103
|
+
retry
|
|
114
104
|
end
|
|
105
|
+
|
|
115
106
|
# Wakeup all remaining objects on shutdown.
|
|
116
107
|
@timeouts.each(&@block)
|
|
117
|
-
@selector.close
|
|
108
|
+
@selector.close
|
|
118
109
|
end
|
|
119
110
|
|
|
120
111
|
# Start monitoring the object.
|
data/lib/puma/request.rb
CHANGED
|
@@ -36,17 +36,19 @@ module Puma
|
|
|
36
36
|
# Takes the request contained in +client+, invokes the Rack application to construct
|
|
37
37
|
# the response and writes it back to +client.io+.
|
|
38
38
|
#
|
|
39
|
-
# It'll return +
|
|
39
|
+
# It'll return +:close+ when the connection is closed, this doesn't mean
|
|
40
40
|
# that the response wasn't successful.
|
|
41
41
|
#
|
|
42
|
+
# It'll return +:keep_alive+ if the connection is a pipeline or keep-alive connection.
|
|
43
|
+
# Which may contain additional requests.
|
|
44
|
+
#
|
|
42
45
|
# It'll return +:async+ if the connection remains open but will be handled
|
|
43
46
|
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
44
47
|
#
|
|
45
48
|
# Finally, it'll return +true+ on keep-alive connections.
|
|
46
49
|
# @param client [Puma::Client]
|
|
47
50
|
# @param requests [Integer]
|
|
48
|
-
# @return [
|
|
49
|
-
#
|
|
51
|
+
# @return [:close, :keep_alive, :async]
|
|
50
52
|
def handle_request(client, requests)
|
|
51
53
|
env = client.env
|
|
52
54
|
io_buffer = client.io_buffer
|
|
@@ -54,7 +56,7 @@ module Puma
|
|
|
54
56
|
app_body = nil
|
|
55
57
|
error = nil
|
|
56
58
|
|
|
57
|
-
return
|
|
59
|
+
return :close if closed_socket?(socket)
|
|
58
60
|
|
|
59
61
|
if client.http_content_length_limit_exceeded
|
|
60
62
|
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
|
@@ -167,13 +169,13 @@ module Puma
|
|
|
167
169
|
# a call to `Server#lowlevel_error`
|
|
168
170
|
# @param requests [Integer] number of inline requests handled
|
|
169
171
|
# @param client [Puma::Client]
|
|
170
|
-
# @return [
|
|
172
|
+
# @return [:close, :keep_alive, :async]
|
|
171
173
|
def prepare_response(status, headers, res_body, requests, client)
|
|
172
174
|
env = client.env
|
|
173
175
|
socket = client.io
|
|
174
176
|
io_buffer = client.io_buffer
|
|
175
177
|
|
|
176
|
-
return
|
|
178
|
+
return :close if closed_socket?(socket)
|
|
177
179
|
|
|
178
180
|
# Close the connection after a reasonable number of inline requests
|
|
179
181
|
force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
|
|
@@ -244,7 +246,7 @@ module Puma
|
|
|
244
246
|
io_buffer << LINE_END
|
|
245
247
|
fast_write_str socket, io_buffer.read_and_reset
|
|
246
248
|
socket.flush
|
|
247
|
-
return keep_alive
|
|
249
|
+
return keep_alive ? :keep_alive : :close
|
|
248
250
|
end
|
|
249
251
|
else
|
|
250
252
|
if content_length
|
|
@@ -270,7 +272,7 @@ module Puma
|
|
|
270
272
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
|
271
273
|
body.close if close_body
|
|
272
274
|
# if we're shutting down, close keep_alive connections
|
|
273
|
-
!shutting_down? && keep_alive
|
|
275
|
+
!shutting_down? && keep_alive ? :keep_alive : :close
|
|
274
276
|
end
|
|
275
277
|
|
|
276
278
|
HTTP_ON_VALUES = { "on" => true, HTTPS => true }
|
data/lib/puma/runner.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Puma
|
|
|
70
70
|
token = nil if token.empty? || token == 'none'
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
app = Puma::App::Status.new @launcher, token
|
|
73
|
+
app = Puma::App::Status.new @launcher, token: token, data_only: @options[:control_data_only]
|
|
74
74
|
|
|
75
75
|
# A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
|
|
76
76
|
# Use `nil` for events, no hooks in control server
|
data/lib/puma/server.rb
CHANGED
|
@@ -19,9 +19,6 @@ require 'socket'
|
|
|
19
19
|
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
|
20
20
|
|
|
21
21
|
module Puma
|
|
22
|
-
# Add `Thread#puma_server` and `Thread#puma_server=`
|
|
23
|
-
Thread.attr_accessor(:puma_server)
|
|
24
|
-
|
|
25
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
|
26
23
|
#
|
|
27
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
|
@@ -262,7 +259,7 @@ module Puma
|
|
|
262
259
|
|
|
263
260
|
@status = :run
|
|
264
261
|
|
|
265
|
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
|
|
266
263
|
|
|
267
264
|
if @queue_requests
|
|
268
265
|
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
@@ -302,7 +299,7 @@ module Puma
|
|
|
302
299
|
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
303
300
|
# such as nginx) then the application would be subject to a slow client attack.
|
|
304
301
|
#
|
|
305
|
-
# For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/
|
|
302
|
+
# For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/main/docs/architecture.md).
|
|
306
303
|
#
|
|
307
304
|
# The method checks to see if it has the full header and body with
|
|
308
305
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
@@ -481,9 +478,6 @@ module Puma
|
|
|
481
478
|
#
|
|
482
479
|
# Return true if one or more requests were processed.
|
|
483
480
|
def process_client(client)
|
|
484
|
-
# Advertise this server into the thread
|
|
485
|
-
Thread.current.puma_server = self
|
|
486
|
-
|
|
487
481
|
close_socket = true
|
|
488
482
|
|
|
489
483
|
requests = 0
|
|
@@ -502,31 +496,41 @@ module Puma
|
|
|
502
496
|
client.finish(@first_data_timeout)
|
|
503
497
|
end
|
|
504
498
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
499
|
+
can_loop = true
|
|
500
|
+
while can_loop
|
|
501
|
+
can_loop = false
|
|
502
|
+
@requests_count += 1
|
|
503
|
+
case handle_request(client, requests + 1)
|
|
504
|
+
when :close
|
|
505
|
+
when :async
|
|
506
|
+
close_socket = false
|
|
507
|
+
when :keep_alive
|
|
508
|
+
requests += 1
|
|
512
509
|
|
|
513
|
-
|
|
510
|
+
client.reset
|
|
514
511
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
512
|
+
# This indicates data exists in the client read buffer and there may be
|
|
513
|
+
# additional requests on it, so process them
|
|
514
|
+
next_request_ready = if client.has_back_to_back_requests?
|
|
515
|
+
with_force_shutdown(client) { client.process_back_to_back_requests }
|
|
516
|
+
else
|
|
517
|
+
with_force_shutdown(client) { client.eagerly_finish }
|
|
518
|
+
end
|
|
522
519
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
520
|
+
if next_request_ready
|
|
521
|
+
# When Puma has spare threads, allow this one to be monopolized
|
|
522
|
+
# Perf optimization for https://github.com/puma/puma/issues/3788
|
|
523
|
+
if @thread_pool.waiting > 0
|
|
524
|
+
can_loop = true
|
|
525
|
+
else
|
|
526
|
+
@thread_pool << client
|
|
527
|
+
close_socket = false
|
|
528
|
+
end
|
|
529
|
+
elsif @queue_requests
|
|
530
|
+
client.set_timeout @persistent_timeout
|
|
531
|
+
if @reactor.add client
|
|
532
|
+
close_socket = false
|
|
533
|
+
end
|
|
530
534
|
end
|
|
531
535
|
end
|
|
532
536
|
end
|
|
@@ -706,7 +710,7 @@ module Puma
|
|
|
706
710
|
|
|
707
711
|
def reset_max
|
|
708
712
|
@reactor.reactor_max = 0 if @reactor
|
|
709
|
-
@thread_pool
|
|
713
|
+
@thread_pool&.reset_max
|
|
710
714
|
end
|
|
711
715
|
|
|
712
716
|
# below are 'delegations' to binder
|
data/lib/puma/single.rb
CHANGED
data/lib/puma/state_file.rb
CHANGED
|
@@ -32,10 +32,11 @@ module Puma
|
|
|
32
32
|
"#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
+
|
|
35
36
|
if permission
|
|
36
|
-
File.write path, contents, mode: 'wb:UTF-8'
|
|
37
|
-
else
|
|
38
37
|
File.write path, contents, mode: 'wb:UTF-8', perm: permission
|
|
38
|
+
else
|
|
39
|
+
File.write path, contents, mode: 'wb:UTF-8'
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
data/lib/puma/thread_pool.rb
CHANGED
|
@@ -5,6 +5,10 @@ require 'thread'
|
|
|
5
5
|
require_relative 'io_buffer'
|
|
6
6
|
|
|
7
7
|
module Puma
|
|
8
|
+
|
|
9
|
+
# Add `Thread#puma_server` and `Thread#puma_server=`
|
|
10
|
+
Thread.attr_accessor(:puma_server)
|
|
11
|
+
|
|
8
12
|
# Internal Docs for A simple thread pool management object.
|
|
9
13
|
#
|
|
10
14
|
# Each Puma "worker" has a thread pool to process requests.
|
|
@@ -33,7 +37,9 @@ module Puma
|
|
|
33
37
|
# The block passed is the work that will be performed in each
|
|
34
38
|
# thread.
|
|
35
39
|
#
|
|
36
|
-
def initialize(name, options = {}, &block)
|
|
40
|
+
def initialize(name, options = {}, server: nil, &block)
|
|
41
|
+
@server = server
|
|
42
|
+
|
|
37
43
|
@not_empty = ConditionVariable.new
|
|
38
44
|
@not_full = ConditionVariable.new
|
|
39
45
|
@mutex = Mutex.new
|
|
@@ -134,6 +140,9 @@ module Puma
|
|
|
134
140
|
trigger_before_thread_start_hooks
|
|
135
141
|
th = Thread.new(@spawned) do |spawned|
|
|
136
142
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
|
143
|
+
# Advertise server into the thread
|
|
144
|
+
Thread.current.puma_server = @server
|
|
145
|
+
|
|
137
146
|
todo = @todo
|
|
138
147
|
block = @block
|
|
139
148
|
mutex = @mutex
|