puma 4.3.12 → 5.6.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1461 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +38 -9
  25. data/ext/puma_http11/http11_parser.c +45 -47
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +1 -1
  28. data/ext/puma_http11/http11_parser.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +204 -86
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  34. data/ext/puma_http11/puma_http11.c +32 -51
  35. data/lib/puma/app/status.rb +47 -36
  36. data/lib/puma/binder.rb +225 -106
  37. data/lib/puma/cli.rb +24 -18
  38. data/lib/puma/client.rb +104 -76
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +212 -220
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +58 -49
  44. data/lib/puma/const.rb +13 -6
  45. data/lib/puma/control_cli.rb +93 -76
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +364 -96
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -34
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +117 -46
  54. data/lib/puma/minissl/context_builder.rb +14 -9
  55. data/lib/puma/minissl.rb +128 -46
  56. data/lib/puma/null_io.rb +13 -1
  57. data/lib/puma/plugin.rb +3 -12
  58. data/lib/puma/queue_close.rb +26 -0
  59. data/lib/puma/rack/builder.rb +1 -5
  60. data/lib/puma/reactor.rb +85 -369
  61. data/lib/puma/request.rb +472 -0
  62. data/lib/puma/runner.rb +46 -61
  63. data/lib/puma/server.rb +290 -763
  64. data/lib/puma/single.rb +9 -65
  65. data/lib/puma/state_file.rb +47 -8
  66. data/lib/puma/systemd.rb +46 -0
  67. data/lib/puma/thread_pool.rb +125 -57
  68. data/lib/puma/util.rb +20 -1
  69. data/lib/puma.rb +46 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  72. metadata +26 -22
  73. data/docs/tcp_mode.md +0 -96
  74. data/ext/puma_http11/io_buffer.c +0 -155
  75. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  76. data/lib/puma/accept_nonblock.rb +0 -29
  77. data/lib/puma/tcp_logger.rb +0 -41
  78. data/tools/jungle/README.md +0 -19
  79. data/tools/jungle/init.d/README.md +0 -61
  80. data/tools/jungle/init.d/puma +0 -421
  81. data/tools/jungle/init.d/run-puma +0 -18
  82. data/tools/jungle/upstart/README.md +0 -61
  83. data/tools/jungle/upstart/puma-manager.conf +0 -31
  84. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/server.rb CHANGED
@@ -9,12 +9,12 @@ require 'puma/null_io'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/accept_nonblock'
13
12
  require 'puma/util'
14
-
15
- require 'puma/puma_http11'
13
+ require 'puma/io_buffer'
14
+ require 'puma/request'
16
15
 
17
16
  require 'socket'
17
+ require 'io/wait'
18
18
  require 'forwardable'
19
19
 
20
20
  module Puma
@@ -32,18 +32,31 @@ module Puma
32
32
  class Server
33
33
 
34
34
  include Puma::Const
35
+ include Request
35
36
  extend Forwardable
36
37
 
37
38
  attr_reader :thread
38
39
  attr_reader :events
40
+ attr_reader :min_threads, :max_threads # for #stats
41
+ attr_reader :requests_count # @version 5.0.0
42
+
43
+ # @todo the following may be deprecated in the future
44
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
45
+ :leak_stack_on_error,
46
+ :persistent_timeout, :reaping_time
47
+
48
+ # @deprecated v6.0.0
49
+ attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
50
+ :leak_stack_on_error, :min_threads, :max_threads,
51
+ :persistent_timeout, :reaping_time
52
+
39
53
  attr_accessor :app
54
+ attr_accessor :binder
55
+
56
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
57
+ :add_unix_listener, :connected_ports
40
58
 
41
- attr_accessor :min_threads
42
- attr_accessor :max_threads
43
- attr_accessor :persistent_timeout
44
- attr_accessor :auto_trim_time
45
- attr_accessor :reaping_time
46
- attr_accessor :first_data_timeout
59
+ ThreadLocalKey = :puma_server
47
60
 
48
61
  # Create a server for the rack app +app+.
49
62
  #
@@ -53,85 +66,117 @@ module Puma
53
66
  # Server#run returns a thread that you can join on to wait for the server
54
67
  # to do its work.
55
68
  #
69
+ # @note Several instance variables exist so they are available for testing,
70
+ # and have default values set via +fetch+. Normally the values are set via
71
+ # `::Puma::Configuration.puma_default_options`.
72
+ #
56
73
  def initialize(app, events=Events.stdio, options={})
57
74
  @app = app
58
75
  @events = events
59
76
 
60
- @check, @notify = Puma::Util.pipe
61
-
77
+ @check, @notify = nil
62
78
  @status = :stop
63
79
 
64
- @min_threads = 0
65
- @max_threads = 16
66
80
  @auto_trim_time = 30
67
81
  @reaping_time = 1
68
82
 
69
83
  @thread = nil
70
84
  @thread_pool = nil
71
- @early_hints = nil
72
85
 
73
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
86
+ @options = options
75
87
 
76
- @binder = Binder.new(events)
88
+ @early_hints = options.fetch :early_hints, nil
89
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
90
+ @min_threads = options.fetch :min_threads, 0
91
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
92
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
93
+ @queue_requests = options.fetch :queue_requests, true
94
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
95
+ @io_selector_backend = options.fetch :io_selector_backend, :auto
77
96
 
