puma 4.3.12 → 6.0.2

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