puma 6.6.0 → 7.0.4

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 +147 -5
  3. data/README.md +18 -31
  4. data/docs/fork_worker.md +5 -5
  5. data/docs/kubernetes.md +6 -4
  6. data/docs/restart.md +2 -2
  7. data/docs/signals.md +11 -11
  8. data/docs/stats.md +2 -1
  9. data/docs/systemd.md +1 -1
  10. data/ext/puma_http11/extconf.rb +2 -17
  11. data/ext/puma_http11/mini_ssl.c +18 -8
  12. data/ext/puma_http11/org/jruby/puma/Http11.java +10 -2
  13. data/ext/puma_http11/puma_http11.c +23 -11
  14. data/lib/puma/binder.rb +10 -8
  15. data/lib/puma/cli.rb +3 -5
  16. data/lib/puma/client.rb +74 -36
  17. data/lib/puma/cluster/worker.rb +9 -10
  18. data/lib/puma/cluster/worker_handle.rb +36 -5
  19. data/lib/puma/cluster.rb +35 -22
  20. data/lib/puma/cluster_accept_loop_delay.rb +92 -0
  21. data/lib/puma/commonlogger.rb +3 -3
  22. data/lib/puma/configuration.rb +88 -43
  23. data/lib/puma/const.rb +9 -10
  24. data/lib/puma/control_cli.rb +6 -2
  25. data/lib/puma/detect.rb +2 -0
  26. data/lib/puma/dsl.rb +110 -90
  27. data/lib/puma/error_logger.rb +3 -1
  28. data/lib/puma/events.rb +25 -10
  29. data/lib/puma/io_buffer.rb +8 -4
  30. data/lib/puma/launcher/bundle_pruner.rb +1 -1
  31. data/lib/puma/launcher.rb +28 -29
  32. data/lib/puma/minissl.rb +0 -1
  33. data/lib/puma/plugin/systemd.rb +3 -3
  34. data/lib/puma/rack/urlmap.rb +1 -1
  35. data/lib/puma/reactor.rb +19 -4
  36. data/lib/puma/request.rb +45 -32
  37. data/lib/puma/runner.rb +8 -17
  38. data/lib/puma/server.rb +114 -68
  39. data/lib/puma/single.rb +5 -2
  40. data/lib/puma/thread_pool.rb +37 -81
  41. data/lib/puma/util.rb +0 -7
  42. data/lib/puma.rb +10 -0
  43. data/lib/rack/handler/puma.rb +2 -2
  44. data/tools/Dockerfile +3 -1
  45. metadata +5 -4
@@ -70,7 +70,7 @@ module Puma::Rack
70
70
  return app.call(env)
71
71
  end
72
72
 