78
- @leak_stack_on_error = true
97
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
98
+ @leak_stack_on_error = @options[:environment] ? temp : true
79
99
 
80
- @options = options
81
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
100
+ @binder = Binder.new(events)
82
101
 
83
102
  ENV['RACK_ENV'] ||= "development"
84
103
 
85
104
  @mode = :http
86
105
 
87
106
  @precheck_closing = true
88
- end
89
107
 
90
- attr_accessor :binder, :leak_stack_on_error, :early_hints
91
-
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
108
+ @requests_count = 0
109
+ end
93
110
 
94
111
  def inherit_binder(bind)
95
112
  @binder = bind
96
113
  end
97
114
 
98
- def tcp_mode!
99
- @mode = :tcp
115
+ class << self
116
+ # @!attribute [r] current
117
+ def current
118
+ Thread.current[ThreadLocalKey]
119
+ end
120
+
121
+ # :nodoc:
122
+ # @version 5.0.0
123
+ def tcp_cork_supported?
124
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
125
+ end
126
+
127
+ # :nodoc:
128
+ # @version 5.0.0
129
+ def closed_socket_supported?
130
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
131
+ end
132
+ private :tcp_cork_supported?
133
+ private :closed_socket_supported?
100
134
  end
101
135
 
102
136
  # On Linux, use TCP_CORK to better control how the TCP stack
103
137
  # packetizes our stream. This improves both latency and throughput.
138
+ # socket parameter may be an MiniSSL::Socket, so use to_io
104
139
  #
105
- if RUBY_PLATFORM =~ /linux/
106
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
-
140
+ if tcp_cork_supported?
108
141
  # 6 == Socket::IPPROTO_TCP
109
142
  # 3 == TCP_CORK
110
143
  # 1/0 == turn on/off
111
144
  def cork_socket(socket)
145
+ skt = socket.to_io
112
146
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
147
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
114
148
  rescue IOError, SystemCallError
115
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
+ Puma::Util.purge_interrupt_queue
116
150
  end
117
151
  end
118
152
 
119
153
  def uncork_socket(socket)
154
+ skt = socket.to_io
120
155
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
156
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
122
157
  rescue IOError, SystemCallError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
158
+ Puma::Util.purge_interrupt_queue
124
159
  end
125
160
  end
161
+ else
162
+ def cork_socket(socket)
163
+ end
164
+
165
+ def uncork_socket(socket)
166
+ end
167
+ end
168
+
169
+ if closed_socket_supported?
170
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
126
171
 
127
172
  def closed_socket?(socket)
128
- return false unless socket.kind_of? TCPSocket
129
- return false unless @precheck_closing
173
+ skt = socket.to_io
174
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
130
175
 
131
176
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
177
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
178
  rescue IOError, SystemCallError
134
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
179
+ Puma::Util.purge_interrupt_queue
135
180
  @precheck_closing = false
136
181
  false
137
182
  else
@@ -141,21 +186,17 @@ module Puma
141
186
  end
142
187
  end
143
188
  else
144
- def cork_socket(socket)
145
- end
146
-
147
- def uncork_socket(socket)
148
- end
149
-
150
189
  def closed_socket?(socket)
151
190
  false
152
191
  end
153
192
  end
154
193
 
194
+ # @!attribute [r] backlog
155
195
  def backlog
156
196
  @thread_pool and @thread_pool.backlog
157
197
  end
158
198
 
199
+ # @!attribute [r] running
159
200
  def running
160
201
  @thread_pool and @thread_pool.spawned
161
202
  end
@@ -168,180 +209,38 @@ module Puma
168
209
  # there are 5 threads sitting idle ready to take
169
210
  # a request. If one request comes in, then the
170
211
  # value would be 4 until it finishes processing.
212
+ # @!attribute [r] pool_capacity
171
213
  def pool_capacity
172
214
  @thread_pool and @thread_pool.pool_capacity
173
215
  end
174
216
 
