puma 7.0.4 → 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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +162 -0
  3. data/README.md +25 -13
  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/grpc.md +62 -0
  10. data/docs/images/favicon.svg +1 -0
  11. data/docs/images/running-puma.svg +1 -0
  12. data/docs/images/standard-logo.svg +1 -0
  13. data/docs/jungle/README.md +1 -1
  14. data/docs/kubernetes.md +5 -12
  15. data/docs/plugins.md +2 -2
  16. data/docs/signals.md +10 -10
  17. data/docs/stats.md +2 -2
  18. data/docs/systemd.md +3 -3
  19. data/ext/puma_http11/http11_parser.java.rl +51 -65
  20. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
  23. data/ext/puma_http11/puma_http11.c +101 -109
  24. data/lib/puma/app/status.rb +10 -2
  25. data/lib/puma/binder.rb +2 -2
  26. data/lib/puma/cli.rb +1 -1
  27. data/lib/puma/client.rb +138 -102
  28. data/lib/puma/client_env.rb +171 -0
  29. data/lib/puma/cluster/worker.rb +10 -9
  30. data/lib/puma/cluster/worker_handle.rb +2 -2
  31. data/lib/puma/cluster.rb +12 -11
  32. data/lib/puma/cluster_accept_loop_delay.rb +17 -18
  33. data/lib/puma/configuration.rb +87 -17
  34. data/lib/puma/const.rb +4 -3
  35. data/lib/puma/control_cli.rb +1 -1
  36. data/lib/puma/detect.rb +11 -0
  37. data/lib/puma/dsl.rb +115 -18
  38. data/lib/puma/launcher/bundle_pruner.rb +2 -4
  39. data/lib/puma/launcher.rb +35 -30
  40. data/lib/puma/log_writer.rb +8 -2
  41. data/lib/puma/reactor.rb +3 -12
  42. data/lib/puma/{request.rb → response.rb} +25 -194
  43. data/lib/puma/runner.rb +1 -1
  44. data/lib/puma/server.rb +103 -64
  45. data/lib/puma/server_plugin_control.rb +32 -0
  46. data/lib/puma/single.rb +2 -2
  47. data/lib/puma/state_file.rb +3 -2
  48. data/lib/puma/thread_pool.rb +139 -24
  49. data/lib/rack/handler/puma.rb +1 -1
  50. data/tools/Dockerfile +13 -5
  51. metadata +16 -5
  52. data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/dsl.rb CHANGED
@@ -155,7 +155,7 @@ module Puma
155
155
  end
156
156
 
157
157
  def default_host
158
- @options[:default_host] || Configuration::DEFAULTS[:tcp_host]
158
+ @options[:default_host] || Configuration.default_tcp_host
159
159
  end
160
160
 
161
161
  def inject(&blk)
@@ -216,6 +216,8 @@ module Puma
216
216
  # activate_control_app 'unix:///var/run/pumactl.sock', { auth_token: '12345' }
217
217
  # @example
218
218
  # activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true }
219
+ # @example
220
+ # activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true, data_only: true}
219
221
  #
220
222
  def activate_control_app(url="auto", opts={})
221
223
  if url == "auto"
@@ -240,6 +242,7 @@ module Puma
240
242
 
241
243
  @options[:control_auth_token] = auth_token
242
244
  @options[:control_url_umask] = opts[:umask] if opts[:umask]
245
+ @options[:control_data_only] = opts[:data_only] if opts[:data_only]
243
246
  end
244
247
 
245
248
  # Load additional configuration from a file.
@@ -257,7 +260,8 @@ module Puma
257
260
  # accepted protocols. Multiple urls can be bound to, calling +bind+ does
258
261
  # not overwrite previous bindings.
259
262
  #
260
- # The default is "tcp://0.0.0.0:9292".
263
+ # The default is "tcp://[::]:9292" when IPv6 interfaces are available,
264
+ # otherwise "tcp://0.0.0.0:9292".
261
265
  #
262
266
  # You can use query parameters within the url to specify options:
263
267
  #
