puma 5.2.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +483 -4
  3. data/README.md +101 -20
  4. data/bin/puma-wild +1 -1
  5. data/docs/architecture.md +50 -16
  6. data/docs/compile_options.md +38 -2
  7. data/docs/deployment.md +53 -67
  8. data/docs/fork_worker.md +1 -3
  9. data/docs/jungle/rc.d/README.md +1 -1
  10. data/docs/kubernetes.md +1 -1
  11. data/docs/nginx.md +1 -1
  12. data/docs/plugins.md +15 -15
  13. data/docs/rails_dev_mode.md +2 -3
  14. data/docs/restart.md +7 -7
  15. data/docs/signals.md +11 -10
  16. data/docs/stats.md +8 -8
  17. data/docs/systemd.md +65 -69
  18. data/docs/testing_benchmarks_local_files.md +150 -0
  19. data/docs/testing_test_rackup_ci_files.md +36 -0
  20. data/ext/puma_http11/extconf.rb +44 -13
  21. data/ext/puma_http11/http11_parser.c +24 -11
  22. data/ext/puma_http11/http11_parser.h +2 -2
  23. data/ext/puma_http11/http11_parser.java.rl +2 -2
  24. data/ext/puma_http11/http11_parser.rl +2 -2
  25. data/ext/puma_http11/http11_parser_common.rl +3 -3
  26. data/ext/puma_http11/mini_ssl.c +150 -23
  27. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  28. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  29. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  30. data/ext/puma_http11/puma_http11.c +18 -10
  31. data/lib/puma/app/status.rb +10 -7
  32. data/lib/puma/binder.rb +112 -62
  33. data/lib/puma/cli.rb +24 -20
  34. data/lib/puma/client.rb +162 -36
  35. data/lib/puma/cluster/worker.rb +31 -27
  36. data/lib/puma/cluster/worker_handle.rb +12 -1
  37. data/lib/puma/cluster.rb +102 -61
  38. data/lib/puma/commonlogger.rb +21 -14
  39. data/lib/puma/configuration.rb +78 -54
  40. data/lib/puma/const.rb +135 -97
  41. data/lib/puma/control_cli.rb +25 -20
  42. data/lib/puma/detect.rb +12 -2
  43. data/lib/puma/dsl.rb +308 -58
  44. data/lib/puma/error_logger.rb +20 -11
  45. data/lib/puma/events.rb +6 -126
  46. data/lib/puma/io_buffer.rb +39 -4
  47. data/lib/puma/jruby_restart.rb +2 -1
  48. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  49. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  50. data/lib/puma/launcher.rb +114 -173
  51. data/lib/puma/log_writer.rb +147 -0
  52. data/lib/puma/minissl/context_builder.rb +30 -16
  53. data/lib/puma/minissl.rb +132 -38
  54. data/lib/puma/null_io.rb +5 -0
  55. data/lib/puma/plugin/systemd.rb +90 -0
  56. data/lib/puma/plugin/tmp_restart.rb +1 -1
  57. data/lib/puma/plugin.rb +2 -2
  58. data/lib/puma/rack/builder.rb +7 -7
  59. data/lib/puma/rack_default.rb +19 -4
  60. data/lib/puma/reactor.rb +19 -10
  61. data/lib/puma/request.rb +373 -153
  62. data/lib/puma/runner.rb +74 -28
  63. data/lib/puma/sd_notify.rb +149 -0
  64. data/lib/puma/server.rb +127 -136
  65. data/lib/puma/single.rb +13 -11
  66. data/lib/puma/state_file.rb +39 -7
  67. data/lib/puma/thread_pool.rb +33 -26
  68. data/lib/puma/util.rb +20 -15
  69. data/lib/puma.rb +28 -11
  70. data/lib/rack/handler/puma.rb +113 -86
  71. data/tools/Dockerfile +1 -1
  72. metadata +15 -10
  73. data/lib/puma/queue_close.rb +0 -26
  74. data/lib/puma/systemd.rb +0 -46