175
- # Lopez Mode == raw tcp apps
176
-
177
- def run_lopez_mode(background=true)
178
- @thread_pool = ThreadPool.new(@min_threads,
179
- @max_threads,
180
- Hash) do |client, tl|
181
-
182
- io = client.to_io
183
- addr = io.peeraddr.last
184
-
185
- if addr.empty?
186
- # Set unix socket addrs to localhost
187
- addr = "127.0.0.1:0"
188
- else
189
- addr = "#{addr}:#{io.peeraddr[1]}"
190
- end
191
-
192
- env = { 'thread' => tl, REMOTE_ADDR => addr }
193
-
194
- begin
195
- @app.call env, client.to_io
196
- rescue Object => e
197
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
198
- STDERR.puts e.backtrace
199
- end
200
-
201
- client.close unless env['detach']
202
- end
203
-
204
- @events.fire :state, :running
205
-
206
- if background
207
- @thread = Thread.new do
208
- Puma.set_thread_name "server"
209
- handle_servers_lopez_mode
210
- end
211
- return @thread
212
- else
213
- handle_servers_lopez_mode
214
- end
215
- end
216
-
217
- def handle_servers_lopez_mode
218
- begin
219
- check = @check
220
- sockets = [check] + @binder.ios
221
- pool = @thread_pool
222
-
223
- while @status == :run
224
- begin
225
- ios = IO.select sockets
226
- ios.first.each do |sock|
227
- if sock == check
228
- break if handle_check
229
- else
230
- begin
231
- if io = sock.accept_nonblock
232
- client = Client.new io, nil
233
- pool << client
234
- end
235
- rescue SystemCallError
236
- # nothing
237
- rescue Errno::ECONNABORTED
238
- # client closed the socket even before accept
239
- begin
240
- io.close
241
- rescue
242
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
- end
244
- end
245
- end
246
- end
247
- rescue Object => e
248
- @events.unknown_error self, e, "Listen loop"
249
- end
250
- end
251
-
252
- @events.fire :state, @status
253
-
254
- graceful_shutdown if @status == :stop || @status == :restart
255
-
256
- rescue Exception => e
257
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
258
- STDERR.puts e.backtrace
259
- ensure
260
- begin
261
- @check.close
262
- rescue
263
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
- end
265
-
266
- # Prevent can't modify frozen IOError (RuntimeError)
267
- begin
268
- @notify.close
269
- rescue IOError
270
- # no biggy
271
- end
272
- end
273
-
274
- @events.fire :state, :done
275
- end
276
217
  # Runs the server.
277
218
  #
278
219
  # If +background+ is true (the default) then a thread is spun
279
220
  # up in the background to handle requests. Otherwise requests
280
221
  # are handled synchronously.
281
222
  #
282
- def run(background=true)
223
+ def run(background=true, thread_name: 'srv')
283
224
  BasicSocket.do_not_reverse_lookup = true
284
225
 
285
226
  @events.fire :state, :booting
286
227
 
287
228
  @status = :run
288
229
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
- @thread_pool = ThreadPool.new(@min_threads,
296
- @max_threads,
297
- IOBuffer) do |client, buffer|
298
-
299
- # Advertise this server into the thread
300
- Thread.current[ThreadLocalKey] = self
301
-
302
- process_now = false
303
-
304
- begin
305
- if queue_requests
306
- process_now = client.eagerly_finish
307
- else
308
- client.finish
309
- process_now = true
310
- end
311
- rescue MiniSSL::SSLError => e
312
- ssl_socket = client.io
313
- addr = ssl_socket.peeraddr.last
314
- cert = ssl_socket.peercert
315
-
316
- client.close
317
-
318
- @events.ssl_error self, addr, cert, e
319
- rescue HttpParserError => e
320
- client.write_error(400)
321
- client.close
322
-
323
- @events.parse_error self, client.env, e
324
- rescue HttpParserError501 => e
325
- client.write_error(501)
326
- client.close
327
- @events.parse_error self, client.env, e
328
- rescue ConnectionError, EOFError
329
- client.close
330
- else
331
- if process_now
332
- process_client client, buffer
333
- else
334
- client.set_timeout @first_data_timeout
335
- @reactor.add client
336
- end
337
- end
338
- end
230
+ @thread_pool = ThreadPool.new(
231
+ thread_name,
232
+ @min_threads,
233
+ @max_threads,
234
+ ::Puma::IOBuffer,
235
+ &method(:process_client)
236
+ )
339
237
 
238
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
340
239
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
341
240
 
342
- if queue_requests
343
- @reactor = Reactor.new self, @thread_pool
344
- @reactor.run_in_thread
241
+ if @queue_requests
242
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
243
+ @reactor.run
345
244
  end
346
245
 
347
246
  if @reaping_time
@@ -352,11 +251,13 @@ module Puma
352
251
  @thread_pool.auto_trim!(@auto_trim_time)
353
252
  end
354
253
 
254
+ @check, @notify = Puma::Util.pipe unless @notify
255
+
355
256
  @events.fire :state, :running
356
257
 
357
258
  if background
358
259
  @thread = Thread.new do
359
- Puma.set_thread_name "server"
260
+ Puma.set_thread_name thread_name
360
261
  handle_servers
361
262
  end
362
263
  return @thread
@@ -365,75 +266,118 @@ module Puma
365
266
  end
366
267
  end
367
268
 
269
+ # This method is called from the Reactor thread when a queued Client receives data,
270
+ # times out, or when the Reactor is shutting down.
271
+ #
272
+ # It is responsible for ensuring that a request has been completely received
273
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
274
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
275
+ # such as nginx) then the application would be subject to a slow client attack.
276
+ #
277
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
278
+ #
279
+ # The method checks to see if it has the full header and body with
280
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
281
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
282
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
283
+ # The Client is then removed from the reactor (return `true`).
284
+ #
285
+ # If a client object times out, a 408 response is written, its connection is closed,
286
+ # and the object is removed from the reactor (return `true`).
287
+ #
288
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
289
+ # ThreadPool, depending on their current state (#can_close?).
290
+ #
291
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
292
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
293
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
294
+ def reactor_wakeup(client)
295
+ shutdown = !@queue_requests
296
+ if client.try_to_finish || (shutdown && !client.can_close?)
297
+ @thread_pool << client
298
+ elsif shutdown || client.timeout == 0
299
+ client.timeout!
300
+ else
301
+ client.set_timeout(@first_data_timeout)
302
+ false
303
+ end
304
+ rescue StandardError => e
305
+ client_error(e, client)
306
+ client.close
307
+ true
308
+ end
309
+
368
310
  def handle_servers
