puma 5.6.9-java → 6.6.0-java

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +465 -18
  3. data/README.md +152 -42
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +12 -4
  7. data/docs/java_options.md +54 -0
  8. data/docs/kubernetes.md +12 -0
  9. data/docs/nginx.md +1 -1
  10. data/docs/plugins.md +4 -0
  11. data/docs/restart.md +1 -0
  12. data/docs/signals.md +2 -2
  13. data/docs/stats.md +8 -3
  14. data/docs/systemd.md +13 -7
  15. data/docs/testing_benchmarks_local_files.md +150 -0
  16. data/docs/testing_test_rackup_ci_files.md +36 -0
  17. data/ext/puma_http11/extconf.rb +27 -17
  18. data/ext/puma_http11/http11_parser.c +1 -1
  19. data/ext/puma_http11/http11_parser.h +1 -1
  20. data/ext/puma_http11/http11_parser.java.rl +2 -2
  21. data/ext/puma_http11/http11_parser.rl +2 -2
  22. data/ext/puma_http11/http11_parser_common.rl +2 -2
  23. data/ext/puma_http11/mini_ssl.c +137 -19
  24. data/ext/puma_http11/org/jruby/puma/Http11.java +31 -10
  25. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  26. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
  27. data/ext/puma_http11/puma_http11.c +21 -10
  28. data/lib/puma/app/status.rb +4 -4
  29. data/lib/puma/binder.rb +60 -55
  30. data/lib/puma/cli.rb +22 -20
  31. data/lib/puma/client.rb +93 -30
  32. data/lib/puma/cluster/worker.rb +27 -17
  33. data/lib/puma/cluster/worker_handle.rb +8 -6
  34. data/lib/puma/cluster.rb +121 -47
  35. data/lib/puma/commonlogger.rb +21 -14
  36. data/lib/puma/configuration.rb +101 -65
  37. data/lib/puma/const.rb +141 -93
  38. data/lib/puma/control_cli.rb +19 -15
  39. data/lib/puma/detect.rb +7 -4
  40. data/lib/puma/dsl.rb +521 -88
  41. data/lib/puma/error_logger.rb +22 -13
  42. data/lib/puma/events.rb +6 -126
  43. data/lib/puma/io_buffer.rb +39 -4
  44. data/lib/puma/jruby_restart.rb +0 -15
  45. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  46. data/lib/puma/launcher.rb +121 -181
  47. data/lib/puma/log_writer.rb +147 -0
  48. data/lib/puma/minissl/context_builder.rb +27 -12
  49. data/lib/puma/minissl.rb +105 -11
  50. data/lib/puma/null_io.rb +42 -2
  51. data/lib/puma/plugin/systemd.rb +90 -0
  52. data/lib/puma/plugin/tmp_restart.rb +1 -1
  53. data/lib/puma/puma_http11.jar +0 -0
  54. data/lib/puma/rack/builder.rb +6 -6
  55. data/lib/puma/rack/urlmap.rb +1 -1
  56. data/lib/puma/rack_default.rb +19 -4
  57. data/lib/puma/reactor.rb +19 -10
  58. data/lib/puma/request.rb +368 -169
  59. data/lib/puma/runner.rb +65 -22
  60. data/lib/puma/sd_notify.rb +146 -0
  61. data/lib/puma/server.rb +161 -102
  62. data/lib/puma/single.rb +13 -11
  63. data/lib/puma/state_file.rb +3 -6
  64. data/lib/puma/thread_pool.rb +71 -21
  65. data/lib/puma/util.rb +1 -12
  66. data/lib/puma.rb +9 -10
  67. data/lib/rack/handler/puma.rb +116 -86
  68. data/tools/Dockerfile +2 -2
  69. metadata +17 -12
  70. data/lib/puma/queue_close.rb +0 -26
  71. data/lib/puma/systemd.rb +0 -46
  72. data/lib/rack/version_restriction.rb +0 -15
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 }
@@ -83,16 +85,18 @@ module Puma
83
85
  @workers << WorkerHandle.new(idx, pid, @phase, @options)
