puma 6.6.0 → 7.0.1

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.
@@ -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.load
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
- clean_thread_locals: false,
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,17 @@ module Puma
140
144
  io_selector_backend: :auto,
141
145
  log_requests: false,
142
146
  logger: STDOUT,
143
- # How many requests to attempt inline before sending a client back to
144
- # the reactor to be subject to normal ordering. The idea here is that
145
- # we amortize the cost of going back to the reactor for a well behaved
146
- # but very "greedy" client across 10 requests. This prevents a not
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: 20,
157
+ persistent_timeout: ENV.fetch('PUMA_PERSISTENT_TIMEOUT', 65),
156
158
  queue_requests: true,
157
159
  rackup: 'config.ru'.freeze,
158
160
  raise_exception_on_sigterm: true,
@@ -176,24 +178,31 @@ module Puma
176
178
  def initialize(user_options={}, default_options = {}, env = ENV, &block)
177
179
  default_options = self.puma_default_options(env).merge(default_options)
178
180
 
179
- @options = UserFileDefaultOptions.new(user_options, default_options)
181
+ @_options = UserFileDefaultOptions.new(user_options, default_options)
180
182
  @plugins = PluginLoader.new
181
- @user_dsl = DSL.new(@options.user_options, self)
182
- @file_dsl = DSL.new(@options.file_options, self)
183
- @default_dsl = DSL.new(@options.default_options, self)
184
-
185
- if !@options[:prune_bundler]
186
- default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
187
- end
183
+ @events = @_options[:events] || Events.new
184
+ @hooks = {}
185
+ @user_dsl = DSL.new(@_options.user_options, self)
186
+ @file_dsl = DSL.new(@_options.file_options, self)
187
+ @default_dsl = DSL.new(@_options.default_options, self)
188
188
 
189
189
  @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
190
190
 
191
191
  if block
192
192
  configure(&block)
193
193
  end
194
+
195
+ @loaded = false
196
+ @clamped = false
194
197
  end
195
198
 
196
- attr_reader :options, :plugins
199
+ attr_reader :plugins, :events, :hooks
200
+
201
+ def options
202
+ raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
203
+
204
+ @_options
205
+ end
197
206
 
198
207
  def configure
199
208
  yield @user_dsl, @file_dsl, @default_dsl
@@ -206,7 +215,7 @@ module Puma
206
215
  def initialize_copy(other)
207
216
  @conf = nil
208
217
  @cli_options = nil
209
- @options = @options.dup
218
+ @_options = @_options.dup
210
219
  end
211
220
 
212
221
  def flatten
@@ -214,7 +223,7 @@ module Puma
214
223
  end
215
224
 
216
225
  def flatten!
217
- @options = @options.flatten
226
+ @_options = @_options.flatten
218
227
  self
219
228
  end
220
229
 
@@ -243,18 +252,20 @@ module Puma
243
252
  end
244
253
 
245
254
  def load
255
+ @loaded = true
246
256
  config_files.each { |config_file| @file_dsl._load_from(config_file) }
247
-
248
- @options
257
+ @_options
249
258
  end
250
259
 
251
260
  def config_files
252
- files = @options.all_of(:config_files)
261
+ raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded
262
+
263
+ files = @_options.all_of(:config_files)
253
264
 
254
265
  return [] if files == ['-']
255
266
  return files if files.any?
256
267
 
257
- first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
268
+ first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f|
258
269
  File.exist?(f)
259
270
  end
260
271
 
@@ -262,9 +273,16 @@ module Puma
262
273
  end
263
274
 
264
275
  # Call once all configuration (included from rackup files)
265
- # is loaded to flesh out any defaults
276
+ # is loaded to finalize defaults and lock in the configuration.
277
+ #
278
+ # This also calls load if it hasn't been called yet.
266
279
  def clamp
267
- @options.finalize_values
280
+ load unless @loaded
281
+ set_conditional_default_options
282
+ @_options.finalize_values
283
+ @clamped = true
284
+ warn_hooks
285
+ options
268
286
  end
269
287
 
270
288
  # Injects the Configuration object into the env
@@ -283,11 +301,11 @@ module Puma
283
301
  # Indicate if there is a properly configured app
284
302
  #
285
303
  def app_configured?
286
- @options[:app] || File.exist?(rackup)
304
+ options[:app] || File.exist?(rackup)
287
305
  end
288
306
 
289
307
  def rackup
290
- @options[:rackup]
308
+ options[:rackup]
291
309
  end
292
310
 
293
311
  # Load the specified rackup file, pull options from
@@ -296,9 +314,9 @@ module Puma
296
314
  def app
297
315
  found = options[:app] || load_rackup
298
316
 
299
- if @options[:log_requests]
317
+ if options[:log_requests]
300
318
  require_relative 'commonlogger'
301
- logger = @options[:logger]
319
+ logger = options[:custom_logger] ? options[:custom_logger] : options[:logger]
302
320
  found = CommonLogger.new(found, logger)
303
321
  end
304
322
 
@@ -307,7 +325,7 @@ module Puma
307
325
 
308
326
  # Return which environment we're running in
309
327
  def environment
310
- @options[:environment]
328
+ options[:environment]
311
329
  end
312
330
 
313
331
  def load_plugin(name)
@@ -315,18 +333,19 @@ module Puma
315
333
  end
316
334
 