369
311
  begin
370
312
  check = @check
371
313
  sockets = [check] + @binder.ios
372
314
  pool = @thread_pool
373
315
  queue_requests = @queue_requests
316
+ drain = @options[:drain_on_shutdown] ? 0 : nil
374
317
 
375
- remote_addr_value = nil
376
- remote_addr_header = nil
377
-
378
- case @options[:remote_address]
318
+ addr_send_name, addr_value = case @options[:remote_address]
379
319
  when :value
380
- remote_addr_value = @options[:remote_address_value]
320
+ [:peerip=, @options[:remote_address_value]]
381
321
  when :header
382
- remote_addr_header = @options[:remote_address_header]
322
+ [:remote_addr_header=, @options[:remote_address_header]]
323
+ when :proxy_protocol
324
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
325
+ else
326
+ [nil, nil]
383
327
  end
384
328
 
385
- while @status == :run
329
+ while @status == :run || (drain && shutting_down?)
386
330
  begin
387
- ios = IO.select sockets
331
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
332
+ break unless ios
388
333
  ios.first.each do |sock|
389
334
  if sock == check
390
335
  break if handle_check
391
336
  else
392
- begin
393
- if io = sock.accept_nonblock
394
- client = Client.new io, @binder.env(sock)
395
- if remote_addr_value
396
- client.peerip = remote_addr_value
397
- elsif remote_addr_header
398
- client.remote_addr_header = remote_addr_header
399
- end
400
-
401
- pool << client
402
- busy_threads = pool.wait_until_not_full
403
- if busy_threads == 0
404
- @options[:out_of_band].each(&:call) if @options[:out_of_band]
405
- end
406
- end
407
- rescue SystemCallError
408
- # nothing
409
- rescue Errno::ECONNABORTED
410
- # client closed the socket even before accept
411
- begin
412
- io.close
413
- rescue
414
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
415
- end
337
+ pool.wait_until_not_full
338
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
339
+
340
+ io = begin
341
+ sock.accept_nonblock
342
+ rescue IO::WaitReadable
343
+ next
416
344
  end
345
+ drain += 1 if shutting_down?
346
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
347
+ c.listener = sock
348
+ c.send(addr_send_name, addr_value) if addr_value
349
+ }
417
350
  end
418
351
  end
419
- rescue Object => e
420
- @events.unknown_error self, e, "Listen loop"
352
+ rescue IOError, Errno::EBADF
353
+ # In the case that any of the sockets are unexpectedly close.
354
+ raise
355
+ rescue StandardError => e
356
+ @events.unknown_error e, nil, "Listen loop"
421
357
  end
422
358
  end
423
359
 
360
+ @events.debug "Drained #{drain} additional connections." if drain
424
361
  @events.fire :state, @status
425
362
 
426
- graceful_shutdown if @status == :stop || @status == :restart
427
363
  if queue_requests
428
- @reactor.clear!
364
+ @queue_requests = false
429
365
  @reactor.shutdown
430
366
  end
367
+ graceful_shutdown if @status == :stop || @status == :restart
431
368
  rescue Exception => e
432
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
433
- STDERR.puts e.backtrace
369
+ @events.unknown_error e, nil, "Exception handling servers"
434
370
  ensure
435
- @check.close
436
- @notify.close
371
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
+ # Errno::EBADF is infrequently raised
373
+ [@check, @notify].each do |io|
374
+ begin
375
+ io.close unless io.closed?
376
+ rescue Errno::EBADF, RuntimeError
377
+ end
378
+ end
379
+ @notify = nil
380
+ @check = nil
437
381
  end
438
382
 
439
383
  @events.fire :state, :done
@@ -455,522 +399,149 @@ module Puma
455
399
  return true
456
400
  end
457
401
 
458
- return false
402
+ false
459
403
  end
460
404
 
461
- # Given a connection on +client+, handle the incoming requests.
405
+ # Given a connection on +client+, handle the incoming requests,
406
+ # or queue the connection in the Reactor if no request is available.
462
407
  #
463
- # This method support HTTP Keep-Alive so it may, depending on if the client
408
+ # This method is called from a ThreadPool worker thread.
409
+ #
410
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
464
411
  # indicates that it supports keep alive, wait for another request before
465
412
  # returning.
466
413
  #
414
+ # Return true if one or more requests were processed.
467
415
  def process_client(client, buffer)
416
+ # Advertise this server into the thread
417
+ Thread.current[ThreadLocalKey] = self
418
+
419
+ clean_thread_locals = @options[:clean_thread_locals]
420
+ close_socket = true
421
+
422
+ requests = 0
423
+
468
424
  begin
425
+ if @queue_requests &&
426
+ !client.eagerly_finish
469
427
 