84
86
  end
85
87
 
86
- if @options[:fork_worker] &&
87
- @workers.all? {|x| x.phase == @phase}
88
-
88
+ if @options[:fork_worker] && all_workers_in_phase?
89
89
  @fork_writer << "0\n"
90
+
91
+ if worker_at(0).phase > 0
92
+ @fork_writer << "-2\n"
93
+ end
90
94
  end
91
95
  end
92
96
 
93
97
  # @version 5.0.0
94
98
  def spawn_worker(idx, master)
95
- @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
99
+ @config.run_hooks(:before_worker_fork, idx, @log_writer)
96
100
 
97
101
  pid = fork { worker(idx, master) }
98
102
  if !pid
@@ -101,7 +105,7 @@ module Puma
101
105
  exit! 1
102
106
  end
103
107
 
104
- @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
108
+ @config.run_hooks(:after_worker_fork, idx, @log_writer)
105
109
  pid
106
110
  end
107
111
 
@@ -146,10 +150,22 @@ module Puma
146
150
  idx
147
151
  end
148
152
 
153
+ def worker_at(idx)
154
+ @workers.find { |w| w.index == idx }
155
+ end
156
+
149
157
  def all_workers_booted?
150
158
  @workers.count { |w| !w.booted? } == 0
151
159
  end
152
160
 
161
+ def all_workers_in_phase?
162
+ @workers.all? { |w| w.phase == @phase }
163
+ end
164
+
165
+ def all_workers_idle_timed_out?
166
+ (@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
167
+ end
168
+
153
169
  def check_workers
154
170
  return if @next_check >= Time.now
155
171
 
@@ -176,10 +192,10 @@ module Puma
176
192
  end
177
193
  end
178
194
 
179
- @next_check = [
180
- @workers.reject(&:term?).map(&:ping_timeout).min,
181
- @next_check
182
- ].compact.min
195
+ t = @workers.reject(&:term?)
196
+ t.map!(&:ping_timeout)
197
+
198
+ @next_check = [t.min, @next_check].compact.min
183
199
  end
184
200
 
185
201
  def worker(index, master)
@@ -209,8 +225,8 @@ module Puma
209
225
  stop
210
226
  end
211
227
 
212
- def phased_restart
213
- return false if @options[:preload_app]
228
+ def phased_restart(refork = false)
229
+ return false if @options[:preload_app] && !refork
214
230
 
215
231
  @phased_restart = true
216
232
  wakeup!
@@ -226,7 +242,7 @@ module Puma
226
242
  def stop_blocked
227
243
  @status = :stop if @status == :run
228
244
  wakeup!
229
- @control.stop(true) if @control
245
+ @control&.stop true
230
246
  Process.waitall
231
247
  end
232
248
 
@@ -248,24 +264,24 @@ module Puma
248
264
  old_worker_count = @workers.count { |w| w.phase != @phase }
249
265
  worker_status = @workers.map do |w|
250
266
  {
251
- started_at: w.started_at.utc.iso8601,
267
+ started_at: utc_iso8601(w.started_at),
252
268
  pid: w.pid,
253
269
  index: w.index,
254
270
  phase: w.phase,
255
271
  booted: w.booted?,
256
- last_checkin: w.last_checkin.utc.iso8601,
272
+ last_checkin: utc_iso8601(w.last_checkin),
257
273
  last_status: w.last_status,
258
274
  }
259
275
  end
260
276
 
261
277
  {
262
- started_at: @started_at.utc.iso8601,
278
+ started_at: utc_iso8601(@started_at),
263
279
  workers: @workers.size,
264
280
  phase: @phase,
265
281
  booted_workers: worker_status.count { |w| w[:booted] },
266
282
  old_workers: old_worker_count,
267
283
  worker_status: worker_status,
268
- }
284
+ }.merge(super)
269
285
  end
270
286
 
271
287
  def preload?
@@ -274,10 +290,10 @@ module Puma
274
290
 
