puma 4.3.6 → 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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1486 -518
  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/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +46 -9
  28. data/ext/puma_http11/http11_parser.c +68 -57
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +1 -1
  33. data/ext/puma_http11/mini_ssl.c +275 -122
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +47 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +174 -91
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +18 -9
  49. data/lib/puma/control_cli.rb +93 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +364 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +117 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +128 -46
  60. data/lib/puma/null_io.rb +13 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +472 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +287 -743
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +20 -1
  76. data/lib/puma.rb +46 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  79. data/tools/trickletest.rb +0 -0
  80. metadata +28 -24
  81. data/docs/tcp_mode.md +0 -96
  82. data/ext/puma_http11/io_buffer.c +0 -155
  83. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  84. data/lib/puma/accept_nonblock.rb +0 -29
  85. data/lib/puma/tcp_logger.rb +0 -41
  86. data/tools/jungle/README.md +0 -19
  87. data/tools/jungle/init.d/README.md +0 -61
  88. data/tools/jungle/init.d/puma +0 -421
  89. data/tools/jungle/init.d/run-puma +0 -18
  90. data/tools/jungle/upstart/README.md +0 -61
  91. data/tools/jungle/upstart/puma-manager.conf +0 -31
  92. 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,176 +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 ConnectionError, EOFError
325
- client.close
326
- else
327
- if process_now
328
- process_client client, buffer
329
- else
330
- client.set_timeout @first_data_timeout
331
- @reactor.add client
332
- end
333
- end
334
- end
230
+ @thread_pool = ThreadPool.new(
231
+ thread_name,
232
+ @min_threads,
233
+ @max_threads,
234
+ ::Puma::IOBuffer,
235
+ &method(:process_client)
236
+ )
335
237
 
238
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
239
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
240
 
338
- if queue_requests
339
- @reactor = Reactor.new self, @thread_pool
340
- @reactor.run_in_thread
241
+ if @queue_requests
242
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
243
+ @reactor.run
341
244
  end
342
245
 
343
246
  if @reaping_time
@@ -348,11 +251,13 @@ module Puma
348
251
  @thread_pool.auto_trim!(@auto_trim_time)
349
252
  end
350
253
 
254
+ @check, @notify = Puma::Util.pipe unless @notify
255
+
351
256
  @events.fire :state, :running
352
257
 
353
258
  if background
354
259
  @thread = Thread.new do
355
- Puma.set_thread_name "server"
260
+ Puma.set_thread_name thread_name
356
261
  handle_servers
357
262
  end
358
263
  return @thread
@@ -361,75 +266,118 @@ module Puma
361
266
  end
362
267
  end
363
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
+
364
310
  def handle_servers
365
311
  begin
366
312
  check = @check
367
313
  sockets = [check] + @binder.ios
368
314
  pool = @thread_pool
369
315
  queue_requests = @queue_requests
316
+ drain = @options[:drain_on_shutdown] ? 0 : nil
370
317
 
371
- remote_addr_value = nil
372
- remote_addr_header = nil
373
-
374
- case @options[:remote_address]
318
+ addr_send_name, addr_value = case @options[:remote_address]
375
319
  when :value
376
- remote_addr_value = @options[:remote_address_value]
320
+ [:peerip=, @options[:remote_address_value]]
377
321
  when :header
378
- 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]
379
327
  end
380
328
 
381
- while @status == :run
329
+ while @status == :run || (drain && shutting_down?)
382
330
  begin
383
- ios = IO.select sockets
331
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
332
+ break unless ios
384
333
  ios.first.each do |sock|
385
334
  if sock == check
386
335
  break if handle_check
387
336
  else
