puma 6.4.3 → 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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +448 -8
  3. data/README.md +110 -51
  4. data/docs/5.0-Upgrade.md +98 -0
  5. data/docs/6.0-Upgrade.md +56 -0
  6. data/docs/7.0-Upgrade.md +52 -0
  7. data/docs/8.0-Upgrade.md +100 -0
  8. data/docs/deployment.md +58 -23
  9. data/docs/fork_worker.md +11 -1
  10. data/docs/grpc.md +62 -0
  11. data/docs/images/favicon.svg +1 -0
  12. data/docs/images/running-puma.svg +1 -0
  13. data/docs/images/standard-logo.svg +1 -0
  14. data/docs/java_options.md +54 -0
  15. data/docs/jungle/README.md +1 -1
  16. data/docs/kubernetes.md +11 -16
  17. data/docs/plugins.md +6 -2
  18. data/docs/restart.md +2 -2
  19. data/docs/signals.md +21 -21
  20. data/docs/stats.md +11 -5
  21. data/docs/systemd.md +14 -5
  22. data/ext/puma_http11/extconf.rb +20 -32
  23. data/ext/puma_http11/http11_parser.java.rl +51 -65
  24. data/ext/puma_http11/mini_ssl.c +29 -9
  25. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +194 -101
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
  28. data/ext/puma_http11/puma_http11.c +125 -118
  29. data/lib/puma/app/status.rb +11 -3
  30. data/lib/puma/binder.rb +22 -12
  31. data/lib/puma/cli.rb +11 -9
  32. data/lib/puma/client.rb +233 -136
  33. data/lib/puma/client_env.rb +171 -0
  34. data/lib/puma/cluster/worker.rb +24 -21
  35. data/lib/puma/cluster/worker_handle.rb +38 -8
  36. data/lib/puma/cluster.rb +74 -48
  37. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  38. data/lib/puma/commonlogger.rb +3 -3
  39. data/lib/puma/configuration.rb +197 -64
  40. data/lib/puma/const.rb +23 -12
  41. data/lib/puma/control_cli.rb +11 -7
  42. data/lib/puma/detect.rb +13 -0
  43. data/lib/puma/dsl.rb +483 -127
  44. data/lib/puma/error_logger.rb +7 -5
  45. data/lib/puma/events.rb +25 -10
  46. data/lib/puma/io_buffer.rb +8 -4
  47. data/lib/puma/jruby_restart.rb +0 -16
  48. data/lib/puma/launcher/bundle_pruner.rb +3 -5
  49. data/lib/puma/launcher.rb +76 -59
  50. data/lib/puma/log_writer.rb +17 -11
  51. data/lib/puma/minissl/context_builder.rb +1 -0
  52. data/lib/puma/minissl.rb +1 -1
  53. data/lib/puma/null_io.rb +26 -0
  54. data/lib/puma/plugin/systemd.rb +3 -3
  55. data/lib/puma/rack/urlmap.rb +1 -1
  56. data/lib/puma/reactor.rb +19 -13
  57. data/lib/puma/{request.rb → response.rb} +57 -209
  58. data/lib/puma/runner.rb +15 -17
  59. data/lib/puma/sd_notify.rb +1 -4
  60. data/lib/puma/server.rb +200 -104
  61. data/lib/puma/server_plugin_control.rb +32 -0
  62. data/lib/puma/single.rb +7 -4
  63. data/lib/puma/state_file.rb +3 -2
  64. data/lib/puma/thread_pool.rb +179 -96
  65. data/lib/puma/util.rb +0 -7
  66. data/lib/puma.rb +10 -0
  67. data/lib/rack/handler/puma.rb +11 -8
  68. data/tools/Dockerfile +15 -5
  69. metadata +26 -16
  70. data/ext/puma_http11/ext_help.h +0 -15
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ # Calculate a delay value for sleeping when running in clustered mode
5
+ #
6
+ # The main reason this is a class is so it can be unit tested independently.
7
+ # This makes modification easier in the future if we can encode properties of the
8
+ # delay into a test instead of relying on end-to-end testing only.
9
+ #
10
+ # This is an imprecise mechanism to address specific goals:
11
+ #
12
+ # - Evenly distribute requests across all workers at start
13
+ # - Evenly distribute CPU resources across all workers
14
+ #
15
+ # ## Goal: Distribute requests across workers at start
16
+ #
17
+ # There was a perf bug in Puma where one worker would wake up slightly before the rest and accept
18
+ # all the requests on the socket even though it didn't have enough resources to process all of them.
19
+ # This was originally fixed by never calling accept when a worker had more requests than threads
20
+ # already https://github.com/puma/puma/pull/3678/files/2736ebddb3fc8528e5150b5913fba251c37a8bf7#diff-a95f46e7ce116caddc9b9a9aa81004246d5210d5da5f4df90a818c780630166bL251-L291
21
+ #
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"-ed
24
+ # - A keepalive request is served and the connection is retained. Another request is then accepted
25
+ #
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
+ # These goals are contradictory, because when the server is at maximum capacity due to keepalive connections, it could mean we
28
+ # block all new requests, even if those came in before the new request on the older keepalive connection.
29
+ #
30
+ # ## Goal: Distribute CPU resources across all workers
31
+ #
32
+ # - This issue was opened https://github.com/puma/puma/issues/2078
33
+ #
34
+ # There are several entangled issues and it's not exactly clear what the root cause is, but the observable outcome
35
+ # was that performance was better with a small sleep, and that eventually became the default.
36
+ #
37
+ # An attempt to describe why this works is here: https://github.com/puma/puma/issues/2078#issuecomment-3287032470.
38
+ #
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), they will "race" to accept a request at roughly the same rate. However, if one
42
+ # worker has all threads busy processing requests, then accepting a new request might "steal" it from
43
+ # a less busy worker. If a worker has no work to do, it should loop as fast as possible.
44
+ #
45
+ # ## Solution: Distribute requests across workers at start
46
+ #
47
+ # For now, both goals are framed as "load balancing" across workers (processes) and achieved through
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 amount proportional
50
+ # to the load the server is under, capping the maximum delay to the scenario where all threads are busy
51
+ # and the todo list has reached a multiplier of the maximum number of threads.
52
+ #
53
+ # Private: API may change unexpectedly
54
+ class ClusterAcceptLoopDelay
55
+ attr_reader :max_delay
56
+
57
+ # Initialize happens once, `call` happens often. Perform global calculations here.
58
+ def initialize(
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
+ )
64
+ @on = max_delay > 0 && workers >= 2
65
+ @max_delay = max_delay.to_f
66
+
67
+ # Reach maximum delay when `max_threads * overload_multiplier` is reached in the system
68
+ @overload_multiplier = 25.0
69
+ end
70
+
71
+ def on?
72
+ @on
73
+ end
74
+
75
+ # We want the extreme values of this delay to be known (minimum and maximum) as well as
76
+ # a predictable curve between the two. i.e. no step functions or hard cliffs.
77
+ #
78
+ # Return value is always numeric. Returns 0 if there should be no delay.
79
+ def calculate(
80
+ # Number of threads working right now, plus number of requests in the todo list
81
+ busy_threads_plus_todo:,
82
+ # Maximum number of threads in the pool, note that the busy threads (alone) may go over this value at times
83
+ # if the pool needs to be reaped. The busy thread plus todo count may go over this value by a large amount.
84
+ max_threads:
85
+ )
86
+ max_value = @overload_multiplier * max_threads
87
+ # Approaches max delay when `busy_threads_plus_todo` approaches `max_value`
88
+ return max_delay * busy_threads_plus_todo.clamp(0, max_value) / max_value
89
+ end
90
+ end
91
+ end
@@ -29,13 +29,13 @@ module Puma
29
29
 