275
291
  # @version 5.0.0
276
292
  def fork_worker!
277
- if (worker = @workers.find { |w| w.index == 0 })
293
+ if (worker = worker_at 0)
278
294
  worker.phase += 1
279
295
  end
280
- phased_restart
296
+ phased_restart(true)
281
297
  end
282
298
 
283
299
  # We do this in a separate method to keep the lambda scope
@@ -290,7 +306,7 @@ module Puma
290
306
 
291
307
  # Auto-fork after the specified number of requests.
292
308
  if (fork_requests = @options[:fork_worker].to_i) > 0
293
- @launcher.events.register(:ping!) do |w|
309
+ @events.register(:ping!) do |w|
294
310
  fork_worker! if w.index == 0 &&
295
311
  w.phase == 0 &&
296
312
  w.last_status[:requests_count] >= fork_requests
@@ -372,12 +388,12 @@ module Puma
372
388
  else
373
389
  log "* Restarts: (\u2714) hot (\u2714) phased"
374
390
 
375
- unless @launcher.config.app_configured?
391
+ unless @config.app_configured?
376
392
  error "No application configured, nothing to run"
377
393
  exit 1
378
394
  end
379
395
 
380
- @launcher.binder.parse @options[:binds], self
396
+ @launcher.binder.parse @options[:binds]
381
397
  end
382
398
 
383
399
  read, @wakeup = Puma::Util.pipe
@@ -409,8 +425,9 @@ module Puma
409
425
 
410
426
  @master_read, @worker_write = read, @wakeup
411
427
 
412
- @launcher.config.run_hooks :before_fork, nil, @launcher.events
413
- Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
428
+ @options[:worker_write] = @worker_write
429
+
430
+ @config.run_hooks(:before_fork, nil, @log_writer)
414
431
 
415
432
  spawn_workers
416
433
 
@@ -425,6 +442,11 @@ module Puma
425
442
 
426
443
  while @status == :run
427
444
  begin
445
+ if @options[:idle_timeout] && all_workers_idle_timed_out?
446
+ log "- All workers reached idle timeout"
447
+ break
448
+ end
449
+
428
450
  if @phased_restart
429
451
  start_phased_restart
430
452
  @phased_restart = false
@@ -436,48 +458,66 @@ module Puma
436
458
 
437
459
  if read.wait_readable([0, @next_check - Time.now].max)
438
460
  req = read.read_nonblock(1)
461
+ next unless req
439
462
 
440
- @next_check = Time.now if req == "!"
441
- next if !req || req == "!"
463
+ if req == PIPE_WAKEUP
464
+ @next_check = Time.now
465
+ next
466
+ end
442
467
 
443
468
  result = read.gets
444
469
  pid = result.to_i
445
470
 
446
- if req == "b" || req == "f"
471
+ if req == PIPE_BOOT || req == PIPE_FORK
447
472
  pid, idx = result.split(':').map(&:to_i)
448
- w = @workers.find {|x| x.index == idx}
473
+ w = worker_at idx
449
474
  w.pid = pid if w.pid.nil?
450
475
  end
451
476
 
452
477
  if w = @workers.find { |x| x.pid == pid }
453
478
  case req
454
- when "b"
479
+ when PIPE_BOOT
455
480
  w.boot!
456
481
  log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
457
482
  @next_check = Time.now
458
483
  workers_not_booted -= 1
459
- when "e"
484
+ when PIPE_EXTERNAL_TERM
460
485
  # external term, see worker method, Signal.trap "SIGTERM"
461
486
  w.term!
462
- when "t"
487
+ when PIPE_TERM
463
488
  w.term unless w.term?
