puma 7.0.4 → 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 +162 -0
- data/README.md +25 -13
- 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/deployment.md +58 -23
- 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/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/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/ext/puma_http11/puma_http11.c +101 -109
- data/lib/puma/app/status.rb +10 -2
- data/lib/puma/binder.rb +2 -2
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +138 -102
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +10 -9
- data/lib/puma/cluster/worker_handle.rb +2 -2
- data/lib/puma/cluster.rb +12 -11
- data/lib/puma/cluster_accept_loop_delay.rb +17 -18
- data/lib/puma/configuration.rb +87 -17
- 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 +115 -18
- data/lib/puma/launcher/bundle_pruner.rb +2 -4
- data/lib/puma/launcher.rb +35 -30
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/reactor.rb +3 -12
- data/lib/puma/{request.rb → response.rb} +25 -194
- data/lib/puma/runner.rb +1 -1
- data/lib/puma/server.rb +103 -64
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +2 -2
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +139 -24
- data/lib/rack/handler/puma.rb +1 -1
- data/tools/Dockerfile +13 -5
- metadata +16 -5
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/cluster/worker.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Puma
|
|
|
14
14
|
class Worker < Puma::Runner # :nodoc:
|
|
15
15
|
attr_reader :index, :master
|
|
16
16
|
|
|
17
|
-
def initialize(index:, master:, launcher:, pipes:,
|
|
17
|
+
def initialize(index:, master:, launcher:, pipes:, app: nil)
|
|
18
18
|
super(launcher)
|
|
19
19
|
|
|
20
20
|
@index = index
|
|
@@ -23,7 +23,8 @@ module Puma
|
|
|
23
23
|
@worker_write = pipes[:worker_write]
|
|
24
24
|
@fork_pipe = pipes[:fork_pipe]
|
|
25
25
|
@wakeup = pipes[:wakeup]
|
|
26
|
-
@
|
|
26
|
+
@app = app
|
|
27
|
+
@server = nil
|
|
27
28
|
@hook_data = {}
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -57,7 +58,7 @@ module Puma
|
|
|
57
58
|
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
|
58
59
|
|
|
59
60
|
begin
|
|
60
|
-
|
|
61
|
+
@server = start_server
|
|
61
62
|
rescue Exception => e
|
|
62
63
|
log "! Unable to start worker"
|
|
63
64
|
log e
|
|
@@ -85,7 +86,7 @@ module Puma
|
|
|
85
86
|
if idx == -1 # stop server
|
|
86
87
|
if restart_server.length > 0
|
|
87
88
|
restart_server.clear
|
|
88
|
-
server.begin_restart(true)
|
|
89
|
+
@server.begin_restart(true)
|
|
89
90
|
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
|
90
91
|
end
|
|
91
92
|
elsif idx == -2 # refork cycle is done
|
|
@@ -103,7 +104,7 @@ module Puma
|
|
|
103
104
|
Signal.trap "SIGTERM" do
|
|
104
105
|
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
|
105
106
|
restart_server.clear
|
|
106
|
-
server.stop
|
|
107
|
+
@server.stop
|
|
107
108
|
restart_server << false
|
|
108
109
|
end
|
|
109
110
|
|
|
@@ -115,7 +116,7 @@ module Puma
|
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
while restart_server.pop
|
|
118
|
-
server_thread = server.run
|
|
119
|
+
server_thread = @server.run
|
|
119
120
|
|
|
120
121
|
if @log_writer.debug? && index == 0
|
|
121
122
|
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
|
@@ -129,13 +130,13 @@ module Puma
|
|
|
129
130
|
begin
|
|
130
131
|
payload = base_payload.dup
|
|
131
132
|
|
|
132
|
-
hsh = server.stats
|
|
133
|
+
hsh = @server.stats
|
|
133
134
|
hsh.each do |k, v|
|
|
134
135
|
payload << %Q! "#{k}":#{v || 0},!
|
|
135
136
|
end
|
|
136
137
|
# sub call properly adds 'closing' string
|
|
137
138
|
io << payload.sub(/,\z/, " }\n")
|
|
138
|
-
server.reset_max
|
|
139
|
+
@server.reset_max
|
|
139
140
|
rescue IOError
|
|
140
141
|
break
|
|
141
142
|
end
|
|
@@ -164,7 +165,7 @@ module Puma
|
|
|
164
165
|
launcher: @launcher,
|
|
165
166
|
pipes: { check_pipe: @check_pipe,
|
|
166
167
|
worker_write: @worker_write },
|
|
167
|
-
|
|
168
|
+
app: @app
|
|
168
169
|
new_worker.run
|
|
169
170
|
end
|
|
170
171
|
|
|
@@ -28,10 +28,10 @@ module Puma
|
|
|
28
28
|
@worker_max = Array.new WORKER_MAX_KEYS.length, 0
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
|
31
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at, :process_status
|
|
32
32
|
|
|
33
33
|
# @version 5.0.0
|
|
34
|
-
attr_writer :pid, :phase
|
|
34
|
+
attr_writer :pid, :phase, :process_status
|
|
35
35
|
|
|
36
36
|
def booted?
|
|
37
37
|
@stage == :booted
|
data/lib/puma/cluster.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Puma
|
|
|
23
23
|
@next_check = Time.now
|
|
24
24
|
|
|
25
25
|
@worker_max = [] # keeps track of 'max' stat values
|
|
26
|
-
@
|
|
26
|
+
@pending_phased_restart = false
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# Returns the list of cluster worker handles.
|
|
@@ -87,7 +87,7 @@ module Puma
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
debug "Spawned worker: #{pid}"
|
|
90
|
-
@workers
|
|
90
|
+
@workers.insert(idx, WorkerHandle.new(idx, pid, @phase, @options))
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
if @options[:fork_worker] && all_workers_in_phase?
|
|
@@ -186,7 +186,7 @@ module Puma
|
|
|
186
186
|
# we need to phase any workers out (which will restart
|
|
187
187
|
# in the right phase).
|
|
188
188
|
#
|
|
189
|
-
w = @workers.find { |x| x.phase
|
|
189
|
+
w = @workers.find { |x| x.phase < @phase }
|
|
190
190
|
|
|
191
191
|
if w
|
|
192
192
|
if refork
|
|
@@ -221,12 +221,11 @@ module Puma
|
|
|
221
221
|
pipes[:wakeup] = @wakeup
|
|
222
222
|
end
|
|
223
223
|
|
|
224
|
-
server = start_server if preload?
|
|
225
224
|
new_worker = Worker.new index: index,
|
|
226
225
|
master: master,
|
|
227
226
|
launcher: @launcher,
|
|
228
227
|
pipes: pipes,
|
|
229
|
-
|
|
228
|
+
app: (app if preload?)
|
|
230
229
|
new_worker.run
|
|
231
230
|
end
|
|
232
231
|
|
|
@@ -238,7 +237,7 @@ module Puma
|
|
|
238
237
|
def phased_restart(refork = false)
|
|
239
238
|
return false if @options[:preload_app] && !refork
|
|
240
239
|
|
|
241
|
-
@
|
|
240
|
+
@pending_phased_restart = refork ? :refork : true
|
|
242
241
|
wakeup!
|
|
243
242
|
|
|
244
243
|
true
|
|
@@ -456,11 +455,11 @@ module Puma
|
|
|
456
455
|
break
|
|
457
456
|
end
|
|
458
457
|
|
|
459
|
-
if @
|
|
460
|
-
start_phased_restart(@
|
|
458
|
+
if @pending_phased_restart
|
|
459
|
+
start_phased_restart(@pending_phased_restart == :refork)
|
|
461
460
|
|
|
462
|
-
in_phased_restart = @
|
|
463
|
-
@
|
|
461
|
+
in_phased_restart = @pending_phased_restart
|
|
462
|
+
@pending_phased_restart = false
|
|
464
463
|
|
|
465
464
|
workers_not_booted = @options[:workers]
|
|
466
465
|
# worker 0 is not restarted on refork
|
|
@@ -583,7 +582,9 @@ module Puma
|
|
|
583
582
|
# `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
|
|
584
583
|
# 2. When `fork_worker` is enabled, some worker may not be direct children,
|
|
585
584
|
# but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
|
|
586
|
-
if reaped_children.delete(w.pid) || Process.
|
|
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)
|
|
587
588
|
true
|
|
588
589
|
else
|
|
589
590
|
w.term if w.term?
|
|
@@ -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
|
@@ -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,
|
|
@@ -155,15 +161,16 @@ module Puma
|
|
|
155
161
|
out_of_band: [],
|
|
156
162
|
# Number of seconds for another request within a persistent session.
|
|
157
163
|
persistent_timeout: 65, # PUMA_PERSISTENT_TIMEOUT
|
|
164
|
+
prune_bundler: false,
|
|
158
165
|
queue_requests: true,
|
|
159
166
|
rackup: 'config.ru'.freeze,
|
|
160
167
|
raise_exception_on_sigterm: true,
|
|
161
168
|
reaping_time: 1,
|
|
162
169
|
remote_address: :socket,
|
|
163
|
-
silence_single_worker_warning: false,
|
|
164
170
|
silence_fork_callback_warning: false,
|
|
171
|
+
silence_single_worker_warning: false,
|
|
165
172
|
tag: File.basename(Dir.getwd),
|
|
166
|
-
tcp_host: '
|
|
173
|
+
tcp_host: '::'.freeze,
|
|
167
174
|
tcp_port: 9292,
|
|
168
175
|
wait_for_less_busy_worker: 0.005,
|
|
169
176
|
worker_boot_timeout: 60,
|
|
@@ -172,11 +179,10 @@ module Puma
|
|
|
172
179
|
worker_shutdown_timeout: 30,
|
|
173
180
|
worker_timeout: 60,
|
|
174
181
|
workers: 0,
|
|
175
|
-
http_content_length_limit: nil
|
|
176
182
|
}
|
|
177
183
|
|
|
178
184
|
def initialize(user_options={}, default_options = {}, env = ENV, &block)
|
|
179
|
-
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)
|
|
180
186
|
|
|
181
187
|
@_options = UserFileDefaultOptions.new(user_options, default_options)
|
|
182
188
|
@plugins = PluginLoader.new
|
|
@@ -196,7 +202,7 @@ module Puma
|
|
|
196
202
|
@clamped = false
|
|
197
203
|
end
|
|
198
204
|
|
|
199
|
-
attr_reader :plugins, :events, :hooks
|
|
205
|
+
attr_reader :plugins, :events, :hooks, :_options
|
|
200
206
|
|
|
201
207
|
def options
|
|
202
208
|
raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
|
|
@@ -229,6 +235,8 @@ module Puma
|
|
|
229
235
|
|
|
230
236
|
def puma_default_options(env = ENV)
|
|
231
237
|
defaults = DEFAULTS.dup
|
|
238
|
+
defaults[:tcp_host] = self.class.default_tcp_host
|
|
239
|
+
defaults[:binds] = [self.class.default_tcp_bind]
|
|
232
240
|
puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
|
|
233
241
|
defaults
|
|
234
242
|
end
|
|
@@ -237,18 +245,14 @@ module Puma
|
|
|
237
245
|
min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
|
|
238
246
|
max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
|
|
239
247
|
persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
::Concurrent.available_processor_count
|
|
243
|
-
else
|
|
244
|
-
env['WEB_CONCURRENCY']
|
|
245
|
-
end
|
|
248
|
+
workers_env = env['WEB_CONCURRENCY']
|
|
249
|
+
workers = workers_env && workers_env.strip != "" ? parse_workers(workers_env.strip) : nil
|
|
246
250
|
|
|
247
251
|
{
|
|
248
252
|
min_threads: min && min != "" && Integer(min),
|
|
249
253
|
max_threads: max && max != "" && Integer(max),
|
|
250
254
|
persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
|
|
251
|
-
workers: workers
|
|
255
|
+
workers: workers,
|
|
252
256
|
environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
|
|
253
257
|
}
|
|
254
258
|
end
|
|
@@ -280,8 +284,10 @@ module Puma
|
|
|
280
284
|
# This also calls load if it hasn't been called yet.
|
|
281
285
|
def clamp
|
|
282
286
|
load unless @loaded
|
|
287
|
+
run_mode_hooks
|
|
283
288
|
set_conditional_default_options
|
|
284
289
|
@_options.finalize_values
|
|
290
|
+
rewrite_unavailable_ipv6_binds!
|
|
285
291
|
@clamped = true
|
|
286
292
|
warn_hooks
|
|
287
293
|
options
|
|
@@ -338,7 +344,7 @@ module Puma
|
|
|
338
344
|
# @param arg [Launcher, Int] `:before_restart` passes Launcher
|
|
339
345
|
#
|
|
340
346
|
def run_hooks(key, arg, log_writer, hook_data = nil)
|
|
341
|
-
log_writer.debug "Running #{key} hooks"
|
|
347
|
+
log_writer.debug { "Running #{key} hooks" }
|
|
342
348
|
|
|
343
349
|
options.all_of(key).each do |hook_options|
|
|
344
350
|
begin
|
|
@@ -360,6 +366,23 @@ module Puma
|
|
|
360
366
|
options.final_options
|
|
361
367
|
end
|
|
362
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
|
+
|
|
363
386
|
def self.temp_path
|
|
364
387
|
require 'tmpdir'
|
|
365
388
|
|
|
@@ -375,16 +398,52 @@ module Puma
|
|
|
375
398
|
|
|
376
399
|
private
|
|
377
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
|
+
|
|
378
426
|
def require_processor_counter
|
|
379
427
|
require 'concurrent/utility/processor_counter'
|
|
380
428
|
rescue LoadError
|
|
381
429
|
warn <<~MESSAGE
|
|
382
|
-
WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
|
|
430
|
+
WEB_CONCURRENCY=auto or workers(:auto) requires the "concurrent-ruby" gem to be installed.
|
|
383
431
|
Please add "concurrent-ruby" to your Gemfile.
|
|
384
432
|
MESSAGE
|
|
385
433
|
raise
|
|
386
434
|
end
|
|
387
435
|
|
|
436
|
+
def parse_workers(value)
|
|
437
|
+
if value == :auto || value == 'auto'
|
|
438
|
+
require_processor_counter
|
|
439
|
+
Integer(::Concurrent.available_processor_count)
|
|
440
|
+
else
|
|
441
|
+
Integer(value)
|
|
442
|
+
end
|
|
443
|
+
rescue ArgumentError, TypeError
|
|
444
|
+
raise ArgumentError, "workers must be an Integer or :auto"
|
|
445
|
+
end
|
|
446
|
+
|
|
388
447
|
# Load and use the normal Rack builder if we can, otherwise
|
|
389
448
|
# fallback to our minimal version.
|
|
390
449
|
def rack_builder
|
|
@@ -425,6 +484,17 @@ module Puma
|
|
|
425
484
|
rack_app
|
|
426
485
|
end
|
|
427
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
|
+
|
|
428
498
|
def set_conditional_default_options
|
|
429
499
|
@_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
|
|
430
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
|