30
30
  CONTENT_LENGTH = 'Content-Length' # should be lower case from app,
31
31
  # Util::HeaderHash allows mixed
32
- HTTP_VERSION = Const::HTTP_VERSION
33
32
  HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
34
33
  PATH_INFO = Const::PATH_INFO
35
34
  QUERY_STRING = Const::QUERY_STRING
36
35
  REMOTE_ADDR = Const::REMOTE_ADDR
37
36
  REMOTE_USER = 'REMOTE_USER'
38
37
  REQUEST_METHOD = Const::REQUEST_METHOD
38
+ SERVER_PROTOCOL = Const::SERVER_PROTOCOL
39
39
 
40
40
  def initialize(app, logger=nil)
41
41
  @app = app
@@ -70,7 +70,7 @@ module Puma
70
70
  env[REQUEST_METHOD],
71
71
  env[PATH_INFO],
72
72
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
73
- env[HTTP_VERSION],
73
+ env[SERVER_PROTOCOL],
74
74
  now - began_at ]
75
75
 
76
76
  write(msg)
@@ -87,7 +87,7 @@ module Puma
87
87
  env[REQUEST_METHOD],
88
88
  env[PATH_INFO],
89
89
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
90
- env[HTTP_VERSION],
90
+ env[SERVER_PROTOCOL],
91
91
  status.to_s[0..3],