464
- when "p"
465
- w.ping!(result.sub(/^\d+/,'').chomp)
466
- @launcher.events.fire(:ping!, w)
489
+ when PIPE_PING
490
+ status = result.sub(/^\d+/,'').chomp
491
+ w.ping!(status)
492
+ @events.fire(:ping!, w)
493
+
494
+ if in_phased_restart && @options[:fork_worker] && workers_not_booted.positive? && w0 = worker_at(0)
495
+ w0.ping!(status)
496
+ @events.fire(:ping!, w0)
497
+ end
498
+
467
499
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
468
- @launcher.events.fire_on_booted!
500
+ @events.fire_on_booted!
501
+ debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
469
502
  booted = true
470
503
  end
504
+ when PIPE_IDLE
505
+ if idle_workers[pid]
506
+ idle_workers.delete pid
507
+ else
508
+ idle_workers[pid] = true
509
+ end
471
510
  end
472
511
  else
473
512
  log "! Out-of-sync worker list, no #{pid} worker"
474
513
  end
475
514
  end
515
+
476
516
  if in_phased_restart && workers_not_booted.zero?
477
517
  @events.fire_on_booted!
518
+ debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
478
519
  in_phased_restart = false
479
520
  end
480
-
481
521
  rescue Interrupt
482
522
  @status = :stop
483
523
  end
@@ -506,10 +546,31 @@ module Puma
506
546
  # loops thru @workers, removing workers that exited, and calling
507
547
  # `#term` if needed
508
548
  def wait_workers
549
+ # Reap all children, known workers or otherwise.
550
+ # If puma has PID 1, as it's common in containerized environments,
551
+ # then it's responsible for reaping orphaned processes, so we must reap
552
+ # all our dead children, regardless of whether they are workers we spawned
553
+ # or some reattached processes.
554
+ reaped_children = {}
555
+ loop do
556
+ begin
557
+ pid, status = Process.wait2(-1, Process::WNOHANG)
558
+ break unless pid
559
+ reaped_children[pid] = status
560
+ rescue Errno::ECHILD
561
+ break
562
+ end
563
+ end
564
+
509
565
  @workers.reject! do |w|
510
566
  next false if w.pid.nil?
511
567
  begin
512
- if Process.wait(w.pid, Process::WNOHANG)
568
+ # We may need to check the PID individually because:
569
+ # 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
570
+ # `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
571
+ # 2. When `fork_worker` is enabled, some worker may not be direct children,
572
+ # but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
573
+ if reaped_children.delete(w.pid) || Process.wait(w.pid, Process::WNOHANG)
513
574
  true
514
575
  else
515
576
  w.term if w.term?
@@ -526,6 +587,11 @@ module Puma
526
587
  end
527
588
  end
528
589
  end
590
+
591
+ # Log unknown children
592
+ reaped_children.each do |pid, status|
593
+ log "! reaped unknown child process pid=#{pid} status=#{status}"
594
+ end
529
595
  end
530
596
 
531
597
  # @version 5.0.0
@@ -533,14 +599,22 @@ module Puma
533
599
  @workers.each do |w|
534
600
  if !w.term? && w.ping_timeout <= Time.now
535
601
  details = if w.booted?
536
- "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
602
+ "(Worker #{w.index} failed to check in within #{@options[:worker_timeout]} seconds)"
537
603
  else
538
- "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
604
+ "(Worker #{w.index} failed to boot within #{@options[:worker_boot_timeout]} seconds)"
539
605
  end
540
606
  log "! Terminating timed out worker #{details}: #{w.pid}"
541
607
  w.kill
542
608
  end
543
609
  end
544
610
  end
611
+
612
+ def idle_timed_out_worker_pids
613
+ idle_workers.keys
614
+ end
615
+
616
+ def idle_workers
617
+ @idle_workers ||= {}
618
+ end
545
619
  end
546
620
  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,21 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/rack/builder'
4
- require 'puma/plugin'
5
- require 'puma/const'
3
+ require_relative 'plugin'
4
+ require_relative 'const'
5
+ require_relative 'dsl'
6
6
 
7
7
  module Puma
