puma 6.6.1 → 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 +224 -4
- data/README.md +34 -34
- data/docs/deployment.md +58 -23
- data/docs/fork_worker.md +5 -5
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +2 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +19 -19
- data/docs/stats.md +4 -3
- data/docs/systemd.md +3 -3
- 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 +122 -118
- data/lib/puma/app/status.rb +10 -2
- 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 +17 -17
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +23 -23
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +104 -51
- 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 +149 -91
- 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 +54 -49
- 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 -13
- data/lib/puma/request.rb +42 -31
- data/lib/puma/runner.rb +9 -18
- data/lib/puma/server.rb +114 -64
- data/lib/puma/single.rb +6 -3
- 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
- data/tools/Dockerfile +13 -5
- metadata +6 -5
- data/ext/puma_http11/ext_help.h +0 -15
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, :_options
|
|
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,34 +237,34 @@ 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']
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
else
|
|
234
|
-
env['WEB_CONCURRENCY']
|
|
235
|
-
end
|
|
240
|
+
persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
|
|
241
|
+
workers_env = env['WEB_CONCURRENCY']
|
|
242
|
+
workers = workers_env && workers_env.strip != "" ? parse_workers(workers_env.strip) : nil
|
|
236
243
|
|
|
237
244
|
{
|
|
238
245
|
min_threads: min && min != "" && Integer(min),
|
|
239
246
|
max_threads: max && max != "" && Integer(max),
|
|
240
|
-
|
|
247
|
+
persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
|
|
248
|
+
workers: workers,
|
|
241
249
|
environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
|
|
242
250
|
}
|
|
243
251
|
end
|
|
244
252
|
|
|
245
253
|
def load
|
|
254
|
+
@loaded = true
|
|
246
255
|
config_files.each { |config_file| @file_dsl._load_from(config_file) }
|
|
247
|
-
|
|
248
|
-
@options
|
|
256
|
+
@_options
|
|
249
257
|
end
|
|
250
258
|
|
|
251
259
|
def config_files
|
|
252
|
-
|
|
260
|
+
raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded
|
|
261
|
+
|
|
262
|
+
files = @_options.all_of(:config_files)
|
|
253
263
|
|
|
254
264
|
return [] if files == ['-']
|
|
255
265
|
return files if files.any?
|
|
256
266
|
|
|
257
|
-
first_default_file = %W(config/puma/#{@
|
|
267
|
+
first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f|
|
|
258
268
|
File.exist?(f)
|
|
259
269
|
end
|
|
260
270
|
|
|
@@ -262,9 +272,16 @@ module Puma
|
|
|
262
272
|
end
|
|
263
273
|
|
|
264
274
|
# Call once all configuration (included from rackup files)
|
|
265
|
-
# is loaded to
|
|
275
|
+
# is loaded to finalize defaults and lock in the configuration.
|
|
276
|
+
#
|
|
277
|
+
# This also calls load if it hasn't been called yet.
|
|
266
278
|
def clamp
|
|
267
|
-
@
|
|
279
|
+
load unless @loaded
|
|
280
|
+
set_conditional_default_options
|
|
281
|
+
@_options.finalize_values
|
|
282
|
+
@clamped = true
|
|
283
|
+
warn_hooks
|
|
284
|
+
options
|
|
268
285
|
end
|
|
269
286
|
|
|
270
287
|
# Injects the Configuration object into the env
|
|
@@ -283,11 +300,11 @@ module Puma
|
|
|
283
300
|
# Indicate if there is a properly configured app
|
|
284
301
|
#
|
|
285
302
|
def app_configured?
|
|
286
|
-
|
|
303
|
+
options[:app] || File.exist?(rackup)
|
|
287
304
|
end
|
|
288
305
|
|
|
289
306
|
def rackup
|
|
290
|
-
|
|
307
|
+
options[:rackup]
|
|
291
308
|
end
|
|
292
309
|
|
|
293
310
|
# Load the specified rackup file, pull options from
|
|
@@ -296,9 +313,9 @@ module Puma
|
|
|
296
313
|
def app
|
|
297
314
|
found = options[:app] || load_rackup
|
|
298
315
|
|
|
299
|
-
if
|
|
316
|
+
if options[:log_requests]
|
|
300
317
|
require_relative 'commonlogger'
|
|
301
|
-
logger =
|
|
318
|
+
logger = options[:custom_logger] ? options[:custom_logger] : options[:logger]
|
|
302
319
|
found = CommonLogger.new(found, logger)
|
|
303
320
|
end
|
|
304
321
|
|
|
@@ -307,7 +324,7 @@ module Puma
|
|
|
307
324
|
|
|
308
325
|
# Return which environment we're running in
|
|
309
326
|
def environment
|
|
310
|
-
|
|
327
|
+
options[:environment]
|
|
311
328
|
end
|
|
312
329
|
|
|
313
330
|
def load_plugin(name)
|
|
@@ -315,18 +332,19 @@ module Puma
|
|
|
315
332
|
end
|
|
316
333
|
|
|
317
334
|
# @param key [:Symbol] hook to run
|
|
318
|
-
# @param arg [Launcher, Int] `:
|
|
335
|
+
# @param arg [Launcher, Int] `:before_restart` passes Launcher
|
|
319
336
|
#
|
|
320
337
|
def run_hooks(key, arg, log_writer, hook_data = nil)
|
|
321
338
|
log_writer.debug "Running #{key} hooks"
|
|
322
339
|
|
|
323
|
-
|
|
340
|
+
options.all_of(key).each do |hook_options|
|
|
324
341
|
begin
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
342
|
+
block = hook_options[:block]
|
|
343
|
+
if id = hook_options[:id]
|
|
344
|
+
hook_data[id] ||= Hash.new
|
|
345
|
+
block.call arg, hook_data[id]
|
|
328
346
|
else
|
|
329
|
-
|
|
347
|
+
block.call arg
|
|
330
348
|
end
|
|
331
349
|
rescue => e
|
|
332
350
|
log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
|
@@ -336,7 +354,7 @@ module Puma
|
|
|
336
354
|
end
|
|
337
355
|
|
|
338
356
|
def final_options
|
|
339
|
-
|
|
357
|
+
options.final_options
|
|
340
358
|
end
|
|
341
359
|
|
|
342
360
|
def self.temp_path
|
|
@@ -346,18 +364,35 @@ module Puma
|
|
|
346
364
|
"#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
|
|
347
365
|
end
|
|
348
366
|
|
|
367
|
+
def self.random_token
|
|
368
|
+
require 'securerandom' unless defined?(SecureRandom)
|
|
369
|
+
|
|
370
|
+
SecureRandom.hex(16)
|
|
371
|
+
end
|
|
372
|
+
|
|
349
373
|
private
|
|
350
374
|
|
|
351
375
|
def require_processor_counter
|
|
352
376
|
require 'concurrent/utility/processor_counter'
|
|
353
377
|
rescue LoadError
|
|
354
378
|
warn <<~MESSAGE
|
|
355
|
-
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.
|
|
356
380
|
Please add "concurrent-ruby" to your Gemfile.
|
|
357
381
|
MESSAGE
|
|
358
382
|
raise
|
|
359
383
|
end
|
|
360
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
|
+
|
|
361
396
|
# Load and use the normal Rack builder if we can, otherwise
|
|
362
397
|
# fallback to our minimal version.
|
|
363
398
|
def rack_builder
|
|
@@ -386,22 +421,40 @@ module Puma
|
|
|
386
421
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
|
387
422
|
rack_options = rack_options || {}
|
|
388
423
|
|
|
389
|
-
|
|
424
|
+
options.file_options.merge!(rack_options)
|
|
390
425
|
|
|
391
426
|
config_ru_binds = []
|
|
392
427
|
rack_options.each do |k, v|
|
|
393
428
|
config_ru_binds << v if k.to_s.start_with?("bind")
|
|
394
429
|
end
|
|
395
430
|
|
|
396
|
-
|
|
431
|
+
options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
|
|
397
432
|
|
|
398
433
|
rack_app
|
|
399
434
|
end
|
|
400
435
|
|
|
401
|
-
def
|
|
402
|
-
|
|
436
|
+
def set_conditional_default_options
|
|
437
|
+
@_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
|
|
438
|
+
(@_options[:workers] > 1) && Puma.forkable?
|
|
439
|
+
end
|
|
403
440
|
|
|
404
|
-
|
|
441
|
+
def warn_hooks
|
|
442
|
+
return if options[:workers] > 0
|
|
443
|
+
return if options[:silence_fork_callback_warning]
|
|
444
|
+
|
|
445
|
+
log_writer = LogWriter.stdio
|
|
446
|
+
@hooks.each_key do |hook|
|
|
447
|
+
options.all_of(hook).each do |hook_options|
|
|
448
|
+
next unless hook_options[:cluster_only]
|
|
449
|
+
|
|
450
|
+
log_writer.log(<<~MSG.tr("\n", " "))
|
|
451
|
+
Warning: The code in the `#{hook}` block will not execute
|
|
452
|
+
in the current Puma configuration. The `#{hook}` block only
|
|
453
|
+
executes in Puma's cluster mode. To fix this, either remove the
|
|
454
|
+
`#{hook}` call or increase Puma's worker count above zero.
|
|
455
|
+
MSG
|
|
456
|
+
end
|
|
457
|
+
end
|
|
405
458
|
end
|
|
406
459
|
end
|
|
407
460
|
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.2.0"
|
|
104
|
+
CODE_NAME = "On The Corner"
|
|
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'"
|