92
92
  length,
93
93
  now - began_at ]
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rack/builder'
3
+ require 'socket'
4
+ require 'uri'
5
+
4
6
  require_relative 'plugin'
5
7
  require_relative 'const'
6
8
  require_relative 'dsl'
9
+ require_relative 'events'
7
10
 
8
11
  module Puma
9
12
  # A class used for storing "leveled" configuration options.
@@ -113,7 +116,7 @@ module Puma
113
116
  # config = Configuration.new({}) do |user_config, file_config, default_config|
114
117
  # user_config.port 3003
115
118
  # end
116
- # config.load
119
+ # config.clamp
117
120
  # puts config.options[:port]
118
121
  # # => 3003
119
122
  #
@@ -126,42 +129,48 @@ module Puma
126
129
  # is done because an environment variable may have been modified while loading
127
130
  # configuration files.
128
131
  class Configuration
132
+ class NotLoadedError < StandardError; end
133
+ class NotClampedError < StandardError; end
134
+
129
135
  DEFAULTS = {
130
136
  auto_trim_time: 30,
131
- binds: ['tcp://0.0.0.0:9292'.freeze],
132
- clean_thread_locals: false,
137
+ binds: ['tcp://[::]:9292'.freeze],
133
138
  debug: false,
134
139
  early_hints: nil,
140
+ enable_keep_alives: true,
135
141
  environment: 'development'.freeze,
142
+ fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
136
143
  # Number of seconds to wait until we get the first data for the request.
137
144
  first_data_timeout: 30,
145
+ force_shutdown_after: -1,
146
+ http_content_length_limit: nil,
138
147
  # Number of seconds to wait until the next request before shutting down.
139
148
  idle_timeout: nil,
140
149
  io_selector_backend: :auto,
141
150
  log_requests: false,
142
151
  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,
152
+ # Limits how many requests a keep alive connection can make.
153
+ # The connection will be closed after it reaches `max_keep_alive`
154
+ # requests.
155
+ max_io_threads: 0,
156
+ max_keep_alive: 999,
149
157
  max_threads: Puma.mri? ? 5 : 16,
150
158
  min_threads: 0,
151
159
  mode: :http,
152
160
  mutate_stdout_and_stderr_to_sync_on_write: true,
153
161
  out_of_band: [],
154
162
  # Number of seconds for another request within a persistent session.
155
- persistent_timeout: 20,
163
+ persistent_timeout: 65, # PUMA_PERSISTENT_TIMEOUT
164
+ prune_bundler: false,
156
165
  queue_requests: true,
157
166
  rackup: 'config.ru'.freeze,
158
167
  raise_exception_on_sigterm: true,
159
168
  reaping_time: 1,
160
169
  remote_address: :socket,
161
- silence_single_worker_warning: false,
162
170
  silence_fork_callback_warning: false,
171
+ silence_single_worker_warning: false,
163
172
  tag: File.basename(Dir.getwd),
164
- tcp_host: '0.0.0.0'.freeze,
173
+ tcp_host: '::'.freeze,
165
174
  tcp_port: 9292,
166
175
  wait_for_less_busy_worker: 0.005,
167
176
  worker_boot_timeout: 60,
@@ -170,28 +179,36 @@ module Puma
170
179
  worker_shutdown_timeout: 30,
171
180
  worker_timeout: 60,
172
181
  workers: 0,
173
- http_content_length_limit: nil
174
182
  }
