puma 5.6.8 → 6.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +332 -16
  3. data/README.md +79 -29
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/kubernetes.md +12 -0
  8. data/docs/nginx.md +1 -1
  9. data/docs/restart.md +1 -0
  10. data/docs/systemd.md +3 -6
  11. data/docs/testing_benchmarks_local_files.md +150 -0
  12. data/docs/testing_test_rackup_ci_files.md +36 -0
  13. data/ext/puma_http11/extconf.rb +16 -9
  14. data/ext/puma_http11/http11_parser.c +1 -1
  15. data/ext/puma_http11/http11_parser.h +1 -1
  16. data/ext/puma_http11/http11_parser.java.rl +2 -2
  17. data/ext/puma_http11/http11_parser.rl +2 -2
  18. data/ext/puma_http11/http11_parser_common.rl +2 -2
  19. data/ext/puma_http11/mini_ssl.c +127 -19
  20. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  21. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
  23. data/ext/puma_http11/puma_http11.c +17 -9
  24. data/lib/puma/app/status.rb +4 -4
  25. data/lib/puma/binder.rb +50 -53
  26. data/lib/puma/cli.rb +16 -18
  27. data/lib/puma/client.rb +59 -19
  28. data/lib/puma/cluster/worker.rb +18 -11
  29. data/lib/puma/cluster/worker_handle.rb +4 -1
  30. data/lib/puma/cluster.rb +102 -40
  31. data/lib/puma/commonlogger.rb +21 -14
  32. data/lib/puma/configuration.rb +77 -59
  33. data/lib/puma/const.rb +137 -92
  34. data/lib/puma/control_cli.rb +15 -11
  35. data/lib/puma/detect.rb +7 -4
  36. data/lib/puma/dsl.rb +250 -56
  37. data/lib/puma/error_logger.rb +18 -9
  38. data/lib/puma/events.rb +6 -126
  39. data/lib/puma/io_buffer.rb +39 -4
  40. data/lib/puma/jruby_restart.rb +2 -1
  41. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  42. data/lib/puma/launcher.rb +102 -175
  43. data/lib/puma/log_writer.rb +147 -0
  44. data/lib/puma/minissl/context_builder.rb +26 -12
  45. data/lib/puma/minissl.rb +104 -11
  46. data/lib/puma/null_io.rb +16 -2
  47. data/lib/puma/plugin/systemd.rb +90 -0
  48. data/lib/puma/plugin/tmp_restart.rb +1 -1
  49. data/lib/puma/rack/builder.rb +6 -6
  50. data/lib/puma/rack/urlmap.rb +1 -1
  51. data/lib/puma/rack_default.rb +19 -4
  52. data/lib/puma/reactor.rb +19 -10
  53. data/lib/puma/request.rb +380 -172
  54. data/lib/puma/runner.rb +56 -20
  55. data/lib/puma/sd_notify.rb +149 -0
  56. data/lib/puma/server.rb +137 -89
  57. data/lib/puma/single.rb +13 -11
  58. data/lib/puma/state_file.rb +3 -6
  59. data/lib/puma/thread_pool.rb +57 -19
  60. data/lib/puma/util.rb +0 -11
  61. data/lib/puma.rb +9 -10
  62. data/lib/rack/handler/puma.rb +113 -86
  63. data/tools/Dockerfile +2 -2
  64. metadata +11 -7
  65. data/lib/puma/queue_close.rb +0 -26
  66. data/lib/puma/systemd.rb +0 -46
  67. data/lib/rack/version_restriction.rb +0 -15
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'optparse'
4
- require_relative 'state_file'
5
4
  require_relative 'const'
6
5
  require_relative 'detect'
7
- require_relative 'configuration'
8
6
  require 'uri'
9
7
  require 'socket'
10
8
 
@@ -33,9 +31,6 @@ module Puma
33
31
  'worker-count-up' => 'SIGTTIN'
34
32
  }.freeze
35
33
 