388
- begin
389
- if io = sock.accept_nonblock
390
- client = Client.new io, @binder.env(sock)
391
- if remote_addr_value
392
- client.peerip = remote_addr_value
393
- elsif remote_addr_header
394
- client.remote_addr_header = remote_addr_header
395
- end
396
-
397
- pool << client
398
- busy_threads = pool.wait_until_not_full
399
- if busy_threads == 0
400
- @options[:out_of_band].each(&:call) if @options[:out_of_band]
401
- end
402
- end
403
- rescue SystemCallError
404
- # nothing
405
- rescue Errno::ECONNABORTED
406
- # client closed the socket even before accept
407
- begin
408
- io.close
409
- rescue
410
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
411
- 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
412
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
+ }
413
350
  end
414
351
  end
415
- rescue Object => e
416
- @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"
417
357
  end
418
358
  end
419
359
 
360
+ @events.debug "Drained #{drain} additional connections." if drain
420
361
  @events.fire :state, @status
421
362
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
363
  if queue_requests
424
- @reactor.clear!
364
+ @queue_requests = false
425
365
  @reactor.shutdown
426
366
  end
367
+ graceful_shutdown if @status == :stop || @status == :restart
427
368
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
369
+ @events.unknown_error e, nil, "Exception handling servers"
430
370
  ensure
431
- @check.close
432
- @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
433
381
  end
434
382
 
435
383
  @events.fire :state, :done
@@ -451,509 +399,149 @@ module Puma
451
399
  return true
452
400
  end
453
401
 
454
- return false
402
+ false
455
403
  end
456
404
 
457
- # 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.
458
407
  #
459
- # 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
460
411
  # indicates that it supports keep alive, wait for another request before
461
412
  # returning.
462
413
  #
414
+ # Return true if one or more requests were processed.
463
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
+
464
424
  begin
425
+ if @queue_requests &&
426
+ !client.eagerly_finish
465
427
 
466
- clean_thread_locals = @options[:clean_thread_locals]
467
- 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
468
434
 
469
- requests = 0
435
+ with_force_shutdown(client) do
436
+ client.finish(@first_data_timeout)
437
+ end
470
438
 
471
439
  while true
472
- case handle_request(client, buffer)
440
+ @requests_count += 1
441
+ case handle_request(client, buffer, requests + 1)
473
442
  when false
474
- return
443
+ break
475
444
  when :async
476
445
  close_socket = false
477
- return
446
+ break
478
447
  when true
479
- return unless @queue_requests
480
448
  buffer.reset
481
449
 
482
450
  ThreadPool.clean_thread_locals if clean_thread_locals
483
451
 
484
452
  requests += 1
485
453
 
486
- check_for_more_data = @status == :run
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
487
462
 
488
- if requests >= MAX_FAST_INLINE
489
- # This will mean that reset will only try to use the data it already
490
- # has buffered and won't try to read more data. What this means is that
491
- # every client, independent of their request speed, gets treated like a slow
492
- # one once every MAX_FAST_INLINE requests.
493
- check_for_more_data = false
463
+ next_request_ready = with_force_shutdown(client) do
464
+ client.reset(fast_check)
494
465
  end
495
466
 
496
- unless client.reset(check_for_more_data)
497
- close_socket = false
467
+ unless next_request_ready
468
+ break unless @queue_requests
498
469
  client.set_timeout @persistent_timeout
499
- @reactor.add client
500
- return
470
+ if @reactor.add client
471
+ close_socket = false
472
+ break
473
+ end
501
474
  end
502
475
  end
503
476
  end
504
-
505
- # The client disconnected while we were reading data
506
- rescue ConnectionError
507
- # Swallow them. The ensure tries to close +client+ down
508
-
509
- # SSL handshake error
510
- rescue MiniSSL::SSLError => e
511
- lowlevel_error(e, client.env)
512
-
513
- ssl_socket = client.io
514
- addr = ssl_socket.peeraddr.last
515
- cert = ssl_socket.peercert
516
-
517
- close_socket = true
518
-
519
- @events.ssl_error self, addr, cert, e
520
-
521
- # The client doesn't know HTTP well
522
- rescue HttpParserError => e
523
- lowlevel_error(e, client.env)
524
-
525
- client.write_error(400)
526
-
527
- @events.parse_error self, client.env, e
528
-
529
- # Server error
477
+ true
530
478
  rescue StandardError => e