470
- clean_thread_locals = @options[:clean_thread_locals]
471
- close_socket = true
428
+ client.set_timeout(@first_data_timeout)
429
+ if @reactor.add client
430
+ close_socket = false
431
+ return false
432
+ end
433
+ end
472
434
 
473
- requests = 0
435
+ with_force_shutdown(client) do
436
+ client.finish(@first_data_timeout)
437
+ end
474
438
 
475
439
  while true
476
- case handle_request(client, buffer)
440
+ @requests_count += 1
441
+ case handle_request(client, buffer, requests + 1)
477
442
  when false
478
- return
443
+ break
479
444
  when :async
480
445
  close_socket = false
481
- return
446
+ break
482
447
  when true
483
- return unless @queue_requests
484
448
  buffer.reset
485
449
 
486
450
  ThreadPool.clean_thread_locals if clean_thread_locals
487
451
 
488
452
  requests += 1
489
453
 
490
- # Closing keepalive sockets after they've made a reasonable
491
- # number of requests allows Puma to service many connections
492
- # fairly, even when the number of concurrent connections exceeds
493
- # the size of the threadpool. It also allows cluster mode Pumas
494
- # to keep load evenly distributed across workers, because clients
495
- # are randomly assigned a new worker when opening a new connection.
496
- #
497
- # Previously, Puma would kick connections in this conditional back
498
- # to the reactor. However, because this causes the todo set to increase
499
- # in size, the wait_until_full mutex would never unlock, leaving
500
- # any additional connections unserviced.
501
- break if requests >= MAX_FAST_INLINE
502
-
503
- check_for_more_data = @status == :run
504
-
505
- unless client.reset(check_for_more_data)
506
- close_socket = false
454
+ # As an optimization, try to read the next request from the
455
+ # socket for a short time before returning to the reactor.
456
+ fast_check = @status == :run
457
+
458
+ # Always pass the client back to the reactor after a reasonable
459
+ # number of inline requests if there are other requests pending.
460
+ fast_check = false if requests >= @max_fast_inline &&
461
+ @thread_pool.backlog > 0
462
+
463
+ next_request_ready = with_force_shutdown(client) do
464
+ client.reset(fast_check)
465
+ end
466
+
467
+ unless next_request_ready
468
+ break unless @queue_requests
507
469
  client.set_timeout @persistent_timeout
508
- @reactor.add client
509
- return
470
+ if @reactor.add client
471
+ close_socket = false
472
+ break
473
+ end
510
474
  end
511
475
  end
512
476
  end
513
-
514
- # The client disconnected while we were reading data
515
- rescue ConnectionError
516
- # Swallow them. The ensure tries to close +client+ down
517
-
518
- # SSL handshake error
519
- rescue MiniSSL::SSLError => e
520
- lowlevel_error(e, client.env)
521
-
522
- ssl_socket = client.io
523
- addr = ssl_socket.peeraddr.last
524
- cert = ssl_socket.peercert
525
-
526
- close_socket = true
527
-
528
- @events.ssl_error self, addr, cert, e
529
-
530
- # The client doesn't know HTTP well
531
- rescue HttpParserError => e
532
- lowlevel_error(e, client.env)
533
-
534
- client.write_error(400)
535
-
536
- @events.parse_error self, client.env, e
537
- rescue HttpParserError501 => e
538
- lowlevel_error(e, client.env)
539
-
540
- client.write_error(501)
541
-
542
- @events.parse_error self, client.env, e
543
- # Server error
477
+ true
544
478
  rescue StandardError => e
545
- lowlevel_error(e, client.env)
546
-
547
- client.write_error(500)
548
-
549
- @events.unknown_error self, e, "Read"
550
-
479
+ client_error(e, client)
480
+ # The ensure tries to close +client+ down
481
+ requests > 0
551
482
  ensure
552
483
  buffer.reset
553
484
 
554
485
  begin
555
486
  client.close if close_socket
556
487
  rescue IOError, SystemCallError
557
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
558
489
  # Already closed
559
490
  rescue StandardError => e
560
- @events.unknown_error self, e, "Client"
491
+ @events.unknown_error e, nil, "Client"
561
492
  end
562
493
  end
563
494
  end
564
495
 