data/lib/puma/cluster.rb CHANGED
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/runner'
4
- require 'puma/util'
5
- require 'puma/plugin'
6
- require 'puma/cluster/worker_handle'
7
- require 'puma/cluster/worker'
8
-
9
- require 'time'
3
+ require_relative 'runner'
4
+ require_relative 'util'
5
+ require_relative 'plugin'
6
+ require_relative 'cluster/worker_handle'
7
+ require_relative 'cluster/worker'
10
8
 
11
9
  module Puma
12
10
  # This class is instantiated by the `Puma::Launcher` and used
@@ -17,8 +15,8 @@ module Puma
17
15
  # via the `spawn_workers` method call. Each worker will have it's own
18
16
  # instance of a `Puma::Server`.
19
17
  class Cluster < Runner
20
- def initialize(cli, events)
21
- super cli, events
18
+ def initialize(launcher)
19
+ super(launcher)
22
20
 
23
21
  @phase = 0
24
22
  @workers = []
@@ -27,6 +25,10 @@ module Puma
27
25
  @phased_restart = false
28
26
  end
29
27
 
28
+ # Returns the list of cluster worker handles.
29
+ # @return [Array<Puma::Cluster::WorkerHandle>]
30
+ attr_reader :workers
31
+
30
32
  def stop_workers
31
33
  log "- Gracefully shutting down workers..."
32
34
  @workers.each { |x| x.term }
@@ -43,6 +45,7 @@ module Puma
43
45
  end
44
46
 
45
47
  def start_phased_restart
48
+ @events.fire_on_restart!
46
49
  @phase += 1
47
50
  log "- Starting phased worker restart, phase: #{@phase}"
48
51
 
@@ -91,7 +94,7 @@ module Puma
91
94
 
92
95
  # @version 5.0.0
93
96
  def spawn_worker(idx, master)
94
- @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
97
+ @config.run_hooks(:before_worker_fork, idx, @log_writer)
95
98
 
96
99
  pid = fork { worker(idx, master) }
97
100
  if !pid
@@ -100,31 +103,49 @@ module Puma
100
103
  exit! 1
101
104
  end
102
105
 
103
- @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
106
+ @config.run_hooks(:after_worker_fork, idx, @log_writer)
104
107
  pid
105
108
  end
106
109
 
107
110
  def cull_workers
108
111
  diff = @workers.size - @options[:workers]
109
112
  return if diff < 1
113
+ debug "Culling #{diff} workers"
110
114
 
111
- debug "Culling #{diff.inspect} workers"
112
-
113
- workers_to_cull = @workers[-diff,diff]
114
- debug "Workers to cull: #{workers_to_cull.inspect}"
115
+ workers = workers_to_cull(diff)
116
+ debug "Workers to cull: #{workers.inspect}"
115
117
 
116
- workers_to_cull.each do |worker|
118
+ workers.each do |worker|
117
119
  log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
118
120
  worker.term
119
121
  end
120
122
  end
121
123
 
124
+ def workers_to_cull(diff)
125
+ workers = @workers.sort_by(&:started_at)
126
+
127
+ # In fork_worker mode, worker 0 acts as our master process.
128
+ # We should avoid culling it to preserve copy-on-write memory gains.
129
+ workers.reject! { |w| w.index == 0 } if @options[:fork_worker]
130
+
131
+ workers[cull_start_index(diff), diff]
132
+ end
133
+
134
+ def cull_start_index(diff)
135
+ case @options[:worker_culling_strategy]
136
+ when :oldest
137
+ 0
138
+ else # :youngest
139
+ -diff
140
+ end
141
+ end
142
+
122
143
  # @!attribute [r] next_worker_index
123
144
  def next_worker_index
124
- all_positions = 0...@options[:workers]
125
- occupied_positions = @workers.map { |w| w.index }
126
- available_positions = all_positions.to_a - occupied_positions
127
- available_positions.first
145
+ occupied_positions = @workers.map(&:index)
146
+ idx = 0
147
+ idx += 1 until !occupied_positions.include?(idx)
148
+ idx
128
149
  end