36
- # @deprecated 6.0.0
37
- COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
38
-
39
34
  # commands that cannot be used in a request
40
35
  NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
41
36
 
@@ -129,6 +124,9 @@ module Puma
129
124
  end
130
125
 
131
126
  if @config_file
127
+ require_relative 'configuration'
128
+ require_relative 'log_writer'
129
+
132
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {})
133
131
  config.load
134
132
  @state ||= config.options[:state]
@@ -152,6 +150,8 @@ module Puma
152
150
  raise "State file not found: #{@state}"
153
151
  end
154
152
 
153
+ require_relative 'state_file'
154
+
155
155
  sf = Puma::StateFile.new
156
156
  sf.load @state
157
157
 
@@ -167,22 +167,26 @@ module Puma
167
167
  def send_request
168
168
  uri = URI.parse @control_url
169
169
 
170
+ host = uri.host
171
+
170
172
  # create server object by scheme
171
173
  server =
172
174
  case uri.scheme
173
175
  when 'ssl'
174
176
  require 'openssl'
177
+ host = host[1..-2] if host&.start_with? '['
175
178
  OpenSSL::SSL::SSLSocket.new(
176
- TCPSocket.new(uri.host, uri.port),
179
+ TCPSocket.new(host, uri.port),
177
180
  OpenSSL::SSL::SSLContext.new)
178
181
  .tap { |ssl| ssl.sync_close = true } # default is false
179
182
  .tap(&:connect)
180
183
  when 'tcp'
181
- TCPSocket.new uri.host, uri.port
184
+ host = host[1..-2] if host&.start_with? '['
185
+ TCPSocket.new host, uri.port
182
186
  when 'unix'
183
187
  # check for abstract UNIXSocket
