puma 6.6.1 → 7.1.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 +153 -4
- data/README.md +17 -25
- data/docs/fork_worker.md +5 -5
- data/docs/kubernetes.md +8 -6
- data/docs/restart.md +2 -2
- data/docs/signals.md +9 -9
- data/docs/stats.md +3 -2
- data/ext/puma_http11/extconf.rb +2 -17
- data/ext/puma_http11/mini_ssl.c +18 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +9 -1
- data/ext/puma_http11/puma_http11.c +23 -11
- data/lib/puma/binder.rb +10 -8
- data/lib/puma/cli.rb +3 -5
- data/lib/puma/client.rb +52 -56
- data/lib/puma/cluster/worker.rb +9 -10
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +21 -20
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +89 -43
- data/lib/puma/const.rb +9 -10
- data/lib/puma/control_cli.rb +6 -2
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +133 -85
- data/lib/puma/error_logger.rb +3 -1
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/launcher/bundle_pruner.rb +1 -1
- data/lib/puma/launcher.rb +52 -48
- data/lib/puma/minissl.rb +0 -1
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -4
- data/lib/puma/request.rb +33 -24
- data/lib/puma/runner.rb +8 -17
- data/lib/puma/server.rb +111 -61
- data/lib/puma/single.rb +4 -1
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +47 -82
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +2 -2
- metadata +4 -2
data/lib/puma/configuration.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative 'plugin'
|
|
4
4
|
require_relative 'const'
|
|
5
5
|
require_relative 'dsl'
|
|
6
|
+
require_relative 'events'
|
|
6
7
|
|
|
7
8
|
module Puma
|
|
8
9
|
# A class used for storing "leveled" configuration options.
|
|
@@ -112,7 +113,7 @@ module Puma
|
|
|
112
113
|
# config = Configuration.new({}) do |user_config, file_config, default_config|
|
|
113
114
|
# user_config.port 3003
|
|
114
115
|
# end
|
|
115
|
-
# config.
|
|
116
|
+
# config.clamp
|
|
116
117
|
# puts config.options[:port]
|
|
117
118
|
# # => 3003
|
|
118
119
|
#
|
|
@@ -125,10 +126,13 @@ module Puma
|
|
|
125
126
|
# is done because an environment variable may have been modified while loading
|
|
126
127
|
# configuration files.
|
|
127
128
|
class Configuration
|
|
129
|
+
class NotLoadedError < StandardError; end
|
|
130
|
+
class NotClampedError < StandardError; end
|
|
131
|
+
|
|
128
132
|
DEFAULTS = {
|
|
129
133
|
auto_trim_time: 30,
|
|
130
134
|
binds: ['tcp://0.0.0.0:9292'.freeze],
|
|
131
|
-
|
|
135
|
+
fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
|
|
132
136
|
debug: false,
|
|
133
137
|
enable_keep_alives: true,
|
|
134
138
|
early_hints: nil,
|
|
@@ -140,19 +144,18 @@ module Puma
|
|
|
140
144
|
io_selector_backend: :auto,
|
|
141
145
|
log_requests: false,
|
|
142
146
|
logger: STDOUT,
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
# well behaved client from monopolizing the thread forever.
|
|
148
|
-
max_fast_inline: 10,
|
|
147
|
+
# Limits how many requests a keep alive connection can make.
|
|
148
|
+
# The connection will be closed after it reaches `max_keep_alive`
|
|
149
|
+
# requests.
|
|
150
|
+
max_keep_alive: 999,
|
|
149
151
|
max_threads: Puma.mri? ? 5 : 16,
|
|
150
152
|
min_threads: 0,
|
|
151
153
|
mode: :http,
|
|
152
154
|
mutate_stdout_and_stderr_to_sync_on_write: true,
|
|
153
155
|
out_of_band: [],
|
|
154
156
|
# Number of seconds for another request within a persistent session.
|
|
155
|
-
persistent_timeout:
|
|
157
|
+
persistent_timeout: 65, # PUMA_PERSISTENT_TIMEOUT
|
|
158
|
+
prune_bundler: false,
|
|
156
159
|
queue_requests: true,
|
|
157
160
|
rackup: 'config.ru'.freeze,
|
|
158
161
|
raise_exception_on_sigterm: true,
|
|
@@ -176,24 +179,31 @@ module Puma
|
|
|
176
179
|
def initialize(user_options={}, default_options = {}, env = ENV, &block)
|
|
177
180
|
default_options = self.puma_default_options(env).merge(default_options)
|
|
178
181
|
|
|
179
|
-
@
|
|
182
|
+
@_options = UserFileDefaultOptions.new(user_options, default_options)
|
|
180
183
|
@plugins = PluginLoader.new
|
|
181
|
-
@
|
|
182
|
-
@
|
|
183
|
-
@
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
|
|
187
|
-
end
|
|
184
|
+
@events = @_options[:events] || Events.new
|
|
185
|
+
@hooks = {}
|
|
186
|
+
@user_dsl = DSL.new(@_options.user_options, self)
|
|
187
|
+
@file_dsl = DSL.new(@_options.file_options, self)
|
|
188
|
+
@default_dsl = DSL.new(@_options.default_options, self)
|
|
188
189
|
|
|
189
190
|
@puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
|
|
190
191
|
|
|
191
192
|
if block
|
|
192
193
|
configure(&block)
|
|
193
194
|
end
|
|
195
|
+
|
|
196
|
+
@loaded = false
|
|
197
|
+
@clamped = false
|
|
194
198
|
end
|
|
195
199
|
|
|
196
|
-
attr_reader :
|
|
200
|
+
attr_reader :plugins, :events, :hooks
|
|
201
|
+
|
|
202
|
+
def options
|
|
203
|
+
raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
|
|
204
|
+
|
|
205
|
+
@_options
|
|
206
|
+
end
|
|
197
207
|
|
|
198
208
|
def configure
|
|
199
209
|
yield @user_dsl, @file_dsl, @default_dsl
|
|
@@ -206,7 +216,7 @@ module Puma
|
|
|
206
216
|
def initialize_copy(other)
|
|
207
217
|
@conf = nil
|
|
208
218
|
@cli_options = nil
|
|
209
|
-
@
|
|
219
|
+
@_options = @_options.dup
|
|
210
220
|
end
|
|
211
221
|
|
|
212
222
|
def flatten
|
|
@@ -214,7 +224,7 @@ module Puma
|
|
|
214
224
|
end
|
|
215
225
|
|
|
216
226
|
def flatten!
|
|
217
|
-
@
|
|
227
|
+
@_options = @_options.flatten
|
|
218
228
|
self
|
|
219
229
|
end
|
|
220
230
|
|
|
@@ -227,6 +237,7 @@ module Puma
|
|
|
227
237
|
def puma_options_from_env(env = ENV)
|
|
228
238
|
min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
|
|
229
239
|
max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
|
|
240
|
+
persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
|
|
230
241
|
workers = if env['WEB_CONCURRENCY'] == 'auto'
|
|
231
242
|
require_processor_counter
|
|
232
243
|
::Concurrent.available_processor_count
|
|
@@ -237,24 +248,27 @@ module Puma
|
|
|
237
248
|
{
|
|
238
249
|
min_threads: min && min != "" && Integer(min),
|
|
239
250
|
max_threads: max && max != "" && Integer(max),
|
|
251
|
+
persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
|
|
240
252
|
workers: workers && workers != "" && Integer(workers),
|
|
241
253
|
environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
|
|
242
254
|
}
|
|
243
255
|
end
|
|
244
256
|
|
|
245
257
|
def load
|
|
258
|
+
@loaded = true
|
|
246
259
|
config_files.each { |config_file| @file_dsl._load_from(config_file) }
|
|
247
|
-
|
|
248
|
-
@options
|
|
260
|
+
@_options
|
|
249
261
|
end
|
|
250
262
|
|
|
251
263
|
def config_files
|
|
252
|
-
|
|
264
|
+
raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded
|
|
265
|
+
|
|
266
|
+
files = @_options.all_of(:config_files)
|
|
253
267
|
|
|
254
268
|
return [] if files == ['-']
|
|
255
269
|
return files if files.any?
|
|
256
270
|
|
|
257
|
-
first_default_file = %W(config/puma/#{@
|
|
271
|
+
first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f|
|
|
258
272
|
File.exist?(f)
|
|
259
273
|
end
|
|
260
274
|
|
|
@@ -262,9 +276,16 @@ module Puma
|
|
|
262
276
|
end
|
|
263
277
|
|
|
264
278
|
# Call once all configuration (included from rackup files)
|
|
265
|
-
# is loaded to
|
|
279
|
+
# is loaded to finalize defaults and lock in the configuration.
|
|
280
|
+
#
|
|
281
|
+
# This also calls load if it hasn't been called yet.
|
|
266
282
|
def clamp
|
|
267
|
-
@
|
|
283
|
+
load unless @loaded
|
|
284
|
+
set_conditional_default_options
|
|
285
|
+
@_options.finalize_values
|
|
286
|
+
@clamped = true
|
|
287
|
+
warn_hooks
|
|
288
|
+
options
|
|
268
289
|
end
|
|
269
290
|
|
|
270
291
|
# Injects the Configuration object into the env
|
|
@@ -283,11 +304,11 @@ module Puma
|
|
|
283
304
|
# Indicate if there is a properly configured app
|
|
284
305
|
#
|
|
285
306
|
def app_configured?
|
|
286
|
-
|
|
307
|
+
options[:app] || File.exist?(rackup)
|
|
287
308
|
end
|
|
288
309
|
|
|
289
310
|
def rackup
|
|
290
|
-
|
|
311
|
+
options[:rackup]
|
|
291
312
|
end
|
|
292
313
|
|
|
293
314
|
# Load the specified rackup file, pull options from
|
|
@@ -296,9 +317,9 @@ module Puma
|
|
|
296
317
|
def app
|
|
297
318
|
found = options[:app] || load_rackup
|
|
298
319
|
|
|
299
|
-
if
|
|
320
|
+
if options[:log_requests]
|
|
300
321
|
require_relative 'commonlogger'
|
|
301
|
-
logger =
|
|
322
|
+
logger = options[:custom_logger] ? options[:custom_logger] : options[:logger]
|
|
302
323
|
found = CommonLogger.new(found, logger)
|
|
303
324
|
end
|
|
304
325
|
|
|
@@ -307,7 +328,7 @@ module Puma
|
|
|
307
328
|
|
|
308
329
|
# Return which environment we're running in
|
|
309
330
|
def environment
|
|
310
|
-
|
|
331
|
+
options[:environment]
|
|
311
332
|
end
|
|
312
333
|
|
|
313
334
|
def load_plugin(name)
|
|
@@ -315,18 +336,19 @@ module Puma
|
|
|
315
336
|
end
|
|
316
337
|
|
|
317
338
|
# @param key [:Symbol] hook to run
|
|
318
|
-
# @param arg [Launcher, Int] `:
|
|
339
|
+
# @param arg [Launcher, Int] `:before_restart` passes Launcher
|
|
319
340
|
#
|
|
320
341
|
def run_hooks(key, arg, log_writer, hook_data = nil)
|
|
321
342
|
log_writer.debug "Running #{key} hooks"
|
|
322
343
|
|
|
323
|
-
|
|
344
|
+
options.all_of(key).each do |hook_options|
|
|
324
345
|
begin
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
346
|
+
block = hook_options[:block]
|
|
347
|
+
if id = hook_options[:id]
|
|
348
|
+
hook_data[id] ||= Hash.new
|
|
349
|
+
block.call arg, hook_data[id]
|
|
328
350
|
else
|
|
329
|
-
|
|
351
|
+
block.call arg
|
|
330
352
|
end
|
|
331
353
|
rescue => e
|
|
332
354
|
log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
|
@@ -336,7 +358,7 @@ module Puma
|
|
|
336
358
|
end
|
|
337
359
|
|
|
338
360
|
def final_options
|
|
339
|
-
|
|
361
|
+
options.final_options
|
|
340
362
|
end
|
|
341
363
|
|
|
342
364
|
def self.temp_path
|
|
@@ -346,6 +368,12 @@ module Puma
|
|
|
346
368
|
"#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
|
|
347
369
|
end
|
|
348
370
|
|
|
371
|
+
def self.random_token
|
|
372
|
+
require 'securerandom' unless defined?(SecureRandom)
|
|
373
|
+
|
|
374
|
+
SecureRandom.hex(16)
|
|
375
|
+
end
|
|
376
|
+
|
|
349
377
|
private
|
|
350
378
|
|
|
351
379
|
def require_processor_counter
|
|
@@ -386,22 +414,40 @@ module Puma
|
|
|
386
414
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
|
387
415
|
rack_options = rack_options || {}
|
|
388
416
|
|
|
389
|
-
|
|
417
|
+
options.file_options.merge!(rack_options)
|
|
390
418
|
|
|
391
419
|
config_ru_binds = []
|
|
392
420
|
rack_options.each do |k, v|
|
|
393
421
|
config_ru_binds << v if k.to_s.start_with?("bind")
|
|
394
422
|
end
|
|
395
423
|
|
|
396
|
-
|
|
424
|
+
options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
|
|
397
425
|
|
|
398
426
|
rack_app
|
|
399
427
|
end
|
|
400
428
|
|
|
401
|
-
def
|
|
402
|
-
|
|
429
|
+
def set_conditional_default_options
|
|
430
|
+
@_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
|
|
431
|
+
(@_options[:workers] > 1) && Puma.forkable?
|
|
432
|
+
end
|
|
403
433
|
|
|
404
|
-
|
|
434
|
+
def warn_hooks
|
|
435
|
+
return if options[:workers] > 0
|
|
436
|
+
return if options[:silence_fork_callback_warning]
|
|
437
|
+
|
|
438
|
+
log_writer = LogWriter.stdio
|
|
439
|
+
@hooks.each_key do |hook|
|
|
440
|
+
options.all_of(hook).each do |hook_options|
|
|
441
|
+
next unless hook_options[:cluster_only]
|
|
442
|
+
|
|
443
|
+
log_writer.log(<<~MSG.tr("\n", " "))
|
|
444
|
+
Warning: The code in the `#{hook}` block will not execute
|
|
445
|
+
in the current Puma configuration. The `#{hook}` block only
|
|
446
|
+
executes in Puma's cluster mode. To fix this, either remove the
|
|
447
|
+
`#{hook}` call or increase Puma's worker count above zero.
|
|
448
|
+
MSG
|
|
449
|
+
end
|
|
450
|
+
end
|
|
405
451
|
end
|
|
406
452
|
end
|
|
407
453
|
end
|
data/lib/puma/const.rb
CHANGED
|
@@ -100,13 +100,11 @@ 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 = "7.1.0"
|
|
104
|
+
CODE_NAME = "Neon Witch"
|
|
105
105
|
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
|
107
107
|
|
|
108
|
-
FAST_TRACK_KA_TIMEOUT = 0.2
|
|
109
|
-
|
|
110
108
|
# How long to wait when getting some write blocking on the socket when
|
|
111
109
|
# sending data back
|
|
112
110
|
WRITE_TIMEOUT = 10
|
|
@@ -125,9 +123,9 @@ module Puma
|
|
|
125
123
|
# Indicate that we couldn't parse the request
|
|
126
124
|
400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
|
|
127
125
|
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
|
128
|
-
404 => "HTTP/1.1 404 Not Found\r\
|
|
126
|
+
404 => "HTTP/1.1 404 Not Found\r\nconnection: close\r\n\r\n",
|
|
129
127
|
# The standard empty 408 response for requests that timed out.
|
|
130
|
-
408 => "HTTP/1.1 408 Request Timeout\r\
|
|
128
|
+
408 => "HTTP/1.1 408 Request Timeout\r\nconnection: close\r\n\r\n",
|
|
131
129
|
# Indicate that there was an internal error, obviously.
|
|
132
130
|
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
|
|
133
131
|
# Incorrect or invalid header value
|
|
@@ -230,6 +228,7 @@ module Puma
|
|
|
230
228
|
RACK_INPUT = "rack.input"
|
|
231
229
|
RACK_URL_SCHEME = "rack.url_scheme"
|
|
232
230
|
RACK_AFTER_REPLY = "rack.after_reply"
|
|
231
|
+
RACK_RESPONSE_FINISHED = "rack.response_finished"
|
|
233
232
|
PUMA_SOCKET = "puma.socket"
|
|
234
233
|
PUMA_CONFIG = "puma.config"
|
|
235
234
|
PUMA_PEERCERT = "puma.peercert"
|
|
@@ -252,14 +251,14 @@ module Puma
|
|
|
252
251
|
KEEP_ALIVE = "keep-alive"
|
|
253
252
|
|
|
254
253
|
CONTENT_LENGTH2 = "content-length"
|
|
255
|
-
CONTENT_LENGTH_S = "
|
|
254
|
+
CONTENT_LENGTH_S = "content-length: "
|
|
256
255
|
TRANSFER_ENCODING = "transfer-encoding"
|
|
257
256
|
TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
|
|
258
257
|
|
|
259
|
-
CONNECTION_CLOSE = "
|
|
260
|
-
CONNECTION_KEEP_ALIVE = "
|
|
258
|
+
CONNECTION_CLOSE = "connection: close\r\n"
|
|
259
|
+
CONNECTION_KEEP_ALIVE = "connection: keep-alive\r\n"
|
|
261
260
|
|
|
262
|
-
TRANSFER_ENCODING_CHUNKED = "
|
|
261
|
+
TRANSFER_ENCODING_CHUNKED = "transfer-encoding: chunked\r\n"
|
|
263
262
|
CLOSE_CHUNKED = "0\r\n\r\n"
|
|
264
263
|
|
|
265
264
|
CHUNKED = "chunked"
|
data/lib/puma/control_cli.rb
CHANGED
|
@@ -124,11 +124,15 @@ module Puma
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
if @config_file
|
|
127
|
+
# needed because neither `Puma::CLI` or `Puma::Server` are loaded
|
|
128
|
+
require_relative '../puma'
|
|
129
|
+
|
|
127
130
|
require_relative 'configuration'
|
|
128
131
|
require_relative 'log_writer'
|
|
129
132
|
|
|
130
133
|
config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env)
|
|
131
|
-
config.
|
|
134
|
+
config.clamp
|
|
135
|
+
|
|
132
136
|
@state ||= config.options[:state]
|
|
133
137
|
@control_url ||= config.options[:control_url]
|
|
134
138
|
@control_auth_token ||= config.options[:control_auth_token]
|
|
@@ -248,7 +252,7 @@ module Puma
|
|
|
248
252
|
@stdout.flush unless @stdout.sync
|
|
249
253
|
return
|
|
250
254
|
elsif sig.start_with? 'SIG'
|
|
251
|
-
if Signal.list.key? sig.
|
|
255
|
+
if Signal.list.key? sig.delete_prefix('SIG')
|
|
252
256
|
Process.kill sig, @pid
|
|
253
257
|
else
|
|
254
258
|
raise "Signal '#{sig}' not available'"
|