129
150
 
130
151
  def all_workers_booted?
@@ -134,7 +155,7 @@ module Puma
134
155
  def check_workers
135
156
  return if @next_check >= Time.now
136
157
 
137
- @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
158
+ @next_check = Time.now + @options[:worker_check_interval]
138
159
 
139
160
  timeout_workers
140
161
  wait_workers
@@ -157,20 +178,10 @@ module Puma
157
178
  end
158
179
  end
159
180
 
160
- @next_check = [
161
- @workers.reject(&:term?).map(&:ping_timeout).min,
162
- @next_check
163
- ].compact.min
164
- end
165
-
166
- def wakeup!
167
- return unless @wakeup
181
+ t = @workers.reject(&:term?)
182
+ t.map!(&:ping_timeout)
168
183
 
169
- begin
170
- @wakeup.write "!" unless @wakeup.closed?
171
- rescue SystemCallError, IOError
172
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
173
- end
184
+ @next_check = [t.min, @next_check].compact.min
174
185
  end
175
186
 
176
187
  def worker(index, master)
@@ -200,8 +211,8 @@ module Puma
200
211
  stop
201
212
  end
202
213
 
203
- def phased_restart
204
- return false if @options[:preload_app]
214
+ def phased_restart(refork = false)
215
+ return false if @options[:preload_app] && !refork
205
216
 
206
217
  @phased_restart = true
207
218
  wakeup!
@@ -217,7 +228,7 @@ module Puma
217
228
  def stop_blocked
218
229
  @status = :stop if @status == :run
219
230
  wakeup!
220
- @control.stop(true) if @control
231
+ @control&.stop true
221
232
  Process.waitall
222
233
  end
223
234
 
@@ -239,24 +250,24 @@ module Puma
239
250
  old_worker_count = @workers.count { |w| w.phase != @phase }
240
251
  worker_status = @workers.map do |w|
241
252
  {
242
- started_at: w.started_at.utc.iso8601,
253
+ started_at: utc_iso8601(w.started_at),
243
254
  pid: w.pid,
244
255
  index: w.index,
245
256
  phase: w.phase,
246
257
  booted: w.booted?,
247
- last_checkin: w.last_checkin.utc.iso8601,
258
+ last_checkin: utc_iso8601(w.last_checkin),
248
259
  last_status: w.last_status,
249
260
  }
250
261
  end
251
262
 
252
263
  {
253
- started_at: @started_at.utc.iso8601,
264
+ started_at: utc_iso8601(@started_at),
254
265
  workers: @workers.size,
255
266
  phase: @phase,
256
267
  booted_workers: worker_status.count { |w| w[:booted] },
257
268
  old_workers: old_worker_count,
258
269
  worker_status: worker_status,
259
- }
270
+ }.merge(super)
260
271
  end
261
272
 
262
273
  def preload?
@@ -268,7 +279,7 @@ module Puma
268
279
  if (worker = @workers.find { |w| w.index == 0 })
269
280
  worker.phase += 1
270
281
  end
271
- phased_restart
282
+ phased_restart(true)
272
283
  end
273
284
 
274
285
  # We do this in a separate method to keep the lambda scope
@@ -281,7 +292,7 @@ module Puma
281
292
 
282
293
  # Auto-fork after the specified number of requests.
283
294
  if (fork_requests = @options[:fork_worker].to_i) > 0
284
- @launcher.events.register(:ping!) do |w|
295
+ @events.register(:ping!) do |w|
285
296
  fork_worker! if w.index == 0 &&
286
297
  w.phase == 0 &&
287
298
  w.last_status[:requests_count] >= fork_requests
@@ -317,7 +328,7 @@ module Puma
317
328
 
318
329
  stop_workers
319
330
  stop
320
-
331
+ @events.fire_on_stopped!
321
332
  raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