565
- # Given a Hash +env+ for the request read from +client+, add
566
- # and fixup keys to comply with Rack's env guidelines.
567
- #
568
- def normalize_env(env, client)
569
- if host = env[HTTP_HOST]
570
- if colon = host.index(":")
571
- env[SERVER_NAME] = host[0, colon]
572
- env[SERVER_PORT] = host[colon+1, host.bytesize]
573
- else
574
- env[SERVER_NAME] = host
575
- env[SERVER_PORT] = default_server_port(env)
576
- end
577
- else
578
- env[SERVER_NAME] = LOCALHOST
579
- env[SERVER_PORT] = default_server_port(env)
580
- end
581
-
582
- unless env[REQUEST_PATH]
583
- # it might be a dumbass full host request header
584
- uri = URI.parse(env[REQUEST_URI])
585
- env[REQUEST_PATH] = uri.path
586
-
587
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
588
-
589
- # A nil env value will cause a LintError (and fatal errors elsewhere),
590
- # so only set the env value if there actually is a value.
591
- env[QUERY_STRING] = uri.query if uri.query
592
- end
593
-
594
- env[PATH_INFO] = env[REQUEST_PATH]
595
-
596
- # From http://www.ietf.org/rfc/rfc3875 :
597
- # "Script authors should be aware that the REMOTE_ADDR and
598
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
599
- # may not identify the ultimate source of the request.
600
- # They identify the client for the immediate request to the
601
- # server; that client may be a proxy, gateway, or other
602
- # intermediary acting on behalf of the actual source client."
603
- #
604
-
605
- unless env.key?(REMOTE_ADDR)
606
- begin
607
- addr = client.peerip
608
- rescue Errno::ENOTCONN
609
- # Client disconnects can result in an inability to get the
610
- # peeraddr from the socket; default to localhost.
611
- addr = LOCALHOST_IP
612
- end
613
-
614
- # Set unix socket addrs to localhost
615
- addr = LOCALHOST_IP if addr.empty?
616
-
617
- env[REMOTE_ADDR] = addr
618
- end
496
+ # Triggers a client timeout if the thread-pool shuts down
497
+ # during execution of the provided block.
498
+ def with_force_shutdown(client, &block)
499
+ @thread_pool.with_force_shutdown(&block)
500
+ rescue ThreadPool::ForceShutdown
501
+ client.timeout!
619
502
  end
620
503
 
621
- def default_server_port(env)
622
- 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"
623
- PORT_443
624
- else
625
- PORT_80
626
- end
627
- end
628
-
629
- # Takes the request +req+, invokes the Rack application to construct
630
- # the response and writes it back to +req.io+.
631
- #
632
- # The second parameter +lines+ is a IO-like object unique to this thread.
633
- # This is normally an instance of Puma::IOBuffer.
634
- #
635
- # It'll return +false+ when the connection is closed, this doesn't mean
636
- # that the response wasn't successful.
637
- #
638
- # It'll return +:async+ if the connection remains open but will be handled
639
- # elsewhere, i.e. the connection has been hijacked by the Rack application.
640
- #
641
- # Finally, it'll return +true+ on keep-alive connections.
642
- def handle_request(req, lines)
643
- env = req.env
644
- client = req.io
645
-
646
- return false if closed_socket?(client)
647
-
648
- normalize_env env, req
504
+ # :nocov:
649
505
 
650
- env[PUMA_SOCKET] = client
651
-
652
- if env[HTTPS_KEY] && client.peercert
653
- env[PUMA_PEERCERT] = client.peercert
654
- end
655
-
656
- env[HIJACK_P] = true
657
- env[HIJACK] = req
658
-
659
- body = req.body
660
-
661
- head = env[REQUEST_METHOD] == HEAD
662
-
663
- env[RACK_INPUT] = body
664
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
665
-
666
- if @early_hints
667
- env[EARLY_HINTS] = lambda { |headers|
668
- begin
669
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
670
-
671
- headers.each_pair do |k, vs|
672
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
673
- vs.to_s.split(NEWLINE).each do |v|
674
- next if possible_header_injection?(v)
675
- fast_write client, "#{k}: #{v}\r\n"
676
- end
677
- else
678
- fast_write client, "#{k}: #{vs}\r\n"
679
- end
680
- end
681
-
682
- fast_write client, "\r\n".freeze
683
- rescue ConnectionError
684
- # noop, if we lost the socket we just won't send the early hints
685
- end
686
- }
687
- end
688
-
689
- # Fixup any headers with , in the name to have _ now. We emit
690
- # headers with , in them during the parse phase to avoid ambiguity
691
- # with the - to _ conversion for critical headers. But here for
692
- # compatibility, we'll convert them back. This code is written to
693
- # avoid allocation in the common case (ie there are no headers
694
- # with , in their names), that's why it has the extra conditionals.
695
-
696
- to_delete = nil
697
- to_add = nil
698
-
699
- env.each do |k,v|
700
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
701
- if to_delete
702
- to_delete << k
703
- else
704
- to_delete = [k]
705
- end
706
-
707
- unless to_add
708
- to_add = {}
709
- end
506
+ # Handle various error types thrown by Client I/O operations.
507
+ def client_error(e, client)
508
+ # Swallow, do not log
509
+ return if [ConnectionError, EOFError].include?(e.class)
710
510
 