531
- lowlevel_error(e, client.env)
532
-
533
- client.write_error(500)
534
-
535
- @events.unknown_error self, e, "Read"
536
-
479
+ client_error(e, client)
480
+ # The ensure tries to close +client+ down
481
+ requests > 0
537
482
  ensure
538
483
  buffer.reset
539
484
 
540
485
  begin
541
486
  client.close if close_socket
542
487
  rescue IOError, SystemCallError
543
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
544
489
  # Already closed
545
490
  rescue StandardError => e
546
- @events.unknown_error self, e, "Client"
547
- end
548
- end
549
- end
550
-
551
- # Given a Hash +env+ for the request read from +client+, add
552
- # and fixup keys to comply with Rack's env guidelines.
553
- #
554
- def normalize_env(env, client)
555
- if host = env[HTTP_HOST]
556
- if colon = host.index(":")
557
- env[SERVER_NAME] = host[0, colon]
558
- env[SERVER_PORT] = host[colon+1, host.bytesize]
559
- else
560
- env[SERVER_NAME] = host
561
- env[SERVER_PORT] = default_server_port(env)
491
+ @events.unknown_error e, nil, "Client"
562
492
  end
563
- else
564
- env[SERVER_NAME] = LOCALHOST
565
- env[SERVER_PORT] = default_server_port(env)
566
- end
567
-
568
- unless env[REQUEST_PATH]
569
- # it might be a dumbass full host request header
570
- uri = URI.parse(env[REQUEST_URI])
571
- env[REQUEST_PATH] = uri.path
572
-
573
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
574
-
575
- # A nil env value will cause a LintError (and fatal errors elsewhere),
576
- # so only set the env value if there actually is a value.
577
- env[QUERY_STRING] = uri.query if uri.query
578
- end
579
-
580
- env[PATH_INFO] = env[REQUEST_PATH]
581
-
582
- # From http://www.ietf.org/rfc/rfc3875 :
583
- # "Script authors should be aware that the REMOTE_ADDR and
584
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
585
- # may not identify the ultimate source of the request.
586
- # They identify the client for the immediate request to the
587
- # server; that client may be a proxy, gateway, or other
588
- # intermediary acting on behalf of the actual source client."
589
- #
590
-
591
- unless env.key?(REMOTE_ADDR)
592
- begin
593
- addr = client.peerip
594
- rescue Errno::ENOTCONN
595
- # Client disconnects can result in an inability to get the
596
- # peeraddr from the socket; default to localhost.
597
- addr = LOCALHOST_IP
598
- end
599
-
600
- # Set unix socket addrs to localhost
601
- addr = LOCALHOST_IP if addr.empty?
602
-
603
- env[REMOTE_ADDR] = addr
604
493
  end
605
494
  end
606
495
 
607
- def default_server_port(env)
608
- 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"
609
- PORT_443
610
- else
611
- PORT_80
612
- 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!
613
502
  end
614
503
 