8
-
9
- module ConfigDefault
10
- DefaultRackup = "config.ru"
11
-
12
- DefaultTCPHost = "0.0.0.0"
13
- DefaultTCPPort = 9292
14
- DefaultWorkerCheckInterval = 5
15
- DefaultWorkerTimeout = 60
16
- DefaultWorkerShutdownTimeout = 30
17
- end
18
-
19
8
  # A class used for storing "leveled" configuration options.
20
9
  #
21
10
  # In this class any "user" specified options take precedence over any
@@ -136,10 +125,56 @@ module Puma
136
125
  # is done because an environment variable may have been modified while loading
137
126
  # configuration files.
138
127
  class Configuration
139
- include ConfigDefault
140
-
141
- def initialize(user_options={}, default_options = {}, &block)
142
- default_options = self.puma_default_options.merge(default_options)
128
+ DEFAULTS = {
129
+ auto_trim_time: 30,
130
+ binds: ['tcp://0.0.0.0:9292'.freeze],
131
+ clean_thread_locals: false,
132
+ debug: false,
133
+ enable_keep_alives: true,
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
+ # Number of seconds to wait until the next request before shutting down.
139
+ idle_timeout: nil,
140
+ io_selector_backend: :auto,
141
+ log_requests: false,
142
+ 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,
149
+ max_threads: Puma.mri? ? 5 : 16,
150
+ min_threads: 0,
151
+ mode: :http,
152
+ mutate_stdout_and_stderr_to_sync_on_write: true,
153
+ out_of_band: [],
154
+ # Number of seconds for another request within a persistent session.
155
+ persistent_timeout: 20,
156
+ queue_requests: true,
157
+ rackup: 'config.ru'.freeze,
158
+ raise_exception_on_sigterm: true,
159
+ reaping_time: 1,
160
+ remote_address: :socket,
161
+ silence_single_worker_warning: false,
162
+ silence_fork_callback_warning: false,
163
+ tag: File.basename(Dir.getwd),
164
+ tcp_host: '0.0.0.0'.freeze,
165
+ tcp_port: 9292,
166
+ wait_for_less_busy_worker: 0.005,
167
+ worker_boot_timeout: 60,
168
+ worker_check_interval: 5,
169
+ worker_culling_strategy: :youngest,
170
+ worker_shutdown_timeout: 30,
171
+ worker_timeout: 60,
172
+ workers: 0,
173
+ http_content_length_limit: nil
174
+ }
175
+
176
+ def initialize(user_options={}, default_options = {}, env = ENV, &block)
177
+ default_options = self.puma_default_options(env).merge(default_options)
143
178
 
144
179
  @options = UserFileDefaultOptions.new(user_options, default_options)
145
180
  @plugins = PluginLoader.new
@@ -151,6 +186,8 @@ module Puma
151
186
  default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
152
187
  end
153
188
 
189
+ @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
190
+
154
191
  if block
155
192
  configure(&block)
156
193
  end
@@ -181,37 +218,27 @@ module Puma
181
218
  self
182
219
  end
183
220
 
184
- # @version 5.0.0
185
- def default_max_threads
186
- Puma.mri? ? 5 : 16
221
+ def puma_default_options(env = ENV)
222
+ defaults = DEFAULTS.dup
223
+ puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
224
+ defaults
187
225
  end
188
226
 
189
- def puma_default_options
227
+ def puma_options_from_env(env = ENV)
228
+ min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
229
+ max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
230
+ workers = if env['WEB_CONCURRENCY'] == 'auto'
231
+ require_processor_counter
232
+ ::Concurrent.available_processor_count
233
+ else
234
+ env['WEB_CONCURRENCY']
235
+ end
236
+
190
237
  {
191
- :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
192
- :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
193
- :log_requests => false,
194
- :debug => false,
195
- :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
196
- :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
197
- :silence_single_worker_warning => false,
198
- :mode => :http,
199
- :worker_check_interval => DefaultWorkerCheckInterval,
200
- :worker_timeout => DefaultWorkerTimeout,
201
- :worker_boot_timeout => DefaultWorkerTimeout,
202
- :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
203
- :worker_culling_strategy => :youngest,
204
- :remote_address => :socket,
205
- :tag => method(:infer_tag),
206
- :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
207
- :rackup => DefaultRackup,
208
- :logger => STDOUT,
209
- :persistent_timeout => Const::PERSISTENT_TIMEOUT,
210
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
211
- :raise_exception_on_sigterm => true,
212
- :max_fast_inline => Const::MAX_FAST_INLINE,
213
- :io_selector_backend => :auto,
214
- :mutate_stdout_and_stderr_to_sync_on_write => true,
238
+ min_threads: min && min != "" && Integer(min),
239
+ max_threads: max && max != "" && Integer(max),
240
+ workers: workers && workers != "" && Integer(workers),
241
+ environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
215
242
  }