@@ -276,7 +280,7 @@ module Puma
276
280
  # @example SSL cert for mutual TLS (mTLS)
277
281
  # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
278
282
  # @example Disable optimization for low latency
279
- # bind 'tcp://0.0.0.0:9292?low_latency=false'
283
+ # bind 'tcp://[::]:9292?low_latency=false'
280
284
  # @example Socket permissions
281
285
  # bind 'unix:///var/run/puma.sock?umask=0111'
282
286
  #
@@ -346,7 +350,7 @@ module Puma
346
350
 
347
351
  # Define how long persistent connections can be idle before Puma closes them.
348
352
  #
349
- # The default is 20 seconds.
353
+ # The default is 65 seconds.
350
354
  #
351
355
  # @example
352
356
  # persistent_timeout 30
@@ -592,6 +596,29 @@ module Puma
592
596
  @options[:max_threads] = max
593
597
  end
594
598
 
599
+ # Configure the max number of IO threads.
600
+ #
601
+ # When request handlers know the current requests will no longer use a significant amount
602
+ # of CPU, they can mark the current request as IO bound using <tt>env["puma.mark_as_io_bound"]</tt>.
603
+ #
604
+ # Threads marked as IO bound are allowed to go over the max thread limit.
605
+ #
606
+ # @example
607
+ # threads 5
608
+ # max_io_threads 5
609
+ #
610
+ # The above example allows for 5 regular threads and 5 IO threads to process requests concurrently.
611
+ # Any IO thread over the limit is counted as a regular thread, hence the above configuration also
612
+ # allows for 3 regular threads and 7 IO threads for example.
613
+ def max_io_threads(max)
614
+ max = Integer(max)
615
+ if max < 0
616
+ raise "The maximum number of IO threads (#{max}) must be a positive number"
617
+ end
618
+
619
+ @options[:max_io_threads] = max
620
+ end
621
+
595
622
  # Instead of using +bind+ and manually constructing a URI like:
596
623
  #
597
624
  # bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
@@ -656,7 +683,8 @@ module Puma
656
683
  @options[:state] = path.to_s
657
684
  end
658
685
 
659
- # Use +permission+ to restrict permissions for the state file.
686
+ # Use +permission+ to restrict permissions for the state file. By convention,
687
+ # +permission+ is an octal number (e.g. `0640` or `0o640`).
660
688
  #
661
689
  # @example
662
690
  # state_permission 0600
@@ -665,21 +693,27 @@ module Puma
665
693
  @options[:state_permission] = permission
666
694
  end
667
695
 
668
- # How many worker processes to run. Typically this is set to
669
- # the number of available cores.
696
+ # How many worker processes to run. Typically this is set to the number of
697
+ # available cores.
670
698
  #
671
699
  # The default is the value of the environment variable +WEB_CONCURRENCY+ if
672
- # set, otherwise 0.
700
+ # set, otherwise 0. Passing +:auto+ will set the value to
701
+ # +Concurrent.available_processor_count+ (requires the concurrent-ruby gem).
702
+ # On some platforms (e.g. under CPU quotas) this may be fractional, and Puma
703
+ # will round down. If it rounds down to 0, Puma will run in single mode and
704
+ # cluster-only hooks like +before_worker_boot+ will not execute.
705
+ # If you rely on cluster-only hooks, set an explicit worker count.
673
706
  #
674
- # @note Cluster mode only.
707
+ # A value of 0 or nil means run in single mode.
675
708
  #
676
709
  # @example
677
710
  # workers 2
711
+ # workers :auto
678
712
  #
679
713
  # @see Puma::Cluster
680
714
  #
681
715
  def workers(count)
682
- @options[:workers] = count.to_i
716
+ @options[:workers] = count.nil? ? 0 : @config.send(:parse_workers, count)
683
717
  end
684
718
 
685
719
  # Disable warning message when running in cluster mode with a single worker.
@@ -717,6 +751,44 @@ module Puma
717
751
  @options[:silence_fork_callback_warning] = true
718
752
  end
719
753
 