175
183
 
176
- def initialize(user_options={}, default_options = {}, &block)
177
- default_options = self.puma_default_options.merge(default_options)
184
+ def initialize(user_options={}, default_options = {}, env = ENV, &block)
185
+ default_options = self.puma_default_options(env).merge(events: Events.new).merge(default_options)
178
186
 
179
- @options = UserFileDefaultOptions.new(user_options, default_options)
187
+ @_options = UserFileDefaultOptions.new(user_options, default_options)
180
188
  @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)
189
+ @events = @_options[:events] || Events.new
190
+ @hooks = {}
191
+ @user_dsl = DSL.new(@_options.user_options, self)
192
+ @file_dsl = DSL.new(@_options.file_options, self)
193
+ @default_dsl = DSL.new(@_options.default_options, self)
184
194
 
185
- if !@options[:prune_bundler]
186
- default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
187
- end
195
+ @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
188
196
 
189
197
  if block
190
198
  configure(&block)
191
199
  end
200
+
201
+ @loaded = false
202
+ @clamped = false
192
203
  end
193
204
 
194
- attr_reader :options, :plugins
205
+ attr_reader :plugins, :events, :hooks, :_options
206
+
207
+ def options
208
+ raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
209
+
210
+ @_options
211
+ end
195
212
 
196
213
  def configure
197
214
  yield @user_dsl, @file_dsl, @default_dsl
@@ -204,7 +221,7 @@ module Puma
204
221
  def initialize_copy(other)
205
222
  @conf = nil
206
223
  @cli_options = nil
207
- @options = @options.dup
224
+ @_options = @_options.dup
208
225
  end
209
226
 
210
227
  def flatten
@@ -212,42 +229,49 @@ module Puma
212
229
  end
213
230
 
214
231
  def flatten!
215
- @options = @options.flatten
232
+ @_options = @_options.flatten
216
233
  self
217
234
  end
218
235
 
219
- def puma_default_options
236
+ def puma_default_options(env = ENV)
220
237
  defaults = DEFAULTS.dup
221
- puma_options_from_env.each { |k,v| defaults[k] = v if v }
238
+ defaults[:tcp_host] = self.class.default_tcp_host
239
+ defaults[:binds] = [self.class.default_tcp_bind]
240
+ puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
222
241
  defaults
223
242
  end
224
243
 
225
- def puma_options_from_env
226
- min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
227
- max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
228
- workers = ENV['WEB_CONCURRENCY']
244
+ def puma_options_from_env(env = ENV)
245
+ min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
246
+ max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
247
+ persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
248
+ workers_env = env['WEB_CONCURRENCY']
249
+ workers = workers_env && workers_env.strip != "" ? parse_workers(workers_env.strip) : nil
229
250
 
230
251
  {
231
- min_threads: min && Integer(min),
232
- max_threads: max && Integer(max),
233
- workers: workers && Integer(workers),
234
- environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
252
+ min_threads: min && min != "" && Integer(min),
253
+ max_threads: max && max != "" && Integer(max),
254
+ persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
255
+ workers: workers,
256
+ environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
235
257
  }
236
258
  end
237
259
 
238
260
  def load
261
+ @loaded = true
239
262
  config_files.each { |config_file| @file_dsl._load_from(config_file) }
240
-
241
- @options
263
+ @_options
242
264
  end
243
265
 
244
266
  def config_files
245
- files = @options.all_of(:config_files)
267
+ raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded
268
+
269
+ files = @_options.all_of(:config_files)
246
270
 
247
271
  return [] if files == ['-']
248
272
  return files if files.any?