216
243
  end
217
244
 
@@ -227,7 +254,7 @@ module Puma
227
254
  return [] if files == ['-']
228
255
  return files if files.any?
229
256
 
230
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
257
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
231
258
  File.exist?(f)
232
259
  end
233
260
 
@@ -270,7 +297,7 @@ module Puma
270
297
  found = options[:app] || load_rackup
271
298
 
272
299
  if @options[:log_requests]
273
- require 'puma/commonlogger'
300
+ require_relative 'commonlogger'
274
301
  logger = @options[:logger]
275
302
  found = CommonLogger.new(found, logger)
276
303
  end
@@ -283,21 +310,27 @@ module Puma
283
310
  @options[:environment]
284
311
  end
285
312
 
286
- def environment_str
287
- environment.respond_to?(:call) ? environment.call : environment
288
- end
289
-
290
313
  def load_plugin(name)
291
314
  @plugins.create name
292
315
  end
293
316
 
294
- def run_hooks(key, arg, events)
317
+ # @param key [:Symbol] hook to run
318
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
319
+ #
320
+ def run_hooks(key, arg, log_writer, hook_data = nil)
321
+ log_writer.debug "Running #{key} hooks"
322
+
295
323
  @options.all_of(key).each do |b|
296
324
  begin
297
- b.call arg
325
+ if Array === b
326
+ hook_data[b[1]] ||= Hash.new
327
+ b[0].call arg, hook_data[b[1]]
328
+ else
329
+ b.call arg
330
+ end
298
331
  rescue => e
299
- events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
300
- events.debug e.backtrace.join("\n")
332
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
333
+ log_writer.debug e.backtrace.join("\n")
301
334
  end
302
335
  end
303
336
  end
@@ -315,8 +348,14 @@ module Puma
315
348
 
316
349
  private
317
350
 
318
- def infer_tag
319
- File.basename(Dir.getwd)
351
+ def require_processor_counter
352
+ require 'concurrent/utility/processor_counter'
353
+ rescue LoadError
354
+ warn <<~MESSAGE
355
+ WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
356
+ Please add "concurrent-ruby" to your Gemfile.
357
+ MESSAGE
358
+ raise
320
359
  end
321
360
 
322
361
  # Load and use the normal Rack builder if we can, otherwise
@@ -324,7 +363,7 @@ module Puma
324
363
  def rack_builder
325
364
  # Load bundler now if we can so that we can pickup rack from
326
365
  # a Gemfile
327
- if ENV.key? 'PUMA_BUNDLER_PRUNED'
366
+ if @puma_bundler_pruned
328
367
  begin
329
368
  require 'bundler/setup'
330
369
  rescue LoadError
@@ -334,11 +373,10 @@ module Puma
334
373
  begin
335
374
  require 'rack'
336
375
  require 'rack/builder'
376
+ ::Rack::Builder
337
377
  rescue LoadError
338
- # ok, use builtin version
339
- return Puma::Rack::Builder
340
- else
341
- return ::Rack::Builder
378
+ require_relative 'rack/builder'
379
+ Puma::Rack::Builder
342
380
  end
343
381
  end
344
382
 
@@ -367,5 +405,3 @@ module Puma
367
405
  end
368
406
  end
369
407
  end
370
-
371
- require 'puma/dsl'