puma 8.0.3-java → 8.1.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +115 -0
  3. data/README.md +20 -5
  4. data/docs/6.0-Upgrade.md +1 -1
  5. data/docs/8.0-Upgrade.md +2 -2
  6. data/docs/deployment.md +3 -3
  7. data/docs/fork_worker.md +34 -4
  8. data/docs/kubernetes.md +3 -3
  9. data/docs/plugins.md +2 -2
  10. data/docs/puma_support.md +78 -0
  11. data/docs/rails_dev_mode.md +2 -2
  12. data/docs/restart.md +4 -3
  13. data/docs/signals.md +2 -2
  14. data/docs/stats.md +5 -5
  15. data/docs/systemd.md +4 -2
  16. data/docs/testing_benchmarks_local_files.md +1 -1
  17. data/docs/testing_test_rackup_ci_files.md +2 -2
  18. data/ext/puma_http11/extconf.rb +2 -0
  19. data/ext/puma_http11/http11_parser.c +6 -2
  20. data/ext/puma_http11/http11_parser_common.rl +4 -1
  21. data/ext/puma_http11/mini_ssl.c +8 -4
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +36 -36
  23. data/ext/puma_http11/puma_http11.c +24 -0
  24. data/lib/puma/binder.rb +29 -0
  25. data/lib/puma/cli.rb +6 -0
  26. data/lib/puma/client.rb +75 -29
  27. data/lib/puma/client_env.rb +32 -18
  28. data/lib/puma/cluster/worker.rb +109 -33
  29. data/lib/puma/cluster/worker_handle.rb +16 -0
  30. data/lib/puma/cluster.rb +173 -44
  31. data/lib/puma/commonlogger.rb +3 -3
  32. data/lib/puma/configuration.rb +22 -3
  33. data/lib/puma/const.rb +11 -9
  34. data/lib/puma/dsl.rb +265 -91
  35. data/lib/puma/launcher.rb +6 -4
  36. data/lib/puma/minissl.rb +1 -1
  37. data/lib/puma/plugin/systemd.rb +1 -1
  38. data/lib/puma/puma_http11.jar +0 -0
  39. data/lib/puma/response.rb +13 -5
  40. data/lib/puma/runner.rb +5 -1
  41. data/lib/puma/sd_notify.rb +4 -1
  42. data/lib/puma/server.rb +45 -20
  43. data/lib/puma/thread_pool.rb +12 -1
  44. data/lib/puma.rb +1 -1
  45. metadata +3 -2
data/lib/puma/launcher.rb CHANGED
@@ -78,6 +78,7 @@ module Puma
78
78
  @binder = Binder.new(@log_writer, @options)
79
79
  @binder.create_inherited_fds(env).each { |k| env.delete k }
80
80
  @binder.create_activated_fds(env).each { |k| env.delete k }
81
+ @control_activated_socket = @binder.take_activated_socket(@options[:control_url]) if @options[:control_url]
81
82
 
82
83
  @environment = @config.environment
83
84
 
@@ -112,7 +113,7 @@ module Puma
112
113
  @status = :run
113
114
  end
114
115
 
115
- attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
116
+ attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir, :control_activated_socket
116
117
 
117
118
  # Return stats about the server
118
119
  def stats
@@ -187,6 +188,9 @@ module Puma
187
188
  if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
188
189
  @runner.fork_worker!
189
190
  true
191
+ elsif clustered? && @runner.respond_to?(:mold_worker!) && @options[:mold_worker]
192
+ @runner.mold_worker!
193
+ true
190
194
  else
191
195
  log "* refork called but not available."
192
196
  false
@@ -197,8 +201,6 @@ module Puma
197
201
  def run
198
202
  previous_env = get_env
199
203
 
200
- @config.clamp
201
-
202
204
  @config.plugins.fire_starts self
203
205
 
204
206
  setup_signals
@@ -282,8 +284,8 @@ module Puma
282
284
  end
283
285
 
284
286
  def do_graceful_stop
285
- @events.fire_after_stopped!
286
287
  @runner.stop_blocked
288
+ @events.fire_after_stopped!
287
289
  end
288
290
 
289
291
  def do_restart(previous_env)