322
333
  exit 0 # Clean exit, workers were stopped
323
334
  end
@@ -332,16 +343,22 @@ module Puma
332
343
  # This is aligned with the output from Runner, see Runner#output_header
333
344
  log "* Workers: #{@options[:workers]}"
334
345
 
335
- # Threads explicitly marked as fork safe will be ignored.
336
- # Used in Rails, but may be used by anyone.
337
- before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
338
-
339
346
  if preload?
347
+ # Threads explicitly marked as fork safe will be ignored. Used in Rails,
348
+ # but may be used by anyone. Note that we need to explicit
349
+ # Process::Waiter check here because there's a bug in Ruby 2.6 and below
350
+ # where calling thread_variable_get on a Process::Waiter will segfault.
351
+ # We can drop that clause once those versions of Ruby are no longer
352
+ # supported.
353
+ fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
354
+
355
+ before = Thread.list.reject(&fork_safe)
356
+
340
357
  log "* Restarts: (\u2714) hot (\u2716) phased"
341
358
  log "* Preloading application"
342
359
  load_and_bind
343
360
 
344
- after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
361
+ after = Thread.list.reject(&fork_safe)
345
362
 
346
363
  if after.size > before.size
347
364
  threads = (after - before)
@@ -357,12 +374,12 @@ module Puma
357
374
  else
358
375
  log "* Restarts: (\u2714) hot (\u2714) phased"
359
376
 
360
- unless @launcher.config.app_configured?
377
+ unless @config.app_configured?
361
378
  error "No application configured, nothing to run"
362
379
  exit 1
363
380
  end
364
381
 
365
- @launcher.binder.parse @options[:binds], self
382
+ @launcher.binder.parse @options[:binds]
366
383
  end
367
384
 
368
385
  read, @wakeup = Puma::Util.pipe
@@ -382,6 +399,8 @@ module Puma
382
399
 
383
400
  log "Use Ctrl-C to stop"
384
401
 
402
+ single_worker_warning
403
+
385
404
  redirect_io
386
405
 
387
406
  Plugins.fire_background
@@ -392,8 +411,7 @@ module Puma
392
411
 
393
412
  @master_read, @worker_write = read, @wakeup
394
413
 
395
- @launcher.config.run_hooks :before_fork, nil, @launcher.events
396
- Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
414
+ @config.run_hooks(:before_fork, nil, @log_writer)
397
415
 
398
416
  spawn_workers
399
417
 
@@ -403,19 +421,21 @@ module Puma
403
421
 
404
422
  begin
405
423
  booted = false
424
+ in_phased_restart = false
425
+ workers_not_booted = @options[:workers]
406
426
 
407
427
  while @status == :run
408
428
  begin
409
429
  if @phased_restart
410
430
  start_phased_restart
411
431
  @phased_restart = false
432
+ in_phased_restart = true
433
+ workers_not_booted = @options[:workers]
412
434
  end
413
435
 
414
436
  check_workers
415
437
 
416
- res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
417
-
418
- if res
438
+ if read.wait_readable([0, @next_check - Time.now].max)
419
439
  req = read.read_nonblock(1)
420
440
 
421
441
  @next_check = Time.now if req == "!"
@@ -434,18 +454,20 @@ module Puma
434
454
  case req
435
455
  when "b"
436
456
  w.boot!
437
- log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
457
+ log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
438
458
  @next_check = Time.now
459
+ workers_not_booted -= 1
439
460
  when "e"
440
461
  # external term, see worker method, Signal.trap "SIGTERM"
441
- w.instance_variable_set :@term, true
462
+ w.term!
442
463
  when "t"
443
464
  w.term unless w.term?
444
465
  when "p"
445
466
  w.ping!(result.sub(/^\d+/,'').chomp)
446
- @launcher.events.fire(:ping!, w)
467
+ @events.fire(:ping!, w)
447
468
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
448
- @launcher.events.fire_on_booted!
469
+ @events.fire_on_booted!
470
+ debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
449
471
  booted = true