754
+ # Code to run only in single mode.
755
+ # Runs after all config files are loaded.
756
+ #
757
+ # This can be called multiple times.
758
+ #
759
+ # @note Single mode only.
760
+ #
761
+ # @example
762
+ # single do
763
+ # silence_fork_callback_warning
764
+ # end
765
+ #
766
+ def single(&block)
767
+ raise ArgumentError, "A block must be provided to `single`" unless block
768
+
769
+ @options[:single] ||= []
770
+ @options[:single] << block
771
+ end
772
+
773
+ # Code to run only in cluster mode.
774
+ # Runs after all config files are loaded.
775
+ #
776
+ # This can be called multiple times.
777
+ #
778
+ # @note Cluster mode only.
779
+ #
780
+ # @example
781
+ # cluster do
782
+ # prune_bundler
783
+ # end
784
+ #
785
+ def cluster(&block)
786
+ raise ArgumentError, "A block must be provided to `cluster`" unless block
787
+
788
+ @options[:cluster] ||= []
789
+ @options[:cluster] << block
790
+ end
791
+
720
792
  # Code to run immediately before master process
721
793
  # forks workers (once on boot). These hooks can block if necessary
722
794
  # to wait for background operations unknown to Puma to finish before
@@ -818,6 +890,20 @@ module Puma
818
890
 
819
891
  alias_method :after_worker_boot, :after_worker_fork
820
892
 
893
+ # Code to run in the master right after a worker has stopped. The worker's
894
+ # index and Process::Status are passed as arguments.
895
+ #
896
+ # @note Cluster mode only.
897
+ #
898
+ # @example
899
+ # after_worker_shutdown do |worker_handle|
900
+ # puts 'Worker crashed' unless worker_handle.process_status.success?
901
+ # end
902
+ #
903
+ def after_worker_shutdown(&block)
904
+ process_hook :after_worker_shutdown, nil, block, cluster_only: true
905
+ end
906
+
821
907
  # Code to run after puma is booted (works for both single and cluster modes).
822
908
  #
823
909
  # @example
@@ -980,6 +1066,7 @@ module Puma
980
1066
  # The default is +true+ if your app uses more than 1 worker.
981
1067
  #
982
1068
  # @note Cluster mode only.
1069
+ # @note When using `fork_worker`, this only applies to worker 0.
983
1070
  #
984
1071
  # @example
985
1072
  # preload_app!
@@ -1015,6 +1102,7 @@ module Puma
1015
1102
  # new Bundler context and thus can float around as the release
1016
1103
  # dictates.
1017
1104
  #
1105
+ # @note Cluster mode only.
1018
1106
  # @note This is incompatible with +preload_app!+.
1019
1107
  # @note This is only supported for RubyGems 2.2+
1020
1108
  #
@@ -1120,7 +1208,7 @@ module Puma
1120
1208
 
1121
1209
  # Change the default worker timeout for booting.
1122
1210
  #
1123
- # The default is the value of `worker_timeout`.
1211
+ # The default is 60 seconds.
1124
1212
  #
1125
1213
  # @note Cluster mode only.
1126
1214
  #
@@ -1202,18 +1290,27 @@ module Puma
1202
1290
  # threads will be written to $stdout. This can help figure
1203
1291
  # out why shutdown is hanging.
1204
1292
  #
1205
- def shutdown_debug(val=true)
1206
- @options[:shutdown_debug] = val
1293
+ # If `on_force` is true, the backtraces will be written only
1294
+ # when the shutdown is forced i.e. not graceful.
1295
+ #
1296
+ # @see force_shutdown_after
1297
+ def shutdown_debug(val = true, on_force: false)
1298
+ @options[:shutdown_debug] = val && on_force ? :on_force : val
1207
1299
  end
1208
1300
 
1209
-
1210
- # Attempts to route traffic to less-busy workers by causing them to delay
1211
- # listening on the socket, allowing workers which are not processing any
1301
+ # Maximum delay of worker accept loop.
1302
+ #
1303
+ # Attempts to route traffic to less-busy workers by causing a busy worker to delay
1304
+ # listening on the socket, allowing workers which are not processing as many
1212
1305
  # requests to pick up new requests first.