data/lib/puma/minissl.rb CHANGED
@@ -97,7 +97,7 @@ module Puma
97
97
  # It would make more sense to let @socket.read_nonblock raise
98
98
  # EAGAIN if necessary but it seems like it'll misbehave on Windows.
99
99
  # I don't have a Windows machine to debug this so I can't explain
100
- # exactly whats happening in that OS. Please let me know if you
100
+ # exactly what's happening in that OS. Please let me know if you
101
101
  # find out!
102
102
  #
103
103
  # In the meantime, we can emulate the correct behavior by
@@ -5,7 +5,7 @@ require_relative '../plugin'
5
5
  # Puma's systemd integration allows Puma to inform systemd:
6
6
  # 1. when it has successfully started
7
7
  # 2. when it is starting shutdown
8
- # 3. periodically for a liveness check with a watchdog thread
8
+ # 3. at startup and then periodically for a liveness check with a watchdog thread
9
9
  # 4. periodically set the status
10
10
  Puma::Plugin.create do
11
11
  def start(launcher)
Binary file
data/lib/puma/response.rb CHANGED
@@ -75,6 +75,7 @@ module Puma
75
75
  end
76
76
 
77
77
  env["puma.mark_as_io_bound"] = -> { processor.mark_as_io_thread! }
78
+ env["puma.connection_closed?"] = -> { closed_socket?(socket) }
78
79
 
79
80
  begin
80
81
  status, headers, app_body = @thread_pool.with_force_shutdown do
@@ -165,7 +166,7 @@ module Puma
165
166
  if res_body.respond_to?(:each) && !resp_info[:response_hijack]
166
167
  # below converts app_body into body, dependent on app_body's characteristics, and
167
168
  # content_length will be set if it can be determined
168
- if !content_length && !resp_info[:transfer_encoding] && status != 204
169
+ if !content_length && !resp_info[:transfer_encoding] && status != 204 && status != 304
169
170
  if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
170
171
  array_body.is_a?(Array)
171
172
  body = array_body.compact
@@ -176,16 +177,16 @@ module Puma
176
177
  elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
177
178
  File.readable?(fn)
178
179
  body = File.open fn, 'rb'
179
- content_length = body.size
180
180
  close_body = true
181
+ content_length = body.size
181
182
  else
182
183
  body = res_body
183
184
  end
184
185
  elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