317
335
  # @param key [:Symbol] hook to run
318
- # @param arg [Launcher, Int] `:on_restart` passes Launcher
336
+ # @param arg [Launcher, Int] `:before_restart` passes Launcher
319
337
  #
320
338
  def run_hooks(key, arg, log_writer, hook_data = nil)
321
339
  log_writer.debug "Running #{key} hooks"
322
340
 
323
- @options.all_of(key).each do |b|
341
+ options.all_of(key).each do |hook_options|
324
342
  begin
325
- if Array === b
326
- hook_data[b[1]] ||= Hash.new
327
- b[0].call arg, hook_data[b[1]]
343
+ block = hook_options[:block]
344
+ if id = hook_options[:id]
345
+ hook_data[id] ||= Hash.new
346
+ block.call arg, hook_data[id]
328
347
  else
329
- b.call arg
348
+ block.call arg
330
349
  end
331
350
  rescue => e
332
351
  log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
@@ -336,7 +355,7 @@ module Puma
336
355
  end
337
356
 
338
357
  def final_options
339
- @options.final_options
358
+ options.final_options
340
359
  end
341
360
 
342
361
  def self.temp_path
@@ -346,6 +365,12 @@ module Puma
346
365
  "#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
347
366
  end
348
367
 
368
+ def self.random_token
369
+ require 'securerandom' unless defined?(SecureRandom)
370
+
371
+ SecureRandom.hex(16)
372
+ end
373
+
349
374
  private
350
375
 
351
376
  def require_processor_counter
@@ -386,22 +411,40 @@ module Puma
386
411
  rack_app, rack_options = rack_builder.parse_file(rackup)
387
412
  rack_options = rack_options || {}
388
413
 
389
- @options.file_options.merge!(rack_options)
414
+ options.file_options.merge!(rack_options)
390
415
 
391
416
  config_ru_binds = []
392
417
  rack_options.each do |k, v|
393
418
  config_ru_binds << v if k.to_s.start_with?("bind")
394
419
  end
395
420
 
396
- @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
421
+ options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
397
422
 
398
423
  rack_app
399
424
  end
400
425
 
401
- def self.random_token
402
- require 'securerandom' unless defined?(SecureRandom)
426
+ def set_conditional_default_options
427
+ @_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
428
+ (@_options[:workers] > 1) && Puma.forkable?
429
+ end
403
430
 
404
- SecureRandom.hex(16)
431
+ def warn_hooks
432
+ return if options[:workers] > 0
433
+ return if options[:silence_fork_callback_warning]
434
+
435
+ log_writer = LogWriter.stdio
436
+ @hooks.each_key do |hook|
437
+ options.all_of(hook).each do |hook_options|
438
+ next unless hook_options[:cluster_only]
439
+
440
+ log_writer.log(<<~MSG.tr("\n", " "))
441
+ Warning: The code in the `#{hook}` block will not execute
442
+ in the current Puma configuration. The `#{hook}` block only
443
+ executes in Puma's cluster mode. To fix this, either remove the
444
+ `#{hook}` call or increase Puma's worker count above zero.
445
+ MSG
446
+ end
447
+ end
405
448
  end
406
449
  end
407
450
  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 = "6.6.0"
104
- CODE_NAME = "Return to Forever"
103
+ PUMA_VERSION = VERSION = "7.0.1"
104
+ CODE_NAME = "Romantic Warrior"
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\nConnection: close\r\n\r\n",
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\nConnection: close\r\n\r\n",
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 = "Content-Length: "
254
+ CONTENT_LENGTH_S = "content-length: "
256
255
  TRANSFER_ENCODING = "transfer-encoding"
257
256
  TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
258
257
 
259
- CONNECTION_CLOSE = "Connection: close\r\n"
260
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
258
+ CONNECTION_CLOSE = "connection: close\r\n"
259
+ CONNECTION_KEEP_ALIVE = "connection: keep-alive\r\n"
261
260
 
262
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
261
+ TRANSFER_ENCODING_CHUNKED = "transfer-encoding: chunked\r\n"
263
262
  CLOSE_CHUNKED = "0\r\n\r\n"
264
263
 
265
264
  CHUNKED = "chunked"
@@ -128,7 +128,8 @@ module Puma
128
128
  require_relative 'log_writer'
129
129
 
130
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env)
131
- config.load
131
+ config.clamp
132
+
132
133
  @state ||= config.options[:state]
133
134
  @control_url ||= config.options[:control_url]
134
135
  @control_auth_token ||= config.options[:control_auth_token]
@@ -248,7 +249,7 @@ module Puma
248
249
  @stdout.flush unless @stdout.sync
249
250
  return
250
251
  elsif sig.start_with? 'SIG'
251
- if Signal.list.key? sig.sub(/\ASIG/, '')
252
+ if Signal.list.key? sig.delete_prefix('SIG')
252
253
  Process.kill sig, @pid
253
254
  else
254
255
  raise "Signal '#{sig}' not available'"
data/lib/puma/detect.rb CHANGED
@@ -18,6 +18,8 @@ module Puma
18
18
 
19
19
  IS_LINUX = !(IS_OSX || IS_WINDOWS)
20
20
 
21
+ IS_ARM = RUBY_PLATFORM.include? 'aarch64'
22
+
21
23
  # @version 5.2.0
22
24
  IS_MRI = RUBY_ENGINE == 'ruby'
23
25