puma 4.3.5 → 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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1639 -519
  3. data/LICENSE +23 -20
  4. data/README.md +130 -42
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  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 +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/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 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +56 -11
  30. data/ext/puma_http11/http11_parser.c +69 -58
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +3 -3
  35. data/ext/puma_http11/mini_ssl.c +322 -130
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +52 -52
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +47 -57
  41. data/lib/puma/app/status.rb +53 -37
  42. data/lib/puma/binder.rb +232 -119
  43. data/lib/puma/cli.rb +33 -33
  44. data/lib/puma/client.rb +197 -101
  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 +224 -229
  48. data/lib/puma/commonlogger.rb +2 -2
  49. data/lib/puma/configuration.rb +112 -87
  50. data/lib/puma/const.rb +30 -25
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +31 -2
  53. data/lib/puma/dsl.rb +426 -110
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -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 +170 -148
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +35 -19
  63. data/lib/puma/minissl.rb +213 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/tmp_restart.rb +1 -1
  66. data/lib/puma/plugin.rb +3 -12
  67. data/lib/puma/rack/builder.rb +5 -9
  68. data/lib/puma/rack/urlmap.rb +0 -0
  69. data/lib/puma/rack_default.rb +1 -1
  70. data/lib/puma/reactor.rb +85 -369
  71. data/lib/puma/request.rb +644 -0
  72. data/lib/puma/runner.rb +83 -77
  73. data/lib/puma/server.rb +303 -773
  74. data/lib/puma/single.rb +18 -74
  75. data/lib/puma/state_file.rb +45 -8
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +136 -68
  78. data/lib/puma/util.rb +21 -4
  79. data/lib/puma.rb +54 -5
  80. data/lib/rack/handler/puma.rb +11 -12
  81. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  82. data/tools/trickletest.rb +0 -0
  83. metadata +36 -28
  84. data/docs/tcp_mode.md +0 -96
  85. data/ext/puma_http11/io_buffer.c +0 -155
  86. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  87. data/lib/puma/accept_nonblock.rb +0 -29
  88. data/lib/puma/tcp_logger.rb +0 -41
  89. data/tools/jungle/README.md +0 -19
  90. data/tools/jungle/init.d/README.md +0 -61
  91. data/tools/jungle/init.d/puma +0 -421
  92. data/tools/jungle/init.d/run-puma +0 -18
  93. data/tools/jungle/upstart/README.md +0 -61
  94. data/tools/jungle/upstart/puma-manager.conf +0 -31
  95. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/server.rb CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  require 'stringio'
4
4
 
5
- require 'puma/thread_pool'
6
- require 'puma/const'
7
- require 'puma/events'
8
- require 'puma/null_io'
9
- require 'puma/reactor'
10
- require 'puma/client'
11
- require 'puma/binder'
12
- require 'puma/accept_nonblock'
13
- require 'puma/util'
14
-
15
- require 'puma/puma_http11'
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'
16
15
 
17
16
  require 'socket'
17
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
18
  require 'forwardable'
19
19
 
20
20
  module Puma
@@ -30,108 +30,155 @@ module Puma
30
30
  #
31
31
  # Each `Puma::Server` will have one reactor and one thread pool.
32
32
  class Server
33
-
34
33
  include Puma::Const
34
+ include Request
35
35
  extend Forwardable
36
36
 
37
37
  attr_reader :thread
38
+ attr_reader :log_writer
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
+
39
48
  attr_accessor :app
49
+ attr_accessor :binder
40
50
 
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
51
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
52
+ :add_unix_listener, :connected_ports
53
+
54
+ ThreadLocalKey = :puma_server
47
55
 
48
56
  # Create a server for the rack app +app+.
49
57
  #
50
- # +events+ is an object which will be called when certain error events occur
51
- # 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.
52
61
  #
53
62
  # Server#run returns a thread that you can join on to wait for the server
54
63
  # to do its work.
55
64
  #
56
- 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 = {})
57
74
  @app = app
58
- @events = events
59
-
60
- @check, @notify = Puma::Util.pipe
75
+ @events = events || Events.new
61
76
 
77
+ @check, @notify = nil
62
78
  @status = :stop
63
79
 