450
472
  end
451
473
  end
@@ -453,6 +475,11 @@ module Puma
453
475
  log "! Out-of-sync worker list, no #{pid} worker"
454
476
  end
455
477
  end
478
+ if in_phased_restart && workers_not_booted.zero?
479
+ @events.fire_on_booted!
480
+ debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
481
+ in_phased_restart = false
482
+ end
456
483
 
457
484
  rescue Interrupt
458
485
  @status = :stop
@@ -470,6 +497,15 @@ module Puma
470
497
 
471
498
  private
472
499
 
500
+ def single_worker_warning
501
+ return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
502
+
503
+ log "! WARNING: Detected running cluster mode with 1 worker."
504
+ log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
505
+ log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
506
+ log "! Set the `silence_single_worker_warning` option to silence this warning message."
507
+ end
508
+
473
509
  # loops thru @workers, removing workers that exited, and calling
474
510
  # `#term` if needed
475
511
  def wait_workers
@@ -499,7 +535,12 @@ module Puma
499
535
  def timeout_workers
500
536
  @workers.each do |w|
501
537
  if !w.term? && w.ping_timeout <= Time.now
502
- log "! Terminating timed out worker: #{w.pid}"
538
+ details = if w.booted?
539
+ "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
540
+ else
541
+ "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
542
+ end
543
+ log "! Terminating timed out worker #{details}: #{w.pid}"
503
544
  w.kill
504
545
  end
505
546
  end
@@ -3,7 +3,7 @@
3
3
  module Puma
4
4
  # Rack::CommonLogger forwards every request to the given +app+, and
5
5
  # logs a line in the
6
- # {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
6
+ # {Apache common log format}[https://httpd.apache.org/docs/2.4/logs.html#common]
7
7
  # to the +logger+.
8
8
  #
9
9
  # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
@@ -16,7 +16,7 @@ module Puma
16
16
  # (which is called without arguments in order to make the error appear for
17
17
  # sure)
18
18
  class CommonLogger
19
- # Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
19
+ # Common Log Format: https://httpd.apache.org/docs/2.4/logs.html#common
20
20
  #
21
21
  # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
22
  #
@@ -25,10 +25,17 @@ module Puma
25
25
 
26
26
  HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
27
27
 
28
- CONTENT_LENGTH = 'Content-Length'.freeze
29
- PATH_INFO = 'PATH_INFO'.freeze
30
- QUERY_STRING = 'QUERY_STRING'.freeze
31
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
28
+ LOG_TIME_FORMAT = '%d/%b/%Y:%H:%M:%S %z'
29
+
30
+ CONTENT_LENGTH = 'Content-Length' # should be lower case from app,
31
+ # Util::HeaderHash allows mixed
32
+ HTTP_VERSION = Const::HTTP_VERSION
33
+ HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
34
+ PATH_INFO = Const::PATH_INFO
35
+ QUERY_STRING = Const::QUERY_STRING
36
+ REMOTE_ADDR = Const::REMOTE_ADDR
37
+ REMOTE_USER = 'REMOTE_USER'
38
+ REQUEST_METHOD = Const::REQUEST_METHOD
32
39
 
33
40
  def initialize(app, logger=nil)
34
41
  @app = app
@@ -57,13 +64,13 @@ module Puma
57
64
  now = Time.now
58
65
 
59
66
  msg = HIJACK_FORMAT % [
60
- env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
61
- env["REMOTE_USER"] || "-",
62
- now.strftime("%d/%b/%Y %H:%M:%S"),
67
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
68
+ env[REMOTE_USER] || "-",
69
+ now.strftime(LOG_TIME_FORMAT),
63
70
  env[REQUEST_METHOD],
64
71
  env[PATH_INFO],
65
72
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
66
- env["HTTP_VERSION"],
73
+ env[HTTP_VERSION],
67
74
  now - began_at ]
68
75
 
69
76
  write(msg)