249
273
 
250
- first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
274
+ first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f|
251
275
  File.exist?(f)
252
276
  end
253
277
 
@@ -255,9 +279,18 @@ module Puma
255
279
  end
256
280
 
257
281
  # Call once all configuration (included from rackup files)
258
- # is loaded to flesh out any defaults
282
+ # is loaded to finalize defaults and lock in the configuration.
283
+ #
284
+ # This also calls load if it hasn't been called yet.
259
285
  def clamp
260
- @options.finalize_values
286
+ load unless @loaded
287
+ run_mode_hooks
288
+ set_conditional_default_options
289
+ @_options.finalize_values
290
+ rewrite_unavailable_ipv6_binds!
291
+ @clamped = true
292
+ warn_hooks
293
+ options
261
294
  end
262
295
 
263
296
  # Injects the Configuration object into the env
@@ -276,11 +309,11 @@ module Puma
276
309
  # Indicate if there is a properly configured app
277
310
  #
278
311
  def app_configured?
279
- @options[:app] || File.exist?(rackup)
312
+ options[:app] || File.exist?(rackup)
280
313
  end
281
314
 
282
315
  def rackup
283
- @options[:rackup]
316
+ options[:rackup]
284
317
  end
285
318
 
286
319
  # Load the specified rackup file, pull options from
@@ -289,9 +322,9 @@ module Puma
289
322
  def app
290
323
  found = options[:app] || load_rackup
291
324
 
292
- if @options[:log_requests]
325
+ if options[:log_requests]
293
326
  require_relative 'commonlogger'
294
- logger = @options[:logger]
327
+ logger = options[:custom_logger] ? options[:custom_logger] : options[:logger]
295
328
  found = CommonLogger.new(found, logger)
296
329
  end
297
330
 
@@ -300,7 +333,7 @@ module Puma
300
333
 
301
334
  # Return which environment we're running in
302
335
  def environment
303
- @options[:environment]
336
+ options[:environment]
304
337
  end
305
338
 
306
339
  def load_plugin(name)
@@ -308,16 +341,19 @@ module Puma
308
341
  end
309
342
 
310
343
  # @param key [:Symbol] hook to run
311
- # @param arg [Launcher, Int] `:on_restart` passes Launcher
344
+ # @param arg [Launcher, Int] `:before_restart` passes Launcher
312
345
  #
313
346
  def run_hooks(key, arg, log_writer, hook_data = nil)
314
- @options.all_of(key).each do |b|
347
+ log_writer.debug { "Running #{key} hooks" }
348
+
349
+ options.all_of(key).each do |hook_options|
315
350
  begin
316
- if Array === b
317
- hook_data[b[1]] ||= Hash.new
318
- b[0].call arg, hook_data[b[1]]
351
+ block = hook_options[:block]
352
+ if id = hook_options[:id]
353
+ hook_data[id] ||= Hash.new
354
+ block.call arg, hook_data[id]
319
355
  else
320
- b.call arg
356
+ block.call arg
321
357
  end
322
358
  rescue => e
323
359
  log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
@@ -327,7 +363,24 @@ module Puma
327
363
  end
328
364
 
329
365
  def final_options
330
- @options.final_options
366
+ options.final_options
367
+ end
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
331
384
  end
332
385
 
333
386
  def self.temp_path
@@ -337,14 +390,66 @@ module Puma
337
390
  "#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
338
391
  end
339
392
 
393
+ def self.random_token
394
+ require 'securerandom' unless defined?(SecureRandom)
395
+
396
+ SecureRandom.hex(16)
397
+ end
398
+
340
399
  private
341
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
+
426
+ def require_processor_counter
427
+ require 'concurrent/utility/processor_counter'
428
+ rescue LoadError
429
+ warn <<~MESSAGE
430
+ WEB_CONCURRENCY=auto or workers(:auto) requires the "concurrent-ruby" gem to be installed.
431
+ Please add "concurrent-ruby" to your Gemfile.
432
+ MESSAGE
433
+ raise
434
+ end
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
+
342
447
  # Load and use the normal Rack builder if we can, otherwise