64
- @min_threads = 0
65
- @max_threads = 16
66
- @auto_trim_time = 30
67
- @reaping_time = 1
68
-
69
80
  @thread = nil
70
81
  @thread_pool = nil
71
- @early_hints = nil
72
82
 
73
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
- @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
75
88
 
76
- @binder = Binder.new(events)
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]
77
98
 
78
- @leak_stack_on_error = true
99
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
100
+ @leak_stack_on_error = @options[:environment] ? temp : true
79
101
 
80
- @options = options
81
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
102
+ @binder = Binder.new(log_writer)
82
103
 
83
104
  ENV['RACK_ENV'] ||= "development"
84
105
 
85
106
  @mode = :http
86
107
 
87
108
  @precheck_closing = true
88
- end
89
109
 
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
110
+ @requests_count = 0
111
+ end
93
112
 
94
113
  def inherit_binder(bind)
95
114
  @binder = bind
96
115
  end
97
116
 
98
- def tcp_mode!
99
- @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?
100
136
  end
101
137
 
102
138
  # On Linux, use TCP_CORK to better control how the TCP stack
103
139
  # packetizes our stream. This improves both latency and throughput.
140
+ # socket parameter may be an MiniSSL::Socket, so use to_io
104
141
  #
105
- if RUBY_PLATFORM =~ /linux/
106
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
-
142
+ if tcp_cork_supported?
108
143
  # 6 == Socket::IPPROTO_TCP
109
144
  # 3 == TCP_CORK
110
145
  # 1/0 == turn on/off
111
146
  def cork_socket(socket)
147
+ skt = socket.to_io
112
148
  begin
113
- 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
114
150
  rescue IOError, SystemCallError
115
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
151
+ Puma::Util.purge_interrupt_queue
116
152
  end
117
153
  end
118
154
 
119
155
  def uncork_socket(socket)
156
+ skt = socket.to_io
120
157
  begin
121
- 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
122
159
  rescue IOError, SystemCallError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
160
+ Puma::Util.purge_interrupt_queue
124
161
  end
125
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
126
173
 
127
174
  def closed_socket?(socket)
128
- return false unless socket.kind_of? TCPSocket
129
- return false unless @precheck_closing
175
+ skt = socket.to_io
176
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
130
177
 
131
178
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
179
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
180
  rescue IOError, SystemCallError
134
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
181
+ Puma::Util.purge_interrupt_queue
135
182
  @precheck_closing = false
136
183
  false
137
184
  else
@@ -141,23 +188,19 @@ module Puma
141
188
  end
142
189
  end
143
190
  else
144
- def cork_socket(socket)
145
- end
146
-
147
- def uncork_socket(socket)
148
- end
149
-
150
191
  def closed_socket?(socket)
151
192
  false
152
193
  end
153
194
  end
154
195
 
196
+ # @!attribute [r] backlog
155
197
  def backlog
156
- @thread_pool and @thread_pool.backlog
198
+ @thread_pool&.backlog
157
199
  end
158
200
 
201
+ # @!attribute [r] running
159
202
  def running
160
- @thread_pool and @thread_pool.spawned
203
+ @thread_pool&.spawned
161
204
  end
162
205
 
163
206
 
@@ -168,191 +211,42 @@ module Puma
168
211
  # there are 5 threads sitting idle ready to take
169
212
  # a request. If one request comes in, then the
170
213
  # value would be 4 until it finishes processing.
214
+ # @!attribute [r] pool_capacity
171
215
  def pool_capacity
172
- @thread_pool and @thread_pool.pool_capacity
173
- end
174
-
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
216
+ @thread_pool&.pool_capacity
215
217
  end
216
218
 
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
219
  # Runs the server.
277
220
  #
278
221
  # If +background+ is true (the default) then a thread is spun
279
222
  # up in the background to handle requests. Otherwise requests
280
223
  # are handled synchronously.
281
224
  #
282
- def run(background=true)
225
+ def run(background=true, thread_name: 'srv')
283
226
  BasicSocket.do_not_reverse_lookup = true
284
227
 
285
228
  @events.fire :state, :booting
286
229
 
287
230
  @status = :run