73
- [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
73
+ [404, {'content-type' => "text/plain", "x-cascade" => "pass"}, ["Not Found: #{path}"]]
74
74
 
75
75
  ensure
76
76
  env['PATH_INFO'] = path
data/lib/puma/reactor.rb CHANGED
@@ -15,6 +15,12 @@ module Puma
15
15
  #
16
16
  # The implementation uses a Queue to synchronize adding new objects from the internal select loop.
17
17
  class Reactor
18
+
19
+ # @!attribute [rw] reactor_max
20
+ # Maximum number of clients in the selector. Reset with calls to `Server.stats`.
21
+ attr_accessor :reactor_max
22
+ attr_reader :reactor_size
23
+
18
24
  # Create a new Reactor to monitor IO objects added by #add.
19
25
  # The provided block will be invoked when an IO has data available to read,
20
26
  # its timeout elapses, or when the Reactor shuts down.
@@ -29,6 +35,8 @@ module Puma
29
35
  @input = Queue.new
30
36
  @timeouts = []
31
37
  @block = block
38
+ @reactor_size = 0
39
+ @reactor_max = 0
32
40
  end
33
41
 
34
42
  # Run the internal select loop, using a background thread by default.
@@ -73,11 +81,15 @@ module Puma
73
81
  # Wakeup any registered object that receives incoming data.
74
82
  # Block until the earliest timeout or Selector#wakeup is called.
75
83
  timeout = (earliest = @timeouts.first) && earliest.timeout
76
- @selector.select(timeout) {|mon| wakeup!(mon.value)}
84
+ monitor_wake_up = false
85
+ @selector.select(timeout) do |monitor|
86
+ monitor_wake_up = true
87
+ wakeup!(monitor.value)
88
+ end
77
89
 
78
90
  # Wakeup all objects that timed out.
79
- timed_out = @timeouts.take_while {|t| t.timeout == 0}
80
- timed_out.each { |c| wakeup! c }
91
+ timed_out = @timeouts.take_while { |client| client.timeout == 0 }
92
+ timed_out.each { |client| wakeup!(client) }
81
93
 
82
94
  unless @input.empty?
83
95
  until @input.empty?
@@ -94,7 +106,7 @@ module Puma
94
106
  # NoMethodError may be rarely raised when calling @selector.select, which
95
107
  # is odd. Regardless, it may continue for thousands of calls if retried.
96
108
  # Also, when it raises, @selector.close also raises an error.
97
- if NoMethodError === e
109
+ if !monitor_wake_up && NoMethodError === e
98
110
  close_selector = false
99
111
  else
100
112
  retry
@@ -108,6 +120,8 @@ module Puma
108
120
  # Start monitoring the object.
109
121
  def register(client)
110
122
  @selector.register(client.to_io, :r).value = client
123
+ @reactor_size += 1
124
+ @reactor_max = @reactor_size if @reactor_max < @reactor_size
111
125
  @timeouts << client
112
126
  rescue ArgumentError
113
127
  # unreadable clients raise error when processed by NIO
@@ -118,6 +132,7 @@ module Puma
118
132
  def wakeup!(client)
119
133
  if @block.call client
120
134
  @selector.deregister client.to_io
135
+ @reactor_size -= 1
121
136
  @timeouts.delete client
122
137
  end
123
138
  end
data/lib/puma/request.rb CHANGED
@@ -52,6 +52,7 @@ module Puma
52
52
  io_buffer = client.io_buffer
53
53
  socket = client.io # io may be a MiniSSL::Socket
54
54
  app_body = nil
55
+ error = nil
55
56
 
56
57
  return false if closed_socket?(socket)
57
58
 
@@ -68,7 +69,7 @@ module Puma
68
69
  end
69
70
 
70
71
  env[HIJACK_P] = true
71
- env[HIJACK] = client
72
+ env[HIJACK] = client.method :full_hijack
72
73
 
73
74
  env[RACK_INPUT] = client.body
74
75
  env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
@@ -92,6 +93,7 @@ module Puma
92
93
  # array, we will invoke them when the request is done.
93
94
  #
94
95
  env[RACK_AFTER_REPLY] ||= []
96
+ env[RACK_RESPONSE_FINISHED] ||= []
95
97
 
96
98
  begin
97
99
  if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
@@ -119,15 +121,15 @@ module Puma
119
121
 
120
122
  return :async
121
123
  end
122
- rescue ThreadPool::ForceShutdown => e
123
- @log_writer.unknown_error e, client, "Rack app"
124
+ rescue ThreadPool::ForceShutdown => error
125
+ @log_writer.unknown_error error, client, "Rack app"
124
126
  @log_writer.log "Detected force shutdown of a thread"
125
127
 
126
- status, headers, res_body = lowlevel_error(e, env, 503)
127
- rescue Exception => e
128
- @log_writer.unknown_error e, client, "Rack app"
128
+ status, headers, res_body = lowlevel_error(error, env, 503)
129
+ rescue Exception => error
130
+ @log_writer.unknown_error error, client, "Rack app"
129
131
 
130
- status, headers, res_body = lowlevel_error(e, env, 500)
132
+ status, headers, res_body = lowlevel_error(error, env, 500)
131
133
  end
132
134
  prepare_response(status, headers, res_body, requests, client)
133
135
  ensure
@@ -135,12 +137,25 @@ module Puma
135
137
  uncork_socket client.io
136
138
  app_body.close if app_body.respond_to? :close
137
139
  client&.tempfile_close
138
- after_reply = env[RACK_AFTER_REPLY] || []
139
- begin
140
- after_reply.each { |o| o.call }
141
- rescue StandardError => e
142
- @log_writer.debug_error e
143
- end unless after_reply.empty?
140
+ if after_reply = env[RACK_AFTER_REPLY]
141
+ after_reply.each do |o|
142
+ begin
143
+ o.call
144
+ rescue StandardError => e
145
+ @log_writer.debug_error e
146
+ end
147
+ end
148
+ end
149
+
150
+ if response_finished = env[RACK_RESPONSE_FINISHED]
151
+ response_finished.reverse_each do |o|
152
+ begin
153
+ o.call(env, status, headers, error)
154
+ rescue StandardError => e
155
+ @log_writer.debug_error e
156
+ end
157
+ end
158
+ end
144
159
  end
145
160
 
146
161
  # Assembles the headers and prepares the body for actually sending the
@@ -161,17 +176,7 @@ module Puma
161
176
  return false if closed_socket?(socket)
162
177
 
163
178
  # Close the connection after a reasonable number of inline requests
164
- # if the server is at capacity and the listener has a new connection ready.
165
- # This allows Puma to service connections fairly when the number
166
- # of concurrent connections exceeds the size of the threadpool.
167
- force_keep_alive = if @enable_keep_alives
168
- requests < @max_fast_inline ||
169
- @thread_pool.busy_threads < @max_threads ||
170
- !client.listener.to_io.wait_readable(0)
171
- else
172
- # Always set force_keep_alive to false if the server has keep-alives not enabled.
173
- false
174
- end
179
+ force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
175
180
 
176
181
  resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
177
182
 
@@ -191,7 +196,8 @@ module Puma
191
196
  elsif res_body.is_a?(File) && res_body.respond_to?(:size)
192
197
  body = res_body
193
198
  content_length = body.size
194
- elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
199
+ elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
200
+ File.readable?(fn)
195
201
  body = File.open fn, 'rb'
196
202
  content_length = body.size
197
203
  close_body = true
@@ -199,7 +205,7 @@ module Puma
199
205
  body = res_body
200
206
  end
201
207
  elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
202
- File.readable?(fn = res_body.to_path)
208
+ (fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
203
209
  body = File.open fn, 'rb'
204
210
  content_length = body.size
205
211
  close_body = true
@@ -263,14 +269,21 @@ module Puma
263
269
 
264
270
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
265
271
  body.close if close_body
266
- keep_alive
272
+ # if we're shutting down, close keep_alive connections
273
+ !shutting_down? && keep_alive
267
274
  end
268
275
 
276
+ HTTP_ON_VALUES = { "on" => true, HTTPS => true }
277
+ private_constant :HTTP_ON_VALUES
278
+
269
279
  # @param env [Hash] see Puma::Client#env, from request
270
280
  # @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
271
281
  #
272
282
  def default_server_port(env)
273
- if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
283
+ if HTTP_ON_VALUES[env[HTTPS_KEY]] ||
284
+ env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
285
+ env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
286
+ env[HTTP_X_FORWARDED_SSL] == "on"
274
287
  PORT_443
275
288
  else
276
289
  PORT_80
@@ -474,7 +487,7 @@ module Puma
474
487
 
475
488
  # The legacy HTTP_VERSION header can be sent as a client header.
476
489
  # Rack v4 may remove using HTTP_VERSION. If so, remove this line.
477
- env[HTTP_VERSION] = env[SERVER_PROTOCOL]
490
+ env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
478
491
  end
479
492
  private :normalize_env
480
493
 
@@ -581,7 +594,7 @@ module Puma
581
594
  # response body
582
595
  # @param io_buffer [Puma::IOBuffer] modified inn place
583
596
  # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
584
- # status and `@max_fast_inline`
597
+ # status and `@max_keep_alive`
585
598
  # @return [Hash] resp_info
586
599
  # @version 5.0.3
587
600
  #
@@ -664,10 +677,10 @@ module Puma
664
677
  if ary
665
678
  ary.each do |v|
666
679
  next if illegal_header_value?(v)
667
- io_buffer.append k, colon, v, line_ending
680
+ io_buffer.append k.downcase, colon, v, line_ending
668
681
  end
669
682
  else
670
- io_buffer.append k, colon, line_ending
683
+ io_buffer.append k.downcase, colon, line_ending
671
684
  end
672
685
  end
673
686
 
data/lib/puma/runner.rb CHANGED
@@ -33,7 +33,6 @@ module Puma
33
33
  @wakeup.write PIPE_WAKEUP unless @wakeup.closed?
34
34
 
35
35
  rescue SystemCallError, IOError
36
- Puma::Util.purge_interrupt_queue
37
36
  end
38
37
 
39
38
  def development?
@@ -93,22 +92,6 @@ module Puma
93
92
  @control.binder.close_listeners if @control
94
93
  end
95
94
 
96
- # @!attribute [r] ruby_engine
97
- # @deprecated Use `RUBY_DESCRIPTION` instead
98
- def ruby_engine
99
- warn "Puma::Runner#ruby_engine is deprecated; use RUBY_DESCRIPTION instead. It will be removed in puma v7."
100
-
101
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
102
- "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
103
- else
104
- if defined?(RUBY_ENGINE_VERSION)
105
- "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
106
- else
107
- "#{RUBY_ENGINE} #{RUBY_VERSION}"
108
- end
109
- end
110
- end
111
-
112
95
  def output_header(mode)
113
96
  min_t = @options[:min_threads]
114
97
  max_t = @options[:max_threads]
@@ -128,6 +111,14 @@ module Puma
128
111
  end
129
112
  end
130
113
 
114
+ def warn_ruby_mn_threads
115
+ return if !ENV.key?('RUBY_MN_THREADS')
116
+
117
+ log "! WARNING: Detected `RUBY_MN_THREADS=#{ENV['RUBY_MN_THREADS']}`"
118
+ log "! This setting is known to cause performance regressions with Puma."
119
+ log "! Consider disabling this environment variable: https://github.com/puma/puma/issues/3720"
120
+ end
121
+
131
122
  def redirected_io?
132
123
  @options[:redirect_stdout] || @options[:redirect_stderr]
133
124
  end
data/lib/puma/server.rb CHANGED
@@ -12,14 +12,15 @@ require_relative 'client'
12
12
  require_relative 'binder'
13
13
  require_relative 'util'
14
14
  require_relative 'request'
15
+ require_relative 'configuration'
16
+ require_relative 'cluster_accept_loop_delay'
15
17
 
16
18
  require 'socket'
17
19
  require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
20
 
19
21
  module Puma
20
-
21
- # This method was private on Ruby 2.4 but became public on Ruby 2.5+:
22
- Thread.send(:attr_accessor, :puma_server)
22
+ # Add `Thread#puma_server` and `Thread#puma_server=`
23
+ Thread.attr_accessor(:puma_server)
23
24
 
24
25
  # The HTTP Server itself. Serves out a single Rack app.
25
26
  #
@@ -32,6 +33,14 @@ module Puma
32
33
  #
33
34
  # Each `Puma::Server` will have one reactor and one thread pool.
34
35
  class Server
36
+ module FiberPerRequest
37
+ def handle_request(client, requests)
38
+ Fiber.new do
39
+ super
40
+ end.resume
41
+ end
42
+ end
43
+
35
44
  include Puma::Const
36
45
  include Request
37
46
 
@@ -50,7 +59,6 @@ module Puma
50
59
  attr_accessor :app
51
60
  attr_accessor :binder
52
61
 
53
-
54
62
  # Create a server for the rack app +app+.
55
63
  #
56
64
  # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
@@ -77,6 +85,9 @@ module Puma
77
85
 
78
86
  @thread = nil
79
87
  @thread_pool = nil
88
+ @reactor = nil
89
+
90
+ @env_set_http_version = nil
80
91
 
81
92
  @options = if options.is_a?(UserFileDefaultOptions)
82
93
  options
@@ -94,10 +105,19 @@ module Puma
94
105
  @min_threads = @options[:min_threads]
95
106
  @max_threads = @options[:max_threads]
96
107
  @queue_requests = @options[:queue_requests]
97
- @max_fast_inline = @options[:max_fast_inline]
108
+ @max_keep_alive = @options[:max_keep_alive]
98
109
  @enable_keep_alives = @options[:enable_keep_alives]
110
+ @enable_keep_alives &&= @queue_requests
99
111
  @io_selector_backend = @options[:io_selector_backend]
100
112
  @http_content_length_limit = @options[:http_content_length_limit]
113
+ @cluster_accept_loop_delay = ClusterAcceptLoopDelay.new(
114
+ workers: @options[:workers],
115
+ max_delay: @options[:wait_for_less_busy_worker] || 0 # Real default is in Configuration::DEFAULTS, this is for unit testing
116
+ )
117
+
118
+ if @options[:fiber_per_request]
119
+ singleton_class.prepend(FiberPerRequest)
120
+ end
101
121
 
102
122
  # make this a hash, since we prefer `key?` over `include?`
103
123
  @supported_http_methods =
@@ -114,7 +134,7 @@ module Puma
114
134
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
115
135
  @leak_stack_on_error = @options[:environment] ? temp : true
116
136
 
117
- @binder = Binder.new(log_writer)
137
+ @binder = Binder.new(log_writer, @options)
118
138
 
119
139
  ENV['RACK_ENV'] ||= "development"
120
140
 
@@ -165,7 +185,6 @@ module Puma
165
185
  begin
166
186
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
167
187
  rescue IOError, SystemCallError
168
- Puma::Util.purge_interrupt_queue
169
188
  end
170
189
  end
171
190
 
@@ -174,7 +193,6 @@ module Puma
174
193
  begin
175
194
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
176
195
  rescue IOError, SystemCallError
177
- Puma::Util.purge_interrupt_queue
178
196
  end
179
197
  end
180
198
  else
@@ -195,7 +213,6 @@ module Puma
195
213
  begin
196
214
  tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
197
215
  rescue IOError, SystemCallError
198
- Puma::Util.purge_interrupt_queue
199
216
  @precheck_closing = false
200
217
  false
201
218
  else
@@ -220,7 +237,6 @@ module Puma
220
237
  @thread_pool&.spawned
221
238
  end
222
239
 
223
-
224
240
  # This number represents the number of requests that
225
241
  # the server is capable of taking right now.
226
242
  #
@@ -233,11 +249,6 @@ module Puma
233
249
  @thread_pool&.pool_capacity
234
250
  end
235
251
 
236
- # @!attribute [r] busy_threads
237
- def busy_threads
238
- @thread_pool&.busy_threads
239
- end
240
-
241
252
  # Runs the server.
242
253
  #
243
254
  # If +background+ is true (the default) then a thread is spun
@@ -254,7 +265,11 @@ module Puma
254
265
  @thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
255
266
 
256
267
  if @queue_requests
257
- @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
268
+ @reactor = Reactor.new(@io_selector_backend) { |c|
269
+ # Inversion of control, the reactor is calling a method on the server when it
270
+ # is done buffering a request or receives a new request from a keepalive connection.
271
+ self.reactor_wakeup(c)
272
+ }
258
273
  @reactor.run
259
274
  end
260
275
 
@@ -279,6 +294,9 @@ module Puma
279
294
  # This method is called from the Reactor thread when a queued Client receives data,
280
295
  # times out, or when the Reactor is shutting down.
281
296
  #
297
+ # While the code lives in the Server, the logic is executed on the reactor thread, independently
298
+ # from the server.
299
+ #
282
300
  # It is responsible for ensuring that a request has been completely received
283
301
  # before it starts to be processed by the ThreadPool. This may be known as read buffering.
284
302
  # If read buffering is not done, and no other read buffering is performed (such as by an application server
@@ -313,11 +331,14 @@ module Puma
313
331
  end
314
332
  rescue StandardError => e
315
333
  client_error(e, client)
316
- client.close
334
+ close_client_safely(client)
317
335
  true
318
336
  end
319
337
 
320
338
  def handle_servers
339
+ @env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
340
+ Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
341
+
321
342
  begin
322
343
  check = @check
323
344
  sockets = [check] + @binder.ios
@@ -364,8 +385,18 @@ module Puma
364
385
  if sock == check
365
386
  break if handle_check
366
387
  else
367
- pool.wait_until_not_full
368
- pool.wait_for_less_busy_worker(options[:wait_for_less_busy_worker]) if @clustered
388
+ # if ThreadPool out_of_band code is running, we don't want to add
389
+ # clients until the code is finished.
390
+ pool.wait_while_out_of_band_running
391
+
392
+ # A well rested herd (cluster) runs faster
393
+ if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
394
+ delay = @cluster_accept_loop_delay.calculate(
395
+ max_threads: @max_threads,
396
+ busy_threads_plus_todo: busy_threads_plus_todo
397
+ )
398
+ sleep(delay)
399
+ end
369
400
 
370
401
  io = begin
371
402
  sock.accept_nonblock
@@ -373,11 +404,9 @@ module Puma
373
404
  next
374
405
  end
375
406
  drain += 1 if shutting_down?
376
- pool << Client.new(io, @binder.env(sock)).tap { |c|
377
- c.listener = sock
378
- c.http_content_length_limit = @http_content_length_limit
379
- c.send(addr_send_name, addr_value) if addr_value
380
- }
407
+ client = new_client(io, sock)
408
+ client.send(addr_send_name, addr_value) if addr_value
409
+ pool << client
381
410
  end
382
411
  end
383
412
  rescue IOError, Errno::EBADF
@@ -414,6 +443,14 @@ module Puma
414
443
  @events.fire :state, :done
415
444
  end
416
445
 
446
+ # :nodoc:
447
+ def new_client(io, sock)
448
+ client = Client.new(io, @binder.env(sock))
449
+ client.listener = sock
450
+ client.http_content_length_limit = @http_content_length_limit
451
+ client
452
+ end
453
+
417
454
  # :nodoc:
418
455
  def handle_check
419
456
  cmd = @check.read(1)
@@ -447,14 +484,12 @@ module Puma
447
484
  # Advertise this server into the thread
448
485
  Thread.current.puma_server = self
449
486
 
450
- clean_thread_locals = options[:clean_thread_locals]
451
487
  close_socket = true
452
488
 
453
489
  requests = 0
454
490
 
455
491
  begin
456
- if @queue_requests &&
457
- !client.eagerly_finish
492
+ if @queue_requests && !client.eagerly_finish
458
493
 
459
494
  client.set_timeout(@first_data_timeout)
460
495
  if @reactor.add client
@@ -467,39 +502,31 @@ module Puma
467
502
  client.finish(@first_data_timeout)
468
503
  end
469
504
 
470
- while true
471
- @requests_count += 1
472
- case handle_request(client, requests + 1)
473
- when false
474
- break
475
- when :async
476
- close_socket = false
477
- break
478
- when true
479
- ThreadPool.clean_thread_locals if clean_thread_locals
480
-
481
- requests += 1
482
-
483
- # As an optimization, try to read the next request from the
484
- # socket for a short time before returning to the reactor.
485
- fast_check = @status == :run
505
+ @requests_count += 1
506
+ case handle_request(client, requests + 1)
507
+ when false
508
+ when :async
509
+ close_socket = false
510
+ when true
511
+ requests += 1
486
512
 
487
- # Always pass the client back to the reactor after a reasonable
488
- # number of inline requests if there are other requests pending.
489
- fast_check = false if requests >= @max_fast_inline &&
490
- @thread_pool.backlog > 0
513
+ client.reset
491
514
 
492
- next_request_ready = with_force_shutdown(client) do
493
- client.reset(fast_check)
494
- end
515
+ # This indicates data exists in the client read buffer and there may be
516
+ # additional requests on it, so process them
517
+ next_request_ready = if client.has_back_to_back_requests?
518
+ with_force_shutdown(client) { client.process_back_to_back_requests }
519
+ else
520
+ with_force_shutdown(client) { client.eagerly_finish }
521
+ end
495
522
 
496
- unless next_request_ready
497
- break unless @queue_requests
498
- client.set_timeout @persistent_timeout
499
- if @reactor.add client
500
- close_socket = false
501
- break
502
- end
523
+ if next_request_ready
524
+ @thread_pool << client
525
+ close_socket = false
526
+ elsif @queue_requests
527
+ client.set_timeout @persistent_timeout
528
+ if @reactor.add client
529
+ close_socket = false
503
530
  end
504
531
  end
505
532
  end
@@ -511,17 +538,21 @@ module Puma
511
538
  ensure
512
539
  client.io_buffer.reset
513
540
 
514
- begin
515
- client.close if close_socket
516
- rescue IOError, SystemCallError
517
- Puma::Util.purge_interrupt_queue
518
- # Already closed
519
- rescue StandardError => e
520
- @log_writer.unknown_error e, nil, "Client"
521
- end
541
+ close_client_safely(client) if close_socket
522
542
  end
523
543
  end
524
544
 
545
+ # :nodoc:
546
+ def close_client_safely(client)
547
+ client.close
548
+ rescue IOError, SystemCallError
549
+ # Already closed
550
+ rescue MiniSSL::SSLError => e
551
+ @log_writer.ssl_error e, client.io
552
+ rescue StandardError => e
553
+ @log_writer.unknown_error e, nil, "Client"
554
+ end
555
+
525
556
  # Triggers a client timeout if the thread-pool shuts down
526
557
  # during execution of the provided block.
527
558
  def with_force_shutdown(client, &block)
@@ -615,11 +646,10 @@ module Puma
615
646
  @notify << message
616
647
  rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
617
648
  # The server, in another thread, is shutting down
618
- Puma::Util.purge_interrupt_queue
619
649
  rescue RuntimeError => e
620
650
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
621
651
  if e.message.include?('IOError')
622
- Puma::Util.purge_interrupt_queue
652
+ # ignore
623
653
  else
624
654
  raise e
625
655
  end
@@ -650,7 +680,16 @@ module Puma
650
680
 
651
681
  # List of methods invoked by #stats.
652
682
  # @version 5.0.0
653
- STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count, :busy_threads].freeze
683
+ STAT_METHODS = [
684
+ :backlog,
685
+ :running,
686
+ :pool_capacity,
687
+ :busy_threads,
688
+ :backlog_max,
689
+ :max_threads,
690
+ :requests_count,
691
+ :reactor_max,
692
+ ].freeze
654
693
 
655
694
  # Returns a hash of stats about the running server for reporting purposes.
656
695
  # @version 5.0.0
@@ -660,9 +699,16 @@ module Puma
660
699
  stats = @thread_pool&.stats || {}
661
700
  stats[:max_threads] = @max_threads
662
701
  stats[:requests_count] = @requests_count
702
+ stats[:reactor_max] = @reactor.reactor_max if @reactor
703
+ reset_max
663
704
  stats
664
705
  end
665
706
 
707
+ def reset_max
708
+ @reactor.reactor_max = 0 if @reactor
709
+ @thread_pool.reset_max
710
+ end
711
+
666
712
  # below are 'delegations' to binder
667
713
  # remove in Puma 7?
668
714
 
data/lib/puma/single.rb CHANGED
@@ -17,7 +17,7 @@ module Puma
17
17
  def stats
18
18
  {
19
19
  started_at: utc_iso8601(@started_at)
20
- }.merge(@server.stats).merge(super)
20
+ }.merge(@server&.stats || {}).merge(super)
21
21
  end
22
22
 
23
23
  def restart
@@ -53,9 +53,12 @@ module Puma
53
53
  server_thread = server.run
54
54
 
55
55
  log "Use Ctrl-C to stop"
56
+
57
+ warn_ruby_mn_threads
58
+
56
59
  redirect_io
57
60
 
58
- @events.fire_on_booted!
61
+ @events.fire_after_booted!
59
62
 
60
63
  debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
61
64