711
- to_add[k.tr(",", "_")] = v
712
- end
713
- end
714
-
715
- if to_delete
716
- to_delete.each { |k| env.delete(k) }
717
- env.merge! to_add
718
- end
719
-
720
- # A rack extension. If the app writes #call'ables to this
721
- # array, we will invoke them when the request is done.
722
- #
723
- after_reply = env[RACK_AFTER_REPLY] = []
724
-
725
- begin
726
- begin
727
- status, headers, res_body = @app.call(env)
728
-
729
- return :async if req.hijacked
730
-
731
- status = status.to_i
732
-
733
- if status == -1
734
- unless headers.empty? and res_body == []
735
- raise "async response must have empty headers and body"
736
- end
737
-
738
- return :async
739
- end
740
- rescue ThreadPool::ForceShutdown => e
741
- @events.log "Detected force shutdown of a thread, returning 503"
742
- @events.unknown_error self, e, "Rack app"
743
-
744
- status = 503
745
- headers = {}
746
- res_body = ["Request was internally terminated early\n"]
747
-
748
- rescue Exception => e
749
- @events.unknown_error self, e, "Rack app", env
750
-
751
- status, headers, res_body = lowlevel_error(e, env)
752
- end
753
-
754
- content_length = nil
755
- no_body = head
756
-
757
- if res_body.kind_of? Array and res_body.size == 1
758
- content_length = res_body[0].bytesize
759
- end
760
-
761
- cork_socket client
762
-
763
- line_ending = LINE_END
764
- colon = COLON
765
-
766
- http_11 = if env[HTTP_VERSION] == HTTP_11
767
- allow_chunked = true
768
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
769
- include_keepalive_header = false
770
-
771
- # An optimization. The most common response is 200, so we can
772
- # reply with the proper 200 status without having to compute
773
- # the response header.
774
- #
775
- if status == 200
776
- lines << HTTP_11_200
777
- else
778
- lines.append "HTTP/1.1 ", status.to_s, " ",
779
- fetch_status_code(status), line_ending
780
-
781
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
782
- end
783
- true
784
- else
785
- allow_chunked = false
786
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
787
- include_keepalive_header = keep_alive
788
-
789
- # Same optimization as above for HTTP/1.1
790
- #
791
- if status == 200
792
- lines << HTTP_10_200
793
- else
794
- lines.append "HTTP/1.0 ", status.to_s, " ",
795
- fetch_status_code(status), line_ending
796
-
797
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
798
- end
799
- false
800
- end
801
-
802
- response_hijack = nil
803
-
804
- headers.each do |k, vs|
805
- case k.downcase
806
- when CONTENT_LENGTH2
807
- next if possible_header_injection?(vs)
808
- content_length = vs
809
- next
810
- when TRANSFER_ENCODING
811
- allow_chunked = false
812
- content_length = nil
813
- when HIJACK
814
- response_hijack = vs
815
- next
816
- end
817
-
818
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
819
- vs.to_s.split(NEWLINE).each do |v|
820
- next if possible_header_injection?(v)
821
- lines.append k, colon, v, line_ending
822
- end
823
- else
824
- lines.append k, colon, line_ending
825
- end
826
- end
827
-
828
- if include_keepalive_header
829
- lines << CONNECTION_KEEP_ALIVE
830
- elsif http_11 && !keep_alive
831
- lines << CONNECTION_CLOSE
832
- end
833
-
834
- if no_body
835
- if content_length and status != 204
836
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
837
- end
838
-
839
- lines << line_ending
840
- fast_write client, lines.to_s
841
- return keep_alive
842
- end
843
-
844
- if content_length
845
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
846
- chunked = false
847
- elsif !response_hijack and allow_chunked
848
- lines << TRANSFER_ENCODING_CHUNKED
849
- chunked = true
850
- end
851
-
852
- lines << line_ending
853
-
854
- fast_write client, lines.to_s
855
-
856
- if response_hijack
857
- response_hijack.call client
858
- return :async
859
- end
860
-
861
- begin
862
- res_body.each do |part|
863
- next if part.bytesize.zero?
864
- if chunked
865
- fast_write client, part.bytesize.to_s(16)
866
- fast_write client, line_ending
867
- fast_write client, part
868
- fast_write client, line_ending
869
- else
870
- fast_write client, part
871
- end
872
-
873
- client.flush
874
- end
875
-
876
- if chunked
877
- fast_write client, CLOSE_CHUNKED
878
- client.flush
879
- end
880
- rescue SystemCallError, IOError
881
- raise ConnectionError, "Connection error detected during write"
882
- end
883
-
884
- ensure
885
- begin
886
- uncork_socket client
887
-
888
- body.close
889
- req.tempfile.unlink if req.tempfile
890
- ensure
891
- res_body.close if res_body.respond_to? :close
892
- end
893
-
894
- after_reply.each { |o| o.call }
895
- end
896
-
897
- return keep_alive
898
- end
899
-
900
- def fetch_status_code(status)
901
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
902
- end
903
- private :fetch_status_code
904
-
905
- # Given the request +env+ from +client+ and the partial body +body+
906
- # plus a potential Content-Length value +cl+, finish reading
907
- # the body and return it.
908
- #
909
- # If the body is larger than MAX_BODY, a Tempfile object is used
910
- # for the body, otherwise a StringIO is used.
911
- #
912
- def read_body(env, client, body, cl)
913
- content_length = cl.to_i
914
-
915
- remain = content_length - body.bytesize
916
-
917
- return StringIO.new(body) if remain <= 0
918
-
919
- # Use a Tempfile if there is a lot of data left
920
- if remain > MAX_BODY
921
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
922
- stream.binmode
511
+ lowlevel_error(e, client.env)
512
+ case e
513
+ when MiniSSL::SSLError
514
+ @events.ssl_error e, client.io
515
+ when HttpParserError
516
+ client.write_error(400)
517
+ @events.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @events.parse_error e, client
923
521
  else