1213
1306
  #
1214
1307
  # The default is 0.005 seconds.
1215
1308
  #
1216
- # Only works on MRI. For all other interpreters, this setting does nothing.
1309
+ # To turn off this feature, set the value to 0.
1310
+ #
1311
+ # @note Cluster mode with >= 2 workers only.
1312
+ #
1313
+ # @note Interpreters with forking support only.
1217
1314
  #
1218
1315
  # @see Puma::Server#handle_servers
1219
1316
  # @see Puma::ThreadPool#wait_for_less_busy_worker
@@ -1357,7 +1454,7 @@ module Puma
1357
1454
  #
1358
1455
  # The default is +:auto+.
1359
1456
  #
1360
- # @see https://github.com/socketry/nio4r/blob/master/lib/nio/selector.rb
1457
+ # @see https://github.com/socketry/nio4r/blob/main/lib/nio/selector.rb
1361
1458
  #
1362
1459
  def io_selector_backend(backend)
1363
1460
  @options[:io_selector_backend] = backend.to_sym
@@ -28,14 +28,12 @@ module Puma
28
28
 
29
29
  log '* Pruning Bundler environment'
30
30
  home = ENV['GEM_HOME']
31
- bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
32
- bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
31
+ original_bundle_env = Bundler.original_env.select { |k, v| k.start_with?('BUNDLE_') && v }
33
32
 
34
33
  with_unbundled_env do
35
34
  ENV['GEM_HOME'] = home
36
- ENV['BUNDLE_GEMFILE'] = bundle_gemfile
37
35
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
38
- ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
36
+ original_bundle_env.each { |k, v| ENV[k] = v }
39
37
  args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
40
38
  # Defaults to true which breaks socket activation
41
39
  args += [{:close_others => false}]
data/lib/puma/launcher.rb CHANGED
@@ -42,26 +42,40 @@ module Puma
42
42
  # end
43
43
  # Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
44
44
  def initialize(conf, launcher_args={})
45
- @runner = nil
46
- @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
47
- @events = launcher_args[:events] || Events.new
48
- @argv = launcher_args[:argv] || []
49
- @original_argv = @argv.dup
50
- @config = conf
51
-
52
- env = launcher_args.delete(:env) || ENV
45
+ ## Minimal initialization before potential early restart (e.g. from bundle pruning)
53
46
 
47
+ @config = conf
48
+ # Advertise the CLI Configuration before config files are loaded
49
+ Puma.cli_config = @config if defined?(Puma.cli_config)
54
50
  @config.clamp
51
+
55
52
  @options = @config.options
56
53
 
54
+ @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
55
+ @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
56
+ @log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
57
+ @log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
57
58
  @options[:log_writer] = @log_writer
58
59
  @options[:logger] = @log_writer if clustered?
59
60
 
60
- # Advertise the Configuration
61
- Puma.cli_config = @config if defined?(Puma.cli_config)
61
+ @events = launcher_args[:events] || Events.new
62
+
63
+ @argv = launcher_args[:argv] || []
64
+ @original_argv = @argv.dup
65
+
66
+ ## End minimal initialization
67
+
68
+ generate_restart_data
69
+ Dir.chdir(@restart_dir)
70
+
71
+ prune_bundler!
72
+
73
+ env = launcher_args.delete(:env) || ENV
74
+
75
+ # Log after prune_bundler! to avoid duplicate logging if a restart occurs
62
76
  log_config if env['PUMA_LOG_CONFIG']
63
77
 
64
- @binder = Binder.new(@log_writer, @options)
78
+ @binder = Binder.new(@log_writer, @options)
65
79
  @binder.create_inherited_fds(env).each { |k| env.delete k }
66
80
  @binder.create_activated_fds(env).each { |k| env.delete k }
67
81
 
@@ -81,21 +95,10 @@ module Puma
81
95
  )
82
96
  end
83
97
 
84
- @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
85
- @log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
86
-
87
- @log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
88
-
89
- generate_restart_data
90
-
91
98
  if clustered? && !Puma.forkable?