615
- # Takes the request +req+, invokes the Rack application to construct
616
- # the response and writes it back to +req.io+.
617
- #
618
- # The second parameter +lines+ is a IO-like object unique to this thread.
619
- # This is normally an instance of Puma::IOBuffer.
620
- #
621
- # It'll return +false+ when the connection is closed, this doesn't mean
622
- # that the response wasn't successful.
623
- #
624
- # It'll return +:async+ if the connection remains open but will be handled
625
- # elsewhere, i.e. the connection has been hijacked by the Rack application.
626
- #
627
- # Finally, it'll return +true+ on keep-alive connections.
628
- def handle_request(req, lines)
629
- env = req.env
630
- client = req.io
631
-
632
- return false if closed_socket?(client)
633
-
634
- normalize_env env, req
635
-
636
- env[PUMA_SOCKET] = client
637
-
638
- if env[HTTPS_KEY] && client.peercert
639
- env[PUMA_PEERCERT] = client.peercert
640
- end
641
-
642
- env[HIJACK_P] = true
643
- env[HIJACK] = req
644
-
645
- body = req.body
646
-
647
- head = env[REQUEST_METHOD] == HEAD
648
-
649
- env[RACK_INPUT] = body
650
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
651
-
652
- if @early_hints
653
- env[EARLY_HINTS] = lambda { |headers|
654
- begin
655
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
656
-
657
- headers.each_pair do |k, vs|
658
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
659
- vs.to_s.split(NEWLINE).each do |v|
660
- next if possible_header_injection?(v)
661
- fast_write client, "#{k}: #{v}\r\n"
662
- end
663
- else
664
- fast_write client, "#{k}: #{vs}\r\n"
665
- end
666
- end
667
-
668
- fast_write client, "\r\n".freeze
669
- rescue ConnectionError
670
- # noop, if we lost the socket we just won't send the early hints
671
- end
672
- }
673
- end
674
-
675
- # Fixup any headers with , in the name to have _ now. We emit
676
- # headers with , in them during the parse phase to avoid ambiguity
677
- # with the - to _ conversion for critical headers. But here for
678
- # compatibility, we'll convert them back. This code is written to
679
- # avoid allocation in the common case (ie there are no headers
680
- # with , in their names), that's why it has the extra conditionals.
681
-
682
- to_delete = nil
683
- to_add = nil
684
-
685
- env.each do |k,v|
686
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
687
- if to_delete
688
- to_delete << k
689
- else
690
- to_delete = [k]
691
- end
692
-
693
- unless to_add
694
- to_add = {}
695
- end
696
-
697
- to_add[k.gsub(",", "_")] = v
698
- end
699
- end
700
-
701
- if to_delete
702
- to_delete.each { |k| env.delete(k) }
703
- env.merge! to_add
704
- end
705
-
706
- # A rack extension. If the app writes #call'ables to this
707
- # array, we will invoke them when the request is done.
708
- #
709
- after_reply = env[RACK_AFTER_REPLY] = []
710
-
711
- begin
712
- begin
713
- status, headers, res_body = @app.call(env)
714
-
715
- return :async if req.hijacked
716
-
717
- status = status.to_i
718
-
719
- if status == -1
720
- unless headers.empty? and res_body == []
721
- raise "async response must have empty headers and body"
722
- end
723
-
724
- return :async
725
- end
726
- rescue ThreadPool::ForceShutdown => e
727
- @events.log "Detected force shutdown of a thread, returning 503"
728
- @events.unknown_error self, e, "Rack app"
504
+ # :nocov:
729
505
 
730
- status = 503
731
- headers = {}
732
- res_body = ["Request was internally terminated early\n"]
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)
733
510
 