343
448
  # fallback to our minimal version.
344
449
  def rack_builder
345
450
  # Load bundler now if we can so that we can pickup rack from
346
451
  # a Gemfile
347
- if ENV.key? 'PUMA_BUNDLER_PRUNED'
452
+ if @puma_bundler_pruned
348
453
  begin
349
454
  require 'bundler/setup'
350
455
  rescue LoadError
@@ -354,11 +459,10 @@ module Puma
354
459
  begin
355
460
  require 'rack'
356
461
  require 'rack/builder'
462
+ ::Rack::Builder
357
463
  rescue LoadError
358
- # ok, use builtin version
359
- return Puma::Rack::Builder
360
- else
361
- return ::Rack::Builder
464
+ require_relative 'rack/builder'
465
+ Puma::Rack::Builder
362
466
  end
363
467
  end
364
468
 
@@ -368,22 +472,51 @@ module Puma
368
472
  rack_app, rack_options = rack_builder.parse_file(rackup)
369
473
  rack_options = rack_options || {}
370
474
 
371
- @options.file_options.merge!(rack_options)
475
+ options.file_options.merge!(rack_options)
372
476
 
373
477
  config_ru_binds = []
374
478
  rack_options.each do |k, v|
375
479
  config_ru_binds << v if k.to_s.start_with?("bind")
376
480
  end
377
481
 
378
- @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
482
+ options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
379
483
 
380
484
  rack_app
381
485
  end
382
486
 
383
- def self.random_token
384
- require 'securerandom' unless defined?(SecureRandom)
487
+ def run_mode_hooks
488
+ workers_before = @_options[:workers]
489
+ key = workers_before > 0 ? :cluster : :single
385
490
 
386
- SecureRandom.hex(16)
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
+
498
+ def set_conditional_default_options
499
+ @_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
500
+ (@_options[:workers] > 1) && Puma.forkable?
501
+ end
502
+
503
+ def warn_hooks
504
+ return if options[:workers] > 0
505
+ return if options[:silence_fork_callback_warning]
506
+
507
+ log_writer = LogWriter.stdio
508
+ @hooks.each_key do |hook|
509
+ options.all_of(hook).each do |hook_options|
510
+ next unless hook_options[:cluster_only]
511
+
512
+ log_writer.log(<<~MSG.tr("\n", " "))
513
+ Warning: The code in the `#{hook}` block will not execute
514
+ in the current Puma configuration. The `#{hook}` block only
515
+ executes in Puma's cluster mode. To fix this, either remove the
516
+ `#{hook}` call or increase Puma's worker count above zero.
517
+ MSG
518
+ end
519
+ end
387
520
  end
388
521
  end
389
522
  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.4.3"
104
- CODE_NAME = "The Eagle of Durango"
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
 
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
@@ -137,7 +135,7 @@ module Puma
137
135
  }.freeze
138
136
 
139
137
  # The basic max request size we'll try to read.
140
- CHUNK_SIZE = 16 * 1024
138
+ CHUNK_SIZE = 64 * 1024
141
139
 
142
140
  # This is the maximum header that is allowed before a client is booted. The parser detects
143
141
  # this, but we'd also like to do this as well.
@@ -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"
@@ -292,6 +291,18 @@ module Puma
292
291
  # Banned keys of response header
293
292
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
294
293
 
295
- PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
294
+ PROXY_PROTOCOL_V1_REGEX = /\APROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
295
+ PROXY_PROTOCOL_V1_MAX_LENGTH = 107
296
+
297
+ # All constants are prefixed with `PIPE_` to avoid name collisions.
298
+ module PipeRequest
299
+ PIPE_WAKEUP = "!"
300
+ PIPE_BOOT = "b"
301
+ PIPE_FORK = "f"
302
+ PIPE_EXTERNAL_TERM = "e"
303
+ PIPE_TERM = "t"
304
+ PIPE_PING = "p"
305
+ PIPE_IDLE = "i"
306
+ end
296
307
  end
297
308
  end