924
- # The body[0,0] trick is to get an empty string in the same
925
- # encoding as body.
926
- stream = StringIO.new body[0,0]
927
- end
928
-
929
- stream.write body
930
-
931
- # Read an odd sized chunk so we can read even sized ones
932
- # after this
933
- chunk = client.readpartial(remain % CHUNK_SIZE)
934
-
935
- # No chunk means a closed socket
936
- unless chunk
937
- stream.close
938
- return nil
939
- end
940
-
941
- remain -= stream.write(chunk)
942
-
943
- # Raed the rest of the chunks
944
- while remain > 0
945
- chunk = client.readpartial(CHUNK_SIZE)
946
- unless chunk
947
- stream.close
948
- return nil
949
- end
950
-
951
- remain -= stream.write(chunk)
522
+ client.write_error(500)
523
+ @events.unknown_error e, nil, "Read"
952
524
  end
953
-
954
- stream.rewind
955
-
956
- return stream
957
525
  end
958
526
 
959
527
  # A fallback rack response if +@app+ raises as exception.
960
528
  #
961
- def lowlevel_error(e, env)
529
+ def lowlevel_error(e, env, status=500)
962
530
  if handler = @options[:lowlevel_error_handler]
963
531
  if handler.arity == 1
964
532
  return handler.call(e)
965
- else
533
+ elsif handler.arity == 2
966
534
  return handler.call(e, env)
535
+ else
536
+ return handler.call(e, env, status)
967
537
  end
968
538
  end
969
539
 
970
540
  if @leak_stack_on_error
971
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
541
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
972
543
  else
973
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
544
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
974
545
  end
975
546
  end
976
547
 
@@ -992,35 +563,13 @@ module Puma
992
563
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
993
564
  end
994
565
 
995
- if @options[:drain_on_shutdown]
996
- count = 0
997
-
998
- while true
999
- ios = IO.select @binder.ios, nil, nil, 0
1000
- break unless ios
1001
-
1002
- ios.first.each do |sock|
1003
- begin
1004
- if io = sock.accept_nonblock
1005
- count += 1
1006
- client = Client.new io, @binder.env(sock)
1007
- @thread_pool << client
1008
- end
1009
- rescue SystemCallError
1010
- end
1011
- end
1012
- end
1013
-
1014
- @events.debug "Drained #{count} additional connections."
1015
- end
1016
-
1017
566
  if @status != :restart
1018
567
  @binder.close
1019
568
  end
1020
569
 
1021
570
  if @thread_pool
1022
571
  if timeout = @options[:force_shutdown_after]
1023
- @thread_pool.shutdown timeout.to_i
572
+ @thread_pool.shutdown timeout.to_f
1024
573
  else
1025
574
  @thread_pool.shutdown
1026
575
  end
@@ -1028,18 +577,16 @@ module Puma
1028
577
  end
1029
578
 
1030
579
  def notify_safely(message)
1031
- begin
1032
- @notify << message
1033
- rescue IOError
1034
- # The server, in another thread, is shutting down
1035
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1036
- rescue RuntimeError => e
1037
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
1038
- if e.message.include?('IOError')
1039
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1040
- else
1041
- raise e
1042
- end
580
+ @notify << message
581
+ rescue IOError, NoMethodError, Errno::EPIPE
582
+ # The server, in another thread, is shutting down
583
+ Puma::Util.purge_interrupt_queue
584
+ rescue RuntimeError => e
585
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
586
+ if e.message.include?('IOError')
587
+ Puma::Util.purge_interrupt_queue
588
+ else
589
+ raise e
1043
590
  end
1044
591
  end
1045
592
  private :notify_safely
@@ -1057,44 +604,24 @@ module Puma
1057
604
  @thread.join if @thread && sync
1058
605
  end
1059
606
 
1060
- def begin_restart
607
+ def begin_restart(sync=false)
1061
608
  notify_safely(RESTART_COMMAND)
1062
- end
1063
-
1064
- def fast_write(io, str)
1065
- n = 0
1066
- while true
1067
- begin
1068
- n = io.syswrite str
1069
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
1070
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
1071
- raise ConnectionError, "Socket timeout writing data"
1072
- end
1073
-
1074
- retry
1075
- rescue Errno::EPIPE, SystemCallError, IOError
1076
- raise ConnectionError, "Socket timeout writing data"
1077
- end
1078
-
1079
- return if n == str.bytesize
1080
- str = str.byteslice(n..-1)
1081
- end
1082
- end
1083
- private :fast_write
1084
-
1085
- ThreadLocalKey = :puma_server
1086
-
1087
- def self.current
1088
- Thread.current[ThreadLocalKey]
609
+ @thread.join if @thread && sync
1089
610
  end
1090
611
 
1091
612
  def shutting_down?
1092
613
  @status == :stop || @status == :restart
1093
614
  end
1094
615
 
1095
- def possible_header_injection?(header_value)
1096
- HTTP_INJECTION_REGEX =~ header_value.to_s
616
+ # List of methods invoked by #stats.
617
+ # @version 5.0.0
618
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
619
+
620
+ # Returns a hash of stats about the running server for reporting purposes.
621
+ # @version 5.0.0
622
+ # @!attribute [r] stats
623
+ def stats
624
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1097
625
  end
1098
- private :possible_header_injection?
1099
626
  end
1100
627
  end