734
- rescue Exception => e
735
- @events.unknown_error self, e, "Rack app", env
736
-
737
- status, headers, res_body = lowlevel_error(e, env)
738
- end
739
-
740
- content_length = nil
741
- no_body = head
742
-
743
- if res_body.kind_of? Array and res_body.size == 1
744
- content_length = res_body[0].bytesize
745
- end
746
-
747
- cork_socket client
748
-
749
- line_ending = LINE_END
750
- colon = COLON
751
-
752
- http_11 = if env[HTTP_VERSION] == HTTP_11
753
- allow_chunked = true
754
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
755
- include_keepalive_header = false
756
-
757
- # An optimization. The most common response is 200, so we can
758
- # reply with the proper 200 status without having to compute
759
- # the response header.
760
- #
761
- if status == 200
762
- lines << HTTP_11_200
763
- else
764
- lines.append "HTTP/1.1 ", status.to_s, " ",
765
- fetch_status_code(status), line_ending
766
-
767
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
768
- end
769
- true
770
- else
771
- allow_chunked = false
772
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
773
- include_keepalive_header = keep_alive
774
-
775
- # Same optimization as above for HTTP/1.1
776
- #
777
- if status == 200
778
- lines << HTTP_10_200
779
- else
780
- lines.append "HTTP/1.0 ", status.to_s, " ",
781
- fetch_status_code(status), line_ending
782
-
783
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
784
- end
785
- false
786
- end
787
-
788
- response_hijack = nil
789
-
790
- headers.each do |k, vs|
791
- case k.downcase
792
- when CONTENT_LENGTH2
793
- next if possible_header_injection?(vs)
794
- content_length = vs
795
- next
796
- when TRANSFER_ENCODING
797
- allow_chunked = false
798
- content_length = nil
799
- when HIJACK
800
- response_hijack = vs
801
- next
802
- end
803
-
804
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
805
- vs.to_s.split(NEWLINE).each do |v|
806
- next if possible_header_injection?(v)
807
- lines.append k, colon, v, line_ending
808
- end
809
- else
810
- lines.append k, colon, line_ending
811
- end
812
- end
813
-
814
- if include_keepalive_header
815
- lines << CONNECTION_KEEP_ALIVE
816
- elsif http_11 && !keep_alive
817
- lines << CONNECTION_CLOSE
818
- end
819
-
820
- if no_body
821
- if content_length and status != 204
822
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
823
- end
824
-
825
- lines << line_ending
826
- fast_write client, lines.to_s
827
- return keep_alive
828
- end
829
-
830
- if content_length
831
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
832
- chunked = false
833
- elsif !response_hijack and allow_chunked
834
- lines << TRANSFER_ENCODING_CHUNKED
835
- chunked = true
836
- end
837
-
838
- lines << line_ending
839
-
840
- fast_write client, lines.to_s
841
-
842
- if response_hijack
843
- response_hijack.call client
844
- return :async
845
- end
846
-
847
- begin
848
- res_body.each do |part|
849
- next if part.bytesize.zero?
850
- if chunked
851
- fast_write client, part.bytesize.to_s(16)
852
- fast_write client, line_ending
853
- fast_write client, part
854
- fast_write client, line_ending
855
- else
856
- fast_write client, part
857
- end
858
-
859
- client.flush
860
- end
861
-
862
- if chunked
863
- fast_write client, CLOSE_CHUNKED
864
- client.flush
865
- end
866
- rescue SystemCallError, IOError
867
- raise ConnectionError, "Connection error detected during write"
868
- end
869
-
870
- ensure
871
- uncork_socket client
872
-
873
- body.close
874
- req.tempfile.unlink if req.tempfile
875
- res_body.close if res_body.respond_to? :close
876
-
877
- after_reply.each { |o| o.call }
878
- end
879
-
880
- return keep_alive
881
- end
882
-
883
- def fetch_status_code(status)
884
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
885
- end
886
- private :fetch_status_code
887
-
888
- # Given the request +env+ from +client+ and the partial body +body+
889
- # plus a potential Content-Length value +cl+, finish reading
890
- # the body and return it.
891
- #
892
- # If the body is larger than MAX_BODY, a Tempfile object is used
893
- # for the body, otherwise a StringIO is used.
894
- #
895
- def read_body(env, client, body, cl)
896
- content_length = cl.to_i
897
-
898
- remain = content_length - body.bytesize
899
-
900
- return StringIO.new(body) if remain <= 0
901
-
902
- # Use a Tempfile if there is a lot of data left
903
- if remain > MAX_BODY
904
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
905
- 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
906
521
  else
907
- # The body[0,0] trick is to get an empty string in the same
908
- # encoding as body.
909
- stream = StringIO.new body[0,0]
910
- end
911
-
912
- stream.write body
913
-
914
- # Read an odd sized chunk so we can read even sized ones
915
- # after this
916
- chunk = client.readpartial(remain % CHUNK_SIZE)
917
-
918
- # No chunk means a closed socket
919
- unless chunk
920
- stream.close
921
- return nil
922
- end
923
-
924
- remain -= stream.write(chunk)
925
-
926
- # Raed the rest of the chunks
927
- while remain > 0
928
- chunk = client.readpartial(CHUNK_SIZE)
929
- unless chunk
930
- stream.close
931
- return nil
932
- end
933
-
934
- remain -= stream.write(chunk)
522
+ client.write_error(500)
523
+ @events.unknown_error e, nil, "Read"
935
524
  end