288
231
 
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
232
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
315
233
 
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
234
+ if @queue_requests
235
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
236
+ @reactor.run
334
237
  end
335
238
 
336
- @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
239
 
338
- if queue_requests
339
- @reactor = Reactor.new self, @thread_pool
340
- @reactor.run_in_thread
341
- end
240
+ @thread_pool.auto_reap! if @options[:reaping_time]
241
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
342
242
 
343
- if @reaping_time
344
- @thread_pool.auto_reap!(@reaping_time)
345
- end
346
-
347
- if @auto_trim_time
348
- @thread_pool.auto_trim!(@auto_trim_time)
349
- end
243
+ @check, @notify = Puma::Util.pipe unless @notify
350
244
 
351
245
  @events.fire :state, :running
352
246
 
353
247
  if background
354
248
  @thread = Thread.new do
355
- Puma.set_thread_name "server"
249
+ Puma.set_thread_name thread_name
356
250
  handle_servers
357
251
  end
358
252
  return @thread
@@ -361,75 +255,117 @@ module Puma
361
255
  end
362
256
  end
363
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
+
364
299
  def handle_servers
365
300
  begin
366
301
  check = @check
367
302
  sockets = [check] + @binder.ios
368
303
  pool = @thread_pool
369
304
  queue_requests = @queue_requests
305
+ drain = @options[:drain_on_shutdown] ? 0 : nil
370
306
 
371
- remote_addr_value = nil
372
- remote_addr_header = nil
373
-
374
- case @options[:remote_address]
307
+ addr_send_name, addr_value = case @options[:remote_address]
375
308
  when :value
376
- remote_addr_value = @options[:remote_address_value]
309
+ [:peerip=, @options[:remote_address_value]]
377
310
  when :header
378
- 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]
379
316
  end
380
317
 
381
- while @status == :run
318
+ while @status == :run || (drain && shutting_down?)
382
319
  begin
383
- ios = IO.select sockets
320
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
321
+ break unless ios
384
322
  ios.first.each do |sock|
385
323
  if sock == check
386
324
  break if handle_check
387
325
  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
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
412
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
+ }
413
339
  end
414
340
  end
415
- rescue Object => e
416
- @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"
417
346
  end
418
347
  end
419
348
 
349
+ @log_writer.debug "Drained #{drain} additional connections." if drain
420
350
  @events.fire :state, @status
421
351
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
352
  if queue_requests
424
- @reactor.clear!
353
+ @queue_requests = false
425
354
  @reactor.shutdown
426
355
  end
356
+ graceful_shutdown if @status == :stop || @status == :restart
427
357
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
358
+ @log_writer.unknown_error e, nil, "Exception handling servers"
430
359
  ensure
431
- @check.close
432
- @notify.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
366
+ end
367
+ @notify = nil
368
+ @check = nil
433
369
  end
434
370
 
435
371
  @events.fire :state, :done
@@ -451,509 +387,147 @@ module Puma
451
387
  return true
452
388
  end
453
389
 
454
- return false
390
+ false
455
391
  end
456
392
 
457
- # 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.
458
397
  #
459
- # 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
460
399
  # indicates that it supports keep alive, wait for another request before
461
400
  # returning.
462
401
  #
463
- 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
+
464
412
  begin
413
+ if @queue_requests &&
414
+ !client.eagerly_finish
465
415
 
466
- clean_thread_locals = @options[:clean_thread_locals]
467
- close_socket = true
416
+ client.set_timeout(@first_data_timeout)
417
+ if @reactor.add client
418
+ close_socket = false
419
+ return false
420
+ end
421
+ end
468
422
 
469
- requests = 0
423
+ with_force_shutdown(client) do
424
+ client.finish(@first_data_timeout)
425
+ end
470
426
 
471
427
  while true
472
- case handle_request(client, buffer)
428
+ @requests_count += 1
429
+ case handle_request(client, requests + 1)
473
430
  when false
474
- return
431
+ break
475
432
  when :async
476
433
  close_socket = false
477
- return
434
+ break
478
435
  when true
479
- return unless @queue_requests
480
- buffer.reset
481
-
482
436
  ThreadPool.clean_thread_locals if clean_thread_locals
483
437
 
484
438
  requests += 1
485
439
 