184
188
  UNIXSocket.new(@control_url.start_with?('unix://@') ?
185
- "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
189
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
186
190
  else
187
191
  raise "Invalid scheme: #{uri.scheme}"
188
192
  end
@@ -287,7 +291,7 @@ module Puma
287
291
 
288
292
  private
289
293
  def start
290
- require 'puma/cli'
294
+ require_relative 'cli'
291
295
 
292
296
  run_args = []
293
297
 
@@ -299,13 +303,13 @@ module Puma
299
303
  run_args += ["-C", @config_file] if @config_file
300
304
  run_args += ["-e", @environment] if @environment
301
305
 
302
- events = Puma::Events.new @stdout, @stderr
306
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
303
307
 
304
308
  # replace $0 because puma use it to generate restart command
305
309
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
306
310
  $0 = puma_cmd if File.exist?(puma_cmd)
307
311
 
308
- cli = Puma::CLI.new run_args, events
312
+ cli = Puma::CLI.new run_args, log_writer
309
313
  cli.run
310
314
  end
311
315
  end
data/lib/puma/detect.rb CHANGED
@@ -8,15 +8,18 @@ module Puma
8
8
  # @version 5.2.1
9
9
  HAS_FORK = ::Process.respond_to? :fork
10
10
 
11
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
11
13
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
14
 
13
- IS_OSX = RUBY_PLATFORM.include? 'darwin'
15
+ IS_OSX = RUBY_DESCRIPTION.include? 'darwin'
16
+
17
+ IS_WINDOWS = RUBY_DESCRIPTION.match?(/mswin|ming|cygwin/)
14
18
 
15
- IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
16
- IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
19
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
17
20
 
18
21
  # @version 5.2.0
19
- IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
22
+ IS_MRI = RUBY_ENGINE == 'ruby'
20
23
 
21
24
  def self.jruby?
22
25
  IS_JRUBY
data/lib/puma/dsl.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
- require 'puma/util'
3
+ require_relative 'const'
4
+ require_relative 'util'
5
5
 
6
6
  module Puma
7
7
  # The methods that are available for use inside the configuration file.
@@ -32,8 +32,24 @@ module Puma
32
32
  # You can also find many examples being used by the test suite in
33
33
  # +test/config+.
34
34
  #
35
+ # Puma v6 adds the option to specify a key name (String or Symbol) to the
36
+ # hooks that run inside the forked workers. All the hooks run inside the
37
+ # {Puma::Cluster::Worker#run} method.
38
+ #
39
+ # Previously, the worker index and the LogWriter instance were passed to the
40
+ # hook blocks/procs. If a key name is specified, a hash is passed as the last
41
+ # parameter. This allows storage of data, typically objects that are created
42
+ # before the worker that need to be passed to the hook when the worker is shutdown.
43
+ #
44
+ # The following hooks have been updated:
45
+ #
46
+ # | DSL Method | Options Key | Fork Block Location |
47
+ # | on_worker_boot | :before_worker_boot | inside, before |
48
+ # | on_worker_shutdown | :before_worker_shutdown | inside, after |
49
+ # | on_refork | :before_refork | inside |
50
+ #
35
51
  class DSL
36
- include ConfigDefault
52
+ ON_WORKER_KEY = [String, Symbol].freeze
37
53
 
38
54
  # convenience method so logic can be used in CI
39
55
  # @see ssl_bind
@@ -49,28 +65,58 @@ module Puma
49
65
 
50
66
  ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
51
67
 
68
+ low_latency_str = opts.key?(:low_latency) ? "&low_latency=#{opts[:low_latency]}" : ''
52
69
  backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
53
70
 
54
71
  if defined?(JRUBY_VERSION)
55
- ssl_cipher_list = opts[:ssl_cipher_list] ?
56
- "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
72
+ cipher_suites = opts[:ssl_cipher_list] ? "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil # old name
73
+ cipher_suites = "#{cipher_suites}&cipher_suites=#{opts[:cipher_suites]}" if opts[:cipher_suites]
74
+ protocols = opts[:protocols] ? "&protocols=#{opts[:protocols]}" : nil
57
75
 
58
76
  keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
77
+ keystore_additions = "#{keystore_additions}&keystore-type=#{opts[:keystore_type]}" if opts[:keystore_type]
78
+ if opts[:truststore]
79
+ truststore_additions = "&truststore=#{opts[:truststore]}"
80
+ truststore_additions = "#{truststore_additions}&truststore-pass=#{opts[:truststore_pass]}" if opts[:truststore_pass]
81
+ truststore_additions = "#{truststore_additions}&truststore-type=#{opts[:truststore_type]}" if opts[:truststore_type]
82
+ end
59
83
 
60
- "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
84
+ "ssl://#{host}:#{port}?#{keystore_additions}#{truststore_additions}#{cipher_suites}#{protocols}" \
61
85
  "&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
62
86
  else
63
- ssl_cipher_filter = opts[:ssl_cipher_filter] ?
64
- "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
65
-
66
- v_flags = (ary = opts[:verification_flags]) ?
67
- "&verification_flags=#{Array(ary).join ','}" : nil
68
-
69
- cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(opts[:cert])}" : nil
70
- key_flags = (cert = opts[:key]) ? "&key=#{Puma::Util.escape(opts[:key])}" : nil
87
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ? "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
88
+ v_flags = (ary = opts[:verification_flags]) ? "&verification_flags=#{Array(ary).join ','}" : nil
89
+
90
+ cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
91
+ key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
92
+ password_flags = (password_command = opts[:key_password_command]) ? "&key_password_command=#{Puma::Util.escape(password_command)}" : nil
93
+
94
+ reuse_flag =
95
+ if (reuse = opts[:reuse])
96
+ if reuse == true
97
+ '&reuse=dflt'
98
+ elsif reuse.is_a?(Hash) && (reuse.key?(:size) || reuse.key?(:timeout))
99
+ val = +''
100
+ if (size = reuse[:size]) && Integer === size
101
+ val << size.to_s
102
+ end
103
+ if (timeout = reuse[:timeout]) && Integer === timeout
104
+ val << ",#{timeout}"
105
+ end
106
+ if val.empty?
107
+ nil
108
+ else
109
+ "&reuse=#{val}"
110
+ end
111
+ else
112
+ nil
113
+ end
114
+ else
115
+ nil
116
+ end
71
117
 