185
186
  (fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
186
187
  body = File.open fn, 'rb'
187
- content_length = body.size
188
188
  close_body = true
189
+ content_length = body.size
189
190
  elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
190
191
  res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
191
192
  # Sprockets::Asset
@@ -246,10 +247,11 @@ module Puma
246
247
  end
247
248
 
248
249
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
249
- body.close if close_body
250
250
 
251
251
  # if we're shutting down, close keep_alive connections
252
252
  !shutting_down? && keep_alive ? :keep_alive : :close
253
+ ensure
254
+ body.close if close_body
253
255
  end
254
256
 
255
257
  def write_pending_interim(socket, client)
@@ -430,6 +432,12 @@ module Puma
430
432
  end
431
433
  private :fetch_status_code
432
434
 
435
+ def request_has_close?(env)
436
+ conn = env.fetch(HTTP_CONNECTION, "").downcase
437
+ conn.include?(CLOSE) && conn.split(",").any? { |option| option.strip == CLOSE }
438
+ end
439
+ private :request_has_close?
440
+
433
441
  # Processes and write headers to the IOBuffer.
434
442
  # @param env [Hash] see Puma::Client#env, from request
435
443
  # @param status [Integer] the status returned by the Rack application
@@ -453,7 +461,7 @@ module Puma
453
461
  http_11 = env[SERVER_PROTOCOL] == HTTP_11
454
462
  if http_11
455
463
  resp_info[:allow_chunked] = true
456
- resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
464
+ resp_info[:keep_alive] = !request_has_close?(env)
457
465
 
458
466
  # An optimization. The most common response is 200, so we can
459
467
  # reply with the proper 200 status without having to compute
data/lib/puma/runner.rb CHANGED
@@ -30,7 +30,7 @@ module Puma
30
30
  def wakeup!
31
31
  return unless @wakeup
32
32
 
33
- @wakeup.write PIPE_WAKEUP unless @wakeup.closed?
33
+ @wakeup.write_nonblock(PIPE_WAKEUP) unless @wakeup.closed?
34
34
 
35
35
  rescue SystemCallError, IOError
36
36
  end
@@ -77,6 +77,10 @@ module Puma
77
77
  control = Puma::Server.new app, nil,
78
78
  { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
79
79
 
80
+ if (activated = @launcher.control_activated_socket)
81
+ control.binder.activated_sockets.store(*activated)
82
+ end
83
+
80
84
  begin
81
85
  control.binder.parse [str], nil, 'Starting control server'
82
86
  rescue Errno::EADDRINUSE, Errno::EACCES => e
@@ -49,8 +49,11 @@ module Puma
49
49
  WATCHDOG = "WATCHDOG=1"
50
50
  FDSTORE = "FDSTORE=1"
51
51
 
52
+ # @note When the service manager expects watchdog pings, this sends an
53
+ # immediate `WATCHDOG=1` in the same datagram, which closes the watchdog
54
+ # timeout window between startup and the first periodic watchdog tick.
52
55
  def self.ready(unset_env=false)
53
- notify(READY, unset_env)
56
+ notify(watchdog? ? "#{READY}\n#{WATCHDOG}" : READY, unset_env)
54
57
  end
55
58
 
56
59
  def self.reloading(unset_env=false)
data/lib/puma/server.rb CHANGED
@@ -109,6 +109,8 @@ module Puma
109
109
  @enable_keep_alives &&= @queue_requests
110
110
  @io_selector_backend = @options[:io_selector_backend]
111
111
  @http_content_length_limit = @options[:http_content_length_limit]
112
+ @continue_callback = @options[:continue_callback]
113
+ @allow_underscore_headers = @options.fetch(:allow_underscore_headers, true)
112
114
  @cluster_accept_loop_delay = ClusterAcceptLoopDelay.new(
113
115
  workers: @options[:workers],
114
116
  max_delay: @options[:wait_for_less_busy_worker] || 0 # Real default is in Configuration::DEFAULTS, this is for unit testing
@@ -412,13 +414,21 @@ module Puma
412
414
  # clients until the code is finished.
413
415
  pool.wait_while_out_of_band_running
414
416
 
415
- # A well rested herd (cluster) runs faster
416
- if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
417
- delay = @cluster_accept_loop_delay.calculate(
418
- max_threads: @max_threads,
419
- busy_threads_plus_todo: busy_threads_plus_todo
420
- )
421
- sleep(delay)
417
+ unless shutting_down?
418
+ if @queue_requests
419
+ if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
420
+ delay = @cluster_accept_loop_delay.calculate(
421
+ max_threads: @max_threads,
422
+ busy_threads_plus_todo: busy_threads_plus_todo
423
+ )
424
+ sleep(delay)
425
+ end
426
+ else
427
+ pool.wait_until_not_full
428
+
429
+ # A stop may have arrived while waiting for a thread
430
+ break if check.wait_readable(0) && handle_check
431
+ end
422
432
  end
423
433
 
424
434
  io = begin
@@ -486,12 +496,14 @@ module Puma
486
496
  client.env_set_http_version = @env_set_http_version
487
497
  client.http_content_length_limit = @http_content_length_limit
488
498
  client.supported_http_methods = @supported_http_methods
499
+ client.allow_underscore_headers = @allow_underscore_headers
500
+ client.continue_callback = @continue_callback
489
501
  client
490
502
  end
491
503
 
492
504
  # :nodoc:
493
505
  def handle_check
494
- cmd = @check.read(1)
506
+ cmd = @check.sysread 1
495
507
 
496
508
  case cmd
497
509
  when STOP_COMMAND
@@ -568,8 +580,12 @@ module Puma
568
580
  if @thread_pool.waiting > 0
569
581
  can_loop = true
570
582
  else
571
- @thread_pool << client
572
- close_socket = false
583
+ @thread_pool.with_mutex do
584
+ unless shutting_down?
585
+ @thread_pool << client
586
+ close_socket = false
587
+ end
588
+ end
573
589
  end
574
590
  elsif @queue_requests
575
591
  client.set_timeout @persistent_timeout
@@ -646,6 +662,13 @@ module Puma
646
662
  when HttpParserError501
647
663
  response_to_error(client, requests, e, 501)
648
664
  @log_writer.parse_error e, client
665
+ when ContinueRejected
666
+ status, headers, res_body = e.response
667
+ begin
668
+ prepare_response(status, headers, res_body, requests, client)
669
+ ensure
670
+ res_body.close if res_body.respond_to? :close
671
+ end
649
672
  else
650
673
  response_to_error(client, requests, e, 500)
651
674
  @log_writer.unknown_error e, nil, "Read"
@@ -708,16 +731,18 @@ module Puma
708
731
  end
709
732
 
710
733
  def notify_safely(message)
711
- @notify << message
712
- rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
713
- # The server, in another thread, is shutting down
714
- rescue RuntimeError => e
715
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
716
- if e.message.include?('IOError')
717
- # ignore
718
- else
719
- raise e
720
- end
734
+ # Use write_nonblock rather than `<<` or `syswrite`. This method is
735
+ # called from signal handlers (TERM/USR2/INT/HUP, and the cluster
736
+ # worker's SIGTERM trap). On Ruby < 4.0, a blocking write from trap
737
+ # context that is still in flight when handle_servers' ensure block
738
+ # runs IO#close leaves the closing thread parked in IO#close forever
739
+ # (puma#3677). write_nonblock is a single write(2) that never waits,
740
+ # so IO#close has nothing to wait for. If the pipe is full a command
741
+ # is already pending, so dropping this one is harmless.
742
+ notify = @notify
743
+ notify.write_nonblock(message, exception: false) if notify && !notify.closed?
744
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF => e
745
+ @log_writer.unknown_error(e, nil, "notify_safely error") if Puma::IS_MRI && RUBY_VERSION > '3.3'
721
746
  end
722
747
  private :notify_safely
723
748
 
@@ -7,7 +7,6 @@ require_relative 'server_plugin_control'
7
7
 
8
8
  module Puma
9
9
 
10
- # Add `Thread#puma_server` and `Thread#puma_server=`
11
10
  Thread.attr_accessor(:puma_server)
12
11
 
13
12
  # Internal Docs for A simple thread pool management object.
@@ -211,6 +210,7 @@ module Puma
211
210
  processor.marked_as_io_thread = false
212
211
  else
213
212
  # We're already at max threads, so we exit the extra io thread.
213
+ @spawned -= 1
214
214
  @processors.delete(processor)
215
215
  trigger_before_thread_exit_hooks
216
216
  Thread.exit
@@ -314,6 +314,17 @@ module Puma
314
314
  end
315
315
  end
316
316
 
317
+ def wait_until_not_full
318
+ with_mutex do
319
+ while true
320
+ return if @shutdown
321
+ return if (@spawned - @waiting + @todo.size) < @max
322
+
323
+ @not_full.wait @mutex
324
+ end
325
+ end
326
+ end
327
+
317
328
  # @version 5.0.0
318
329
  def with_mutex(&block)
319
330
  @mutex.owned? ?
data/lib/puma.rb CHANGED
@@ -82,7 +82,7 @@ module Puma
82
82
  #
83
83
  def self.deprecate_method_change(method_old, method_caller, method_new)
84
84
  if method_old == method_caller
85
- warn "Use '#{method_new}', '#{method_caller}' is deprecated and will be removed in v8"
85
+ warn "Use '#{method_new}', '#{method_caller}' is deprecated and will be removed in Puma v9"
86
86
  end
87
87
  end
88
88
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.3
4
+ version: 8.1.0
5
5
  platform: java
6
6
  authors:
7
7
  - Evan Phoenix
@@ -65,6 +65,7 @@ files:
65
65
  - docs/kubernetes.md
66
66
  - docs/nginx.md
67
67
  - docs/plugins.md
68
+ - docs/puma_support.md
68
69
  - docs/rails_dev_mode.md
69
70
  - docs/restart.md
70
71
  - docs/signals.md
@@ -157,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
158
  - !ruby/object:Gem::Version
158
159
  version: '0'
159
160
  requirements: []
160
- rubygems_version: 4.0.23
161
+ rubygems_version: 4.0.21
161
162
  specification_version: 4
162
163
  summary: A Ruby/Rack web server built for parallelism.
163
164
  test_files: []