486
- check_for_more_data = @status == :run
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
487
443
 
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
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
448
+
449
+ next_request_ready = with_force_shutdown(client) do
450
+ client.reset(fast_check)
494
451
  end
495
452
 
496
- unless client.reset(check_for_more_data)
497
- close_socket = false
453
+ unless next_request_ready
454
+ break unless @queue_requests
498
455
  client.set_timeout @persistent_timeout
499
- @reactor.add client
500
- return
456
+ if @reactor.add client
457
+ close_socket = false
458
+ break
459
+ end
501
460
  end
502
461
  end
503
462
  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
463
+ true
530
464
  rescue StandardError => e
531
- lowlevel_error(e, client.env)
532
-
533
- client.write_error(500)
534
-
535
- @events.unknown_error self, e, "Read"
536
-
465
+ client_error(e, client)
466
+ # The ensure tries to close +client+ down
467
+ requests > 0
537
468
  ensure
538
- buffer.reset
469
+ client.io_buffer.reset
539
470
 
540
471
  begin
541
472
  client.close if close_socket
542
473
  rescue IOError, SystemCallError
543
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
474
+ Puma::Util.purge_interrupt_queue
544
475
  # Already closed
545
476
  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)
562
- 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
- end
605
- end
606
-
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
613
- end
614
-
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"
729
-
730
- status = 503
731
- headers = {}
732
- res_body = ["Request was internally terminated early\n"]
733
-
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
477
+ @log_writer.unknown_error e, nil, "Client"
845
478
  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
479
  end
879
-
880
- return keep_alive
881
480
  end
882
481
 
883
- def fetch_status_code(status)
884
- 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!
885
488
  end
886
- private :fetch_status_code
887
489
 
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
490
+ # :nocov:
897
491
 
898
- remain = content_length - body.bytesize
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)
899
496
 
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
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
906
507
  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)
508
+ client.write_error(500)
509
+ @log_writer.unknown_error e, nil, "Read"
935
510
  end
936
-
937
- stream.rewind
938
-
939
- return stream
940
511
  end
941
512
 
942
513
  # A fallback rack response if +@app+ raises as exception.
943
514
  #
944
- def lowlevel_error(e, env)
515
+ def lowlevel_error(e, env, status=500)
945
516
  if handler = @options[:lowlevel_error_handler]
946
517
  if handler.arity == 1
947
518
  return handler.call(e)
948
- else
519
+ elsif handler.arity == 2
949
520
  return handler.call(e, env)
521
+ else
522
+ return handler.call(e, env, status)
950
523
  end
951
524
  end
952
525
 
953
526
  if @leak_stack_on_error
954
- [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}"]]
955
529
  else
956
- [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"]]
957
531
  end
958
532
  end
959
533
 
@@ -975,35 +549,13 @@ module Puma
975
549
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
976
550
  end
977
551
 
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
552
  if @status != :restart
1001
553
  @binder.close
1002
554
  end
1003
555
 
1004
556
  if @thread_pool
1005
557
  if timeout = @options[:force_shutdown_after]
1006
- @thread_pool.shutdown timeout.to_i
558
+ @thread_pool.shutdown timeout.to_f
1007
559
  else
1008
560
  @thread_pool.shutdown
1009
561
  end
@@ -1011,18 +563,16 @@ module Puma
1011
563
  end
1012
564
 
1013
565
  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
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
1026
576
  end
1027
577
  end
1028
578
  private :notify_safely
@@ -1040,44 +590,24 @@ module Puma
1040
590
  @thread.join if @thread && sync
1041
591
  end
1042
592
 
1043
- def begin_restart
593
+ def begin_restart(sync=false)
1044
594
  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]
595
+ @thread.join if @thread && sync
1072
596
  end
1073
597
 
1074
598
  def shutting_down?
1075
599
  @status == :stop || @status == :restart
1076
600
  end
1077
601
 
1078
- def possible_header_injection?(header_value)
1079
- HTTP_INJECTION_REGEX =~ header_value.to_s
602
+ # List of methods invoked by #stats.
603
+ # @version 5.0.0
604
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
605
+
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
1080
611
  end
1081
- private :possible_header_injection?
1082
612
  end
1083
613
  end