puma 3.11.4 → 6.0.1

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