@@ -74,13 +81,13 @@ module Puma
74
81
  length = extract_content_length(header)
75
82
 
76
83
  msg = FORMAT % [
77
- env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
78
- env["REMOTE_USER"] || "-",
79
- now.strftime("%d/%b/%Y:%H:%M:%S %z"),
84
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
85
+ env[REMOTE_USER] || "-",
86
+ now.strftime(LOG_TIME_FORMAT),
80
87
  env[REQUEST_METHOD],
81
88
  env[PATH_INFO],
82
89
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
83
- env["HTTP_VERSION"],
90
+ env[HTTP_VERSION],
84
91
  status.to_s[0..3],
85
92
  length,
86
93
  now - began_at ]
@@ -1,20 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/rack/builder'
4
- require 'puma/plugin'
5
- require 'puma/const'
3
+ require_relative 'rack/builder'
4
+ require_relative 'plugin'
5
+ require_relative 'const'
6
+ # note that dsl is loaded at end of file, requires ConfigDefault constants
6
7
 
7
8
  module Puma
8
-
9
- module ConfigDefault
10
- DefaultRackup = "config.ru"
11
-
12
- DefaultTCPHost = "0.0.0.0"
13
- DefaultTCPPort = 9292
14
- DefaultWorkerTimeout = 60
15
- DefaultWorkerShutdownTimeout = 30
16
- end
17
-
18
9
  # A class used for storing "leveled" configuration options.
19
10
  #
20
11
  # In this class any "user" specified options take precedence over any
@@ -135,7 +126,50 @@ module Puma
135
126
  # is done because an environment variable may have been modified while loading
136
127
  # configuration files.
137
128
  class Configuration
138
- include ConfigDefault
129
+ DEFAULTS = {
130
+ auto_trim_time: 30,
131
+ binds: ['tcp://0.0.0.0:9292'.freeze],
132
+ clean_thread_locals: false,
133
+ debug: false,
134
+ early_hints: nil,
135
+ environment: 'development'.freeze,
136
+ # Number of seconds to wait until we get the first data for the request
137
+ first_data_timeout: 30,
138
+ io_selector_backend: :auto,
139
+ log_requests: false,
140
+ logger: STDOUT,
141
+ # How many requests to attempt inline before sending a client back to
142
+ # the reactor to be subject to normal ordering. The idea here is that
143
+ # we amortize the cost of going back to the reactor for a well behaved
144
+ # but very "greedy" client across 10 requests. This prevents a not
145
+ # well behaved client from monopolizing the thread forever.
146
+ max_fast_inline: 10,
147
+ max_threads: Puma.mri? ? 5 : 16,
148
+ min_threads: 0,
149
+ mode: :http,
150
+ mutate_stdout_and_stderr_to_sync_on_write: true,
151
+ out_of_band: [],
152
+ # Number of seconds for another request within a persistent session.
153
+ persistent_timeout: 20,
154
+ queue_requests: true,
155
+ rackup: 'config.ru'.freeze,
156
+ raise_exception_on_sigterm: true,
157
+ reaping_time: 1,
158
+ remote_address: :socket,
159
+ silence_single_worker_warning: false,
160
+ silence_fork_callback_warning: false,
161
+ tag: File.basename(Dir.getwd),
162
+ tcp_host: '0.0.0.0'.freeze,
163
+ tcp_port: 9292,
164
+ wait_for_less_busy_worker: 0.005,
165
+ worker_boot_timeout: 60,
166
+ worker_check_interval: 5,
167
+ worker_culling_strategy: :youngest,
168
+ worker_shutdown_timeout: 30,
169
+ worker_timeout: 60,
170
+ workers: 0,
171
+ http_content_length_limit: nil
172
+ }
139
173
 
140
174
  def initialize(user_options={}, default_options = {}, &block)
141
175
  default_options = self.puma_default_options.merge(default_options)
@@ -180,34 +214,22 @@ module Puma
180
214
  self
181
215
  end
182
216
 