936
-
937
- stream.rewind
938
-
939
- return stream
940
525
  end
941
526
 
942
527
  # A fallback rack response if +@app+ raises as exception.
943
528
  #
944
- def lowlevel_error(e, env)
529
+ def lowlevel_error(e, env, status=500)
945
530
  if handler = @options[:lowlevel_error_handler]
946
531
  if handler.arity == 1
947
532
  return handler.call(e)
948
- else
533
+ elsif handler.arity == 2
949
534
  return handler.call(e, env)
535
+ else
536
+ return handler.call(e, env, status)
950
537
  end
951
538
  end
952
539
 
953
540
  if @leak_stack_on_error
954
- [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}"]]
955
543
  else
956
- [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"]]
957
545
  end
958
546
  end
959
547
 
@@ -975,35 +563,13 @@ module Puma
975
563
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
976
564
  end
977
565
 
978
- if @options[:drain_on_shutdown]
979
- count = 0
980
-
981
- while true
982
- ios = IO.select @binder.ios, nil, nil, 0
983
- break unless ios
984
-
985
- ios.first.each do |sock|
986
- begin
987
- if io = sock.accept_nonblock
988
- count += 1
989
- client = Client.new io, @binder.env(sock)
990
- @thread_pool << client
991
- end
992
- rescue SystemCallError
993
- end
994
- end
995
- end
996
-
997
- @events.debug "Drained #{count} additional connections."
998
- end
999
-
1000
566
  if @status != :restart
1001
567
  @binder.close
1002
568
  end
1003
569
 
1004
570
  if @thread_pool
1005
571
  if timeout = @options[:force_shutdown_after]
1006
- @thread_pool.shutdown timeout.to_i
572
+ @thread_pool.shutdown timeout.to_f
1007
573
  else
1008
574
  @thread_pool.shutdown
1009
575
  end
@@ -1011,18 +577,16 @@ module Puma
1011
577
  end
1012
578
 
1013
579
  def notify_safely(message)
1014
- begin
1015
- @notify << message
1016
- rescue IOError
1017
- # The server, in another thread, is shutting down
1018
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1019
- rescue RuntimeError => e
1020
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
1021
- if e.message.include?('IOError')
1022
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1023
- else
1024
- raise e
1025
- 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
1026
590
  end
1027
591
  end
1028
592
  private :notify_safely
@@ -1040,44 +604,24 @@ module Puma
1040
604
  @thread.join if @thread && sync
1041
605
  end
1042
606
 
1043
- def begin_restart
607
+ def begin_restart(sync=false)
1044
608
  notify_safely(RESTART_COMMAND)
1045
- end
1046
-
1047
- def fast_write(io, str)
1048
- n = 0
1049
- while true
1050
- begin
1051
- n = io.syswrite str
1052
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
1053
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
1054
- raise ConnectionError, "Socket timeout writing data"
1055
- end
1056
-
1057
- retry
1058
- rescue Errno::EPIPE, SystemCallError, IOError
1059
- raise ConnectionError, "Socket timeout writing data"
1060
- end
1061
-
1062
- return if n == str.bytesize
1063
- str = str.byteslice(n..-1)
1064
- end
1065
- end
1066
- private :fast_write
1067
-
1068
- ThreadLocalKey = :puma_server
1069
-
1070
- def self.current
1071
- Thread.current[ThreadLocalKey]
609
+ @thread.join if @thread && sync
1072
610
  end
1073
611
 
1074
612
  def shutting_down?
1075
613
  @status == :stop || @status == :restart
1076
614
  end
1077
615
 
1078
- def possible_header_injection?(header_value)
1079
- 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
1080
625
  end
1081
- private :possible_header_injection?
1082
626
  end
1083
627
  end