72
- "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}" \
73
- "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
118
+ "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{password_flags}#{ssl_cipher_filter}" \
119
+ "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
74
120
  end
75
121
  end
76
122
 
@@ -106,7 +152,7 @@ module Puma
106
152
  end
107
153
 
108
154
  def default_host
109
- @options[:default_host] || Configuration::DefaultTCPHost
155
+ @options[:default_host] || Configuration::DEFAULTS[:tcp_host]
110
156
  end
111
157
 
112
158
  def inject(&blk)
@@ -206,6 +252,7 @@ module Puma
206
252
  #
207
253
  # * Set the socket backlog depth with +backlog+, default is 1024.
208
254
  # * Set up an SSL certificate with +key+ & +cert+.
255
+ # * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
209
256
  # * Set whether to optimize for low latency instead of throughput with
210
257
  # +low_latency+, default is to not optimize for low latency. This is done
211
258
  # via +Socket::TCP_NODELAY+.
@@ -215,6 +262,8 @@ module Puma
215
262
  # bind 'unix:///var/run/puma.sock?backlog=512'
216
263
  # @example SSL cert
217
264
  # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
265
+ # @example SSL cert for mutual TLS (mTLS)
266
+ # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
218
267
  # @example Disable optimization for low latency
219
268
  # bind 'tcp://0.0.0.0:9292?low_latency=false'
220
269
  # @example Socket permissions
@@ -266,16 +315,22 @@ module Puma
266
315
  bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
267
316
  end
268
317
 
318
+ # Define how long the tcp socket stays open, if no data has been received.
319
+ # @see Puma::Server.new
320
+ def first_data_timeout(seconds)
321
+ @options[:first_data_timeout] = Integer(seconds)
322
+ end
323
+
269
324
  # Define how long persistent connections can be idle before Puma closes them.
270
325
  # @see Puma::Server.new
271
326
  def persistent_timeout(seconds)
272
327
  @options[:persistent_timeout] = Integer(seconds)
273
328
  end
274
329
 
275
- # Define how long the tcp socket stays open, if no data has been received.
330
+ # If a new request is not received within this number of seconds, begin shutting down.
276
331
  # @see Puma::Server.new
277
- def first_data_timeout(seconds)
278
- @options[:first_data_timeout] = Integer(seconds)
332
+ def idle_timeout(seconds)
333
+ @options[:idle_timeout] = Integer(seconds)
279
334
  end
280
335
 
281
336
  # Work around leaky apps that leave garbage in Thread locals
@@ -371,6 +426,11 @@ module Puma
371
426
  @options[:log_requests] = which
372
427
  end
373
428
 
429
+ # Pass in a custom logging class instance
430
+ def custom_logger(custom_logger)
431
+ @options[:custom_logger] = custom_logger
432
+ end
433
+
374
434
  # Show debugging info
375
435
  #
376
436
  def debug
@@ -452,6 +512,16 @@ module Puma
452
512
  # Puma will assume you are using the +localhost+ gem and try to load the
453
513
  # appropriate files.
454
514
  #
515
+ # When using the options hash parameter, the `reuse:` value is either
516
+ # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
517
+ # and/or `:timeout` keys, each with integer values.
518
+ #
519
+ # The `cert:` options hash parameter can be the path to a certificate
520
+ # file including all intermediate certificates in PEM format.
521
+ #
522
+ # The `cert_pem:` options hash parameter can be String containing the
523
+ # cerificate and all intermediate certificates in PEM format.
524
+ #
455
525
  # @example
456
526
  # ssl_bind '127.0.0.1', '9292', {
457
527
  # cert: path_to_cert,