183
- # @version 5.0.0
184
- def default_max_threads
185
- Puma.mri? ? 5 : 16
217
+ def puma_default_options
218
+ defaults = DEFAULTS.dup
219
+ puma_options_from_env.each { |k,v| defaults[k] = v if v }
220
+ defaults
186
221
  end
187
222
 
188
- def puma_default_options
223
+ def puma_options_from_env
224
+ min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
225
+ max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
226
+ workers = ENV['WEB_CONCURRENCY']
227
+
189
228
  {
190
- :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
191
- :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
192
- :log_requests => false,
193
- :debug => false,
194
- :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
195
- :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
- :mode => :http,
197
- :worker_timeout => DefaultWorkerTimeout,
198
- :worker_boot_timeout => DefaultWorkerTimeout,
199
- :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
200
- :remote_address => :socket,
201
- :tag => method(:infer_tag),
202
- :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
203
- :rackup => DefaultRackup,
204
- :logger => STDOUT,
205
- :persistent_timeout => Const::PERSISTENT_TIMEOUT,
206
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
207
- :raise_exception_on_sigterm => true,
208
- :max_fast_inline => Const::MAX_FAST_INLINE,
209
- :io_selector_backend => :auto,
210
- :mutate_stdout_and_stderr_to_sync_on_write => true,
229
+ min_threads: min && Integer(min),
230
+ max_threads: max && Integer(max),
231
+ workers: workers && Integer(workers),
232
+ environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
211
233
  }
212
234
  end
213
235
 
@@ -223,7 +245,7 @@ module Puma
223
245
  return [] if files == ['-']
224
246
  return files if files.any?
225
247
 
226
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
248
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
227
249
  File.exist?(f)
228
250
  end
229
251
 
@@ -266,7 +288,7 @@ module Puma
266
288
  found = options[:app] || load_rackup
267
289
 
268
290
  if @options[:log_requests]
269
- require 'puma/commonlogger'
291
+ require_relative 'commonlogger'
270
292
  logger = @options[:logger]
271
293
  found = CommonLogger.new(found, logger)
272
294
  end
@@ -279,21 +301,25 @@ module Puma
279
301
  @options[:environment]
280
302
  end
281
303
 
282
- def environment_str
283
- environment.respond_to?(:call) ? environment.call : environment
284
- end
285
-
286
304
  def load_plugin(name)
287
305
  @plugins.create name
288
306
  end
289
307
 
290
- def run_hooks(key, arg, events)
308
+ # @param key [:Symbol] hook to run
309
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
310
+ #
311
+ def run_hooks(key, arg, log_writer, hook_data = nil)
291
312
  @options.all_of(key).each do |b|
292
313
  begin
293
- b.call arg
314
+ if Array === b
315
+ hook_data[b[1]] ||= Hash.new
316
+ b[0].call arg, hook_data[b[1]]
317
+ else
318
+ b.call arg
319
+ end
294
320
  rescue => e
295
- events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
296
- events.debug e.backtrace.join("\n")
321
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
322
+ log_writer.debug e.backtrace.join("\n")
297
323
  end
298
324
  end
299
325
  end
@@ -311,10 +337,6 @@ module Puma
311
337
 
312
338
  private
313
339
 
314
- def infer_tag
315
- File.basename(Dir.getwd)
316
- end
317
-
318
340
  # Load and use the normal Rack builder if we can, otherwise
319
341
  # fallback to our minimal version.
320
342
  def rack_builder
@@ -342,6 +364,8 @@ module Puma
342
364
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
343
365
 
344
366
  rack_app, rack_options = rack_builder.parse_file(rackup)
367
+ rack_options = rack_options || {}
368
+
345
369
  @options.file_options.merge!(rack_options)
346
370
 
347
371
  config_ru_binds = []
@@ -362,4 +386,4 @@ module Puma
362
386
  end
363
387
  end
364
388
 
365
- require 'puma/dsl'
389
+ require_relative 'dsl'