92
99
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
93
100
  end
94
101
 
95
- Dir.chdir(@restart_dir)
96
-
97
- prune_bundler!
98
-
99
102
  @environment = @options[:environment] if @options[:environment]
100
103
  set_rack_environment
101
104
 
@@ -139,7 +142,10 @@ module Puma
139
142
  # Delete the configured pidfile
140
143
  def delete_pidfile
141
144
  path = @options[:pidfile]
142
- File.unlink(path) if path && File.exist?(path)
145
+ begin
146
+ File.unlink(path) if path
147
+ rescue Errno::ENOENT
148
+ end
143
149
  end
144
150
 
145
151
  # Begin async shutdown of the server
@@ -381,9 +387,9 @@ module Puma
381
387
  # using it.
382
388
  @restart_dir = Dir.pwd
383
389
 
384
- # Use the same trick as unicorn, namely favor PWD because
385
- # it will contain an unresolved symlink, useful for when
386
- # the pwd is /data/releases/current.
390
+ # Use the same trick as unicorn, namely favor PWD because
391
+ # it will contain an unresolved symlink, useful for when
392
+ # the pwd is /data/releases/current.
387
393
  elsif dir = ENV['PWD']
388
394
  s_env = File.stat(dir)
389
395
  s_pwd = File.stat(Dir.pwd)
@@ -470,8 +476,8 @@ module Puma
470
476
  end
471
477
 
472
478
  begin
473
- unless Puma.jruby? # INFO in use by JVM already
474
- Signal.trap "SIGINFO" do
479
+ if Puma.backtrace_signal
480
+ Signal.trap Puma.backtrace_signal do
475
481
  thread_status do |name, backtrace|
476
482
  @log_writer.log(name)
477
483
  @log_writer.log(backtrace.map { |bt| " #{bt}" })
@@ -479,8 +485,7 @@ module Puma
479
485
  end
480
486
  end
481
487
  rescue Exception
482
- # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
483
- # to see this constantly on Linux.
488
+ log "*** SIGINFO/SIGPWR not implemented, signal based backtrace unavailable!"
484
489
  end
485
490
  end
486
491
 
@@ -90,8 +90,14 @@ module Puma
90
90
  @debug
91
91
  end
92
92
 
93
- def debug(str)
94
- log("% #{str}") if @debug
93
+ def debug(str=nil, &block)
94
+ if @debug
95
+ if block_given?
96
+ log("% #{yield}")
97
+ else
98
+ log("% #{str}")
99
+ end
100
+ end
95
101
  end
96
102
 
97
103
  # Write +str+ to +@stderr+
data/lib/puma/reactor.rb CHANGED
@@ -75,15 +75,12 @@ module Puma
75
75
  private
76
76
 
77
77
  def select_loop
78
- close_selector = true
79
78
  begin
80
79
  until @input.closed? && @input.empty?
81
80
  # Wakeup any registered object that receives incoming data.
82
81
  # Block until the earliest timeout or Selector#wakeup is called.
83
82
  timeout = (earliest = @timeouts.first) && earliest.timeout
84
- monitor_wake_up = false
85
83
  @selector.select(timeout) do |monitor|
86
- monitor_wake_up = true
87
84
  wakeup!(monitor.value)
88
85
  end
89
86
 
@@ -103,18 +100,12 @@ module Puma
103
100
  STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
104
101
  STDERR.puts e.backtrace
105
102
 
106
- # NoMethodError may be rarely raised when calling @selector.select, which
107
- # is odd. Regardless, it may continue for thousands of calls if retried.
108
- # Also, when it raises, @selector.close also raises an error.
109
- if !monitor_wake_up && NoMethodError === e
110
- close_selector = false
111
- else
112
- retry
113
- end
103
+ retry
114
104
  end
105
+
115
106
  # Wakeup all remaining objects on shutdown.
116
107
  @timeouts.each(&@block)
117
- @selector.close if close_selector
108
+ @selector.close
118
109
  end
119
110
 
120
111
  # Start monitoring the object.