@@ -459,6 +529,7 @@ module Puma
459
529
  # ssl_cipher_filter: cipher_filter, # optional
460
530
  # verify_mode: verify_mode, # default 'none'
461
531
  # verification_flags: flags, # optional, not supported by JRuby
532
+ # reuse: true # optional
462
533
  # }
463
534
  #
464
535
  # @example Using self-signed certificate with the +localhost+ gem:
@@ -468,6 +539,7 @@ module Puma
468
539
  # ssl_bind '127.0.0.1', '9292', {
469
540
  # cert_pem: File.read(path_to_cert),
470
541
  # key_pem: File.read(path_to_key),
542
+ # reuse: {size: 2_000, timeout: 20} # optional
471
543
  # }
472
544
  #
473
545
  # @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
@@ -531,6 +603,11 @@ module Puma
531
603
  @options[:silence_single_worker_warning] = true
532
604
  end
533
605
 
606
+ # Disable warning message when running single mode with callback hook defined.
607
+ def silence_fork_callback_warning
608
+ @options[:silence_fork_callback_warning] = true
609
+ end
610
+
534
611
  # Code to run immediately before master process
535
612
  # forks workers (once on boot). These hooks can block if necessary
536
613
  # to wait for background operations unknown to Puma to finish before
@@ -546,6 +623,8 @@ module Puma
546
623
  # puts "Starting workers..."
547
624
  # end
548
625
  def before_fork(&block)
626
+ warn_if_in_single_mode('before_fork')
627
+
549
628
  @options[:before_fork] ||= []
550
629
  @options[:before_fork] << block
551
630
  end
@@ -560,9 +639,10 @@ module Puma
560
639
  # on_worker_boot do
561
640
  # puts 'Before worker boot...'
562
641
  # end
563
- def on_worker_boot(&block)
564
- @options[:before_worker_boot] ||= []
565
- @options[:before_worker_boot] << block
642
+ def on_worker_boot(key = nil, &block)
643
+ warn_if_in_single_mode('on_worker_boot')
644
+
645
+ process_hook :before_worker_boot, key, block, 'on_worker_boot'
566
646
  end
567
647
 
568
648
  # Code to run immediately before a worker shuts
@@ -577,9 +657,10 @@ module Puma
577
657
  # on_worker_shutdown do
578
658
  # puts 'On worker shutdown...'
579
659
  # end
580
- def on_worker_shutdown(&block)
581
- @options[:before_worker_shutdown] ||= []
582
- @options[:before_worker_shutdown] << block
660
+ def on_worker_shutdown(key = nil, &block)
661
+ warn_if_in_single_mode('on_worker_shutdown')
662
+
663
+ process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
583
664
  end
584
665
 
585
666
  # Code to run in the master right before a worker is started. The worker's
@@ -593,8 +674,9 @@ module Puma
593
674
  # puts 'Before worker fork...'
594
675
  # end
595
676
  def on_worker_fork(&block)
596
- @options[:before_worker_fork] ||= []
597
- @options[:before_worker_fork] << block
677
+ warn_if_in_single_mode('on_worker_fork')
678
+
679
+ process_hook :before_worker_fork, nil, block, 'on_worker_fork'
598
680
  end
599
681
 
600
682
  # Code to run in the master after a worker has been started. The worker's
@@ -608,12 +690,23 @@ module Puma
608
690
  # puts 'After worker fork...'
609
691
  # end
610
692
  def after_worker_fork(&block)
611
- @options[:after_worker_fork] ||= []
612
- @options[:after_worker_fork] << block
693
+ warn_if_in_single_mode('after_worker_fork')
694
+
695
+ process_hook :after_worker_fork, nil, block, 'after_worker_fork'
613
696
  end
614
697
 
615
698
  alias_method :after_worker_boot, :after_worker_fork
616
699
 
700
+ # Code to run after puma is booted (works for both: single and clustered)
701
+ #
702
+ # @example
703
+ # on_booted do
704
+ # puts 'After booting...'
705
+ # end
706
+ def on_booted(&block)
707
+ @config.options[:events].on_booted(&block)
708
+ end
709
+
617
710
  # When `fork_worker` is enabled, code to run in Worker 0
618
711
  # before all other workers are re-forked from this process,
619
712
  # after the server has temporarily stopped serving requests
@@ -632,9 +725,53 @@ module Puma
632
725
  # end
633
726
  # @version 5.0.0
634
727
  #
635
- def on_refork(&block)
636
- @options[:before_refork] ||= []
637
- @options[:before_refork] << block
728
+ def on_refork(key = nil, &block)
729
+ process_hook :before_refork, key, block, 'on_refork'
730
+ end
731
+
732
+ # Provide a block to be executed just before a thread is added to the thread
733
+ # pool. Be careful: while the block executes, thread creation is delayed, and
734
+ # probably a request will have to wait too! The new thread will not be added to
735
+ # the threadpool until the provided block returns.
736
+ #
737
+ # Return values are ignored.
738
+ # Raising an exception will log a warning.
739
+ #
740
+ # This hook is useful for doing something when the thread pool grows.
741
+ #
742
+ # This can be called multiple times to add several hooks.
743
+ #
744
+ # @example
745
+ # on_thread_start do
746
+ # puts 'On thread start...'
747
+ # end
748
+ def on_thread_start(&block)
749
+ @options[:before_thread_start] ||= []
750
+ @options[:before_thread_start] << block
751
+ end
752
+
753
+ # Provide a block to be executed after a thread is trimmed from the thread
754
+ # pool. Be careful: while this block executes, Puma's main loop is
755
+ # blocked, so no new requests will be picked up.
756
+ #
757
+ # This hook only runs when a thread in the threadpool is trimmed by Puma.
758
+ # It does not run when a thread dies due to exceptions or any other cause.
759
+ #
760
+ # Return values are ignored.
761
+ # Raising an exception will log a warning.
762
+ #
763
+ # This hook is useful for cleaning up thread local resources when a thread
764
+ # is trimmed.
765
+ #
766
+ # This can be called multiple times to add several hooks.
767
+ #
768
+ # @example
769
+ # on_thread_exit do
770
+ # puts 'On thread exit...'
771
+ # end
772
+ def on_thread_exit(&block)
773
+ @options[:before_thread_exit] ||= []
774
+ @options[:before_thread_exit] << block
638
775
  end
639
776
 
640
777
  # Code to run out-of-band when the worker is idle.
@@ -647,8 +784,7 @@ module Puma
647
784
  #
648
785
  # This can be called multiple times to add several hooks.
649
786
  def out_of_band(&block)
650
- @options[:out_of_band] ||= []
651
- @options[:out_of_band] << block
787
+ process_hook :out_of_band, nil, block, 'out_of_band'
652
788
  end
653
789
 
654
790
  # The directory to operate out of.
@@ -769,7 +905,8 @@ module Puma
769
905
  # not a request timeout, it is to protect against a hung or dead process.
770
906
  # Setting this value will not protect against slow requests.
771
907
  #
772
- # The minimum value is 6 seconds, the default value is 60 seconds.
908
+ # This value must be greater than worker_check_interval.
909
+ # The default value is 60 seconds.
773
910
  #
774
911
  # @note Cluster mode only.
775
912
  # @example
@@ -778,7 +915,7 @@ module Puma
778
915
  #
779
916
  def worker_timeout(timeout)
780
917
  timeout = Integer(timeout)
781
- min = @options.fetch(:worker_check_interval, Puma::ConfigDefault::DefaultWorkerCheckInterval)
918
+ min = @options.fetch(:worker_check_interval, Configuration::DEFAULTS[:worker_check_interval])
782
919
 
783
920
  if timeout <= min
784
921
  raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
@@ -882,13 +1019,16 @@ module Puma
882
1019
  # There are 5 possible values:
883
1020
  #
884
1021
  # 1. **:socket** (the default) - read the peername from the socket using the
885
- # syscall. This is the normal behavior.
1022
+ # syscall. This is the normal behavior. If this fails for any reason (e.g.,
1023
+ # if the peer disconnects between the connection being accepted and the getpeername
1024
+ # system call), Puma will return "0.0.0.0"
886
1025
  # 2. **:localhost** - set the remote address to "127.0.0.1"
887
1026
  # 3. **header: <http_header>**- set the remote address to the value of the
888
1027
  # provided http header. For instance:
889
1028
  # `set_remote_address header: "X-Real-IP"`.
890
1029
  # Only the first word (as separated by spaces or comma) is used, allowing
891
- # headers such as X-Forwarded-For to be used as well.
1030
+ # headers such as X-Forwarded-For to be used as well. If this header is absent,
1031
+ # Puma will fall back to the behavior of :socket
892
1032
  # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
893
1033
  # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
894
1034
  # protocol attached to it, will fall back to :socket
@@ -942,23 +1082,6 @@ module Puma
942
1082
  @options[:fork_worker] = Integer(after_requests)
943
1083
  end
944
1084
 
945
- # When enabled, Puma will GC 4 times before forking workers.
946
- # If available (Ruby 2.7+), we will also call GC.compact.
947
- # Not recommended for non-MRI Rubies.
948
- #
949
- # Based on the work of Koichi Sasada and Aaron Patterson, this option may
950
- # decrease memory utilization of preload-enabled cluster-mode Pumas. It will
951
- # also increase time to boot and fork. See your logs for details on how much
952
- # time this adds to your boot process. For most apps, it will be less than one
953
- # second.
954
- #
955
- # @see Puma::Cluster#nakayoshi_gc
956
- # @version 5.0.0
957
- #
958
- def nakayoshi_fork(enabled=true)
959
- @options[:nakayoshi_fork] = enabled
960
- end
961
-
962
1085
  # The number of requests to attempt inline before sending a client back to
963
1086
  # the reactor to be subject to normal ordering.
964
1087
  #
@@ -989,6 +1112,51 @@ module Puma
989
1112
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
990
1113
  end
991
1114
 
1115
+ # Specify how big the request payload should be, in bytes.
1116
+ # This limit is compared against Content-Length HTTP header.
1117
+ # If the payload size (CONTENT_LENGTH) is larger than http_content_length_limit,
1118
+ # HTTP 413 status code is returned.
1119
+ #
1120
+ # When no Content-Length http header is present, it is compared against the
1121
+ # size of the body of the request.
1122
+ #
1123
+ # The default value for http_content_length_limit is nil.
1124
+ def http_content_length_limit(limit)
1125
+ @options[:http_content_length_limit] = limit
1126
+ end
1127
+
1128
+ # Supported http methods, which will replace `Puma::Const::SUPPORTED_HTTP_METHODS`.
1129
+ # The value of `:any` will allows all methods, otherwise, the value must be
1130
+ # an array of strings. Note that methods are all uppercase.
1131
+ #
1132
+ # `Puma::Const::SUPPORTED_HTTP_METHODS` is conservative, if you want a
1133
+ # complete set of methods, the methods defined by the
1134
+ # [IANA Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml)
1135
+ # are pre-defined as the constant `Puma::Const::IANA_HTTP_METHODS`.
1136
+ #
1137
+ # @note If the `methods` value is `:any`, no method check with be performed,
1138
+ # similar to Puma v5 and earlier.
1139
+ #
1140
+ # @example Adds 'PROPFIND' to existing supported methods
1141
+ # supported_http_methods(Puma::Const::SUPPORTED_HTTP_METHODS + ['PROPFIND'])
1142
+ # @example Restricts methods to the array elements
1143
+ # supported_http_methods %w[HEAD GET POST PUT DELETE OPTIONS PROPFIND]
1144
+ # @example Restricts methods to the methods in the IANA Registry
1145
+ # supported_http_methods Puma::Const::IANA_HTTP_METHODS
1146
+ # @example Allows any method
1147
+ # supported_http_methods :any
1148
+ #
1149
+ def supported_http_methods(methods)
1150
+ if methods == :any
1151
+ @options[:supported_http_methods] = :any
1152
+ elsif Array === methods && methods == (ary = methods.grep(String).uniq) &&
1153
+ !ary.empty?
1154
+ @options[:supported_http_methods] = ary
1155
+ else
1156
+ raise "supported_http_methods must be ':any' or a unique array of strings"
1157
+ end
1158
+ end
1159
+
992
1160
  private
993
1161
 
994
1162
  # To avoid adding cert_pem and key_pem as URI params, we store them on the
@@ -1008,5 +1176,31 @@ module Puma
1008
1176
  end
1009
1177
  end
1010
1178
  end
1179
+
1180
+ def process_hook(options_key, key, block, meth)
1181
+ @options[options_key] ||= []
1182
+ if ON_WORKER_KEY.include? key.class
1183
+ @options[options_key] << [block, key.to_sym]
1184
+ elsif key.nil?
1185
+ @options[options_key] << block
1186
+ else
1187
+ raise "'#{meth}' key must be String or Symbol"
1188
+ end
1189
+ end
1190
+
1191
+ def warn_if_in_single_mode(hook_name)
1192
+ return if @options[:silence_fork_callback_warning]
1193
+ # user_options (CLI) have precedence over config file
1194
+ workers_val = @config.options.user_options[:workers] || @options[:workers] ||
1195
+ @config.puma_default_options[:workers] || 0
1196
+ if workers_val == 0
1197
+ log_string =
1198
+ "Warning: You specified code to run in a `#{hook_name}` block, " \
1199
+ "but Puma is not configured to run in cluster mode (worker count > 0 ), " \
1200
+ "so your `#{hook_name}` block did not run"
1201
+
1202
+ LogWriter.stdio.log(log_string)
1203
+ end
1204
+ end
1011
1205
  end
1012
1206
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
3
+ require_relative 'const'
4
4
 
5
5
  module Puma
6
6
  # The implementation of a detailed error logging.
@@ -13,6 +13,8 @@ module Puma
13
13
 
14
14
  REQUEST_FORMAT = %{"%s %s%s" - (%s)}
15
15
 
16
+ LOG_QUEUE = Queue.new
17
+
16
18
  def initialize(ioerr)
17
19
  @ioerr = ioerr
18
20
 
@@ -31,7 +33,7 @@ module Puma
31
33
  # and before all remaining info.
32
34
  #
33
35
  def info(options={})
34
- log title(options)
36
+ internal_write title(options)
35
37
  end
36
38
 
37
39
  # Print occurred error details only if
@@ -53,7 +55,7 @@ module Puma
53
55
  string_block << request_dump(req) if request_parsed?(req)
54
56
  string_block << error.backtrace if error
55
57
 
56
- log string_block.join("\n")
58
+ internal_write string_block.join("\n")
57
59
  end
58
60
 
59
61
  def title(options={})
@@ -93,12 +95,19 @@ module Puma
93
95
  req && req.env[REQUEST_METHOD]
94
96
  end
95
97
 
96
- private
97
-
98
- def log(str)
99
- ioerr.puts str
100
-
101
- ioerr.flush unless ioerr.sync
98
+ def internal_write(str)
99
+ LOG_QUEUE << str
100
+ while (w_str = LOG_QUEUE.pop(true)) do
101
+ begin
102
+ @ioerr.is_a?(IO) and @ioerr.wait_writable(1)
103
+ @ioerr.write "#{w_str}\n"
104
+ @ioerr.flush unless @ioerr.sync
105
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
106
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
107
+ end
108
+ end
109
+ rescue ThreadError
102
110
  end
111
+ private :internal_write
103
112
  end
104
113
  end