puma 4.3.12 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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 +1591 -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 +1 -1
  16. data/docs/plugins.md +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/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 +125 -87
  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 -229
  45. data/lib/puma/commonlogger.rb +2 -2
  46. data/lib/puma/configuration.rb +112 -87
  47. data/lib/puma/const.rb +25 -22
  48. data/lib/puma/control_cli.rb +99 -79
  49. data/lib/puma/detect.rb +31 -2
  50. data/lib/puma/dsl.rb +423 -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 +34 -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 +607 -0
  68. data/lib/puma/runner.rb +83 -77
  69. data/lib/puma/server.rb +305 -789
  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 +137 -66
  74. data/lib/puma/util.rb +21 -4
  75. data/lib/puma.rb +54 -5
  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,20 @@
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 'io_buffer'
15
+ require_relative 'request'
16
16
 
17
17
  require 'socket'
18
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
19
  require 'forwardable'
19
20
 
20
21
  module Puma
@@ -30,108 +31,155 @@ module Puma
30
31
  #
31
32
  # Each `Puma::Server` will have one reactor and one thread pool.
32
33
  class Server
33
-
34
34
  include Puma::Const
35
+ include Request
35
36
  extend Forwardable
36
37
 
37
38
  attr_reader :thread
39
+ attr_reader :log_writer
38
40
  attr_reader :events
41
+ attr_reader :min_threads, :max_threads # for #stats
42
+ attr_reader :requests_count # @version 5.0.0
43
+
44
+ # @todo the following may be deprecated in the future
45
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
46
+ :leak_stack_on_error,
47
+ :persistent_timeout, :reaping_time
48
+
39
49
  attr_accessor :app
50
+ attr_accessor :binder
40
51
 
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
52
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
53
+ :add_unix_listener, :connected_ports
54
+
55
+ ThreadLocalKey = :puma_server
47
56
 
48
57
  # Create a server for the rack app +app+.
49
58
  #
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.
59
+ # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
60
+ #
61
+ # +events+ is a Puma::Events object used to notify application status events.
52
62
  #
53
63
  # Server#run returns a thread that you can join on to wait for the server
54
64
  # to do its work.
55
65
  #
56
- def initialize(app, events=Events.stdio, options={})
66
+ # @note Several instance variables exist so they are available for testing,
67
+ # and have default values set via +fetch+. Normally the values are set via
68
+ # `::Puma::Configuration.puma_default_options`.
69
+ #
70
+ # @note The `events` parameter is set to nil, and set to `Events.new` in code.
71
+ # Often `options` needs to be passed, but `events` does not. Using nil allows
72
+ # calling code to not require events.rb.
73
+ #
74
+ def initialize(app, events = nil, options = {})
57
75
  @app = app
58
- @events = events
59
-
60
- @check, @notify = Puma::Util.pipe
76
+ @events = events || Events.new
61
77
 
78
+ @check, @notify = nil
62
79
  @status = :stop
63
80
 
64
- @min_threads = 0
65
- @max_threads = 16
66
- @auto_trim_time = 30
67
- @reaping_time = 1
68
-
69
81
  @thread = nil
70
82
  @thread_pool = nil
71
- @early_hints = nil
72
83
 
73
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
84
+ @options = if options.is_a?(UserFileDefaultOptions)
85
+ options
86
+ else
87
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
88
+ end
75
89
 
76
- @binder = Binder.new(events)
90
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
91
+ @early_hints = @options[:early_hints]
92
+ @first_data_timeout = @options[:first_data_timeout]
93
+ @min_threads = @options[:min_threads]
94
+ @max_threads = @options[:max_threads]
95
+ @persistent_timeout = @options[:persistent_timeout]
96
+ @queue_requests = @options[:queue_requests]
97
+ @max_fast_inline = @options[:max_fast_inline]
98
+ @io_selector_backend = @options[:io_selector_backend]
77
99
 
78
- @leak_stack_on_error = true
100
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
101
+ @leak_stack_on_error = @options[:environment] ? temp : true
79
102
 
80
- @options = options
81
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
103
+ @binder = Binder.new(log_writer)
82
104
 
83
105
  ENV['RACK_ENV'] ||= "development"
84
106
 
85
107
  @mode = :http
86
108
 
87
109
  @precheck_closing = true
88
- end
89
110
 
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
111
+ @requests_count = 0
112
+ end
93
113
 
94
114
  def inherit_binder(bind)
95
115
  @binder = bind
96
116
  end
97
117
 
98
- def tcp_mode!
99
- @mode = :tcp
118
+ class << self
119
+ # @!attribute [r] current
120
+ def current
121
+ Thread.current[ThreadLocalKey]
122
+ end
123
+
124
+ # :nodoc:
125
+ # @version 5.0.0
126
+ def tcp_cork_supported?
127
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
128
+ end
129
+
130
+ # :nodoc:
131
+ # @version 5.0.0
132
+ def closed_socket_supported?
133
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
134
+ end
135
+ private :tcp_cork_supported?
136
+ private :closed_socket_supported?
100
137
  end
101
138
 
102
139
  # On Linux, use TCP_CORK to better control how the TCP stack
103
140
  # packetizes our stream. This improves both latency and throughput.
141
+ # socket parameter may be an MiniSSL::Socket, so use to_io
104
142
  #
105
- if RUBY_PLATFORM =~ /linux/
106
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
-
143
+ if tcp_cork_supported?
108
144
  # 6 == Socket::IPPROTO_TCP
109
145
  # 3 == TCP_CORK
110
146
  # 1/0 == turn on/off
111
147
  def cork_socket(socket)
148
+ skt = socket.to_io
112
149
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
150
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
114
151
  rescue IOError, SystemCallError
115
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
152
+ Puma::Util.purge_interrupt_queue
116
153
  end
117
154
  end
118
155
 
119
156
  def uncork_socket(socket)
157
+ skt = socket.to_io
120
158
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
159
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
122
160
  rescue IOError, SystemCallError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
161
+ Puma::Util.purge_interrupt_queue
124
162
  end
125
163
  end
164
+ else
165
+ def cork_socket(socket)
166
+ end
167
+
168
+ def uncork_socket(socket)
169
+ end
170
+ end
171
+
172
+ if closed_socket_supported?
173
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
126
174
 
127
175
  def closed_socket?(socket)
128
- return false unless socket.kind_of? TCPSocket
129
- return false unless @precheck_closing
176
+ skt = socket.to_io
177
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
130
178
 
131
179
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
180
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
181
  rescue IOError, SystemCallError
134
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
182
+ Puma::Util.purge_interrupt_queue
135
183
  @precheck_closing = false
136
184
  false
137
185
  else
@@ -141,23 +189,19 @@ module Puma
141
189
  end
142
190
  end
143
191
  else
144
- def cork_socket(socket)
145
- end
146
-
147
- def uncork_socket(socket)
148
- end
149
-
150
192
  def closed_socket?(socket)
151
193
  false
152
194
  end
153
195
  end
154
196
 
197
+ # @!attribute [r] backlog
155
198
  def backlog
156
- @thread_pool and @thread_pool.backlog
199
+ @thread_pool&.backlog
157
200
  end
158
201
 
202
+ # @!attribute [r] running
159
203
  def running
160
- @thread_pool and @thread_pool.spawned
204
+ @thread_pool&.spawned
161
205
  end
162
206
 
163
207
 
@@ -168,195 +212,42 @@ module Puma
168
212
  # there are 5 threads sitting idle ready to take
169
213
  # a request. If one request comes in, then the
170
214
  # value would be 4 until it finishes processing.
215
+ # @!attribute [r] pool_capacity
171
216
  def pool_capacity
172
- @thread_pool and @thread_pool.pool_capacity
217
+ @thread_pool&.pool_capacity
173
218
  end
174
219
 
175
- # Lopez Mode == raw tcp apps
176
-
177
- def run_lopez_mode(background=true)
178
- @thread_pool = ThreadPool.new(@min_threads,
179
- @max_threads,
180
- Hash) do |client, tl|
181
-
182
- io = client.to_io
183
- addr = io.peeraddr.last
184
-
185
- if addr.empty?
186
- # Set unix socket addrs to localhost
187
- addr = "127.0.0.1:0"
188
- else
189
- addr = "#{addr}:#{io.peeraddr[1]}"
190
- end
191
-
192
- env = { 'thread' => tl, REMOTE_ADDR => addr }
193
-
194
- begin
195
- @app.call env, client.to_io
196
- rescue Object => e
197
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
198
- STDERR.puts e.backtrace
199
- end
200
-
201
- client.close unless env['detach']
202
- end
203
-
204
- @events.fire :state, :running
205
-
206
- if background
207
- @thread = Thread.new do
208
- Puma.set_thread_name "server"
209
- handle_servers_lopez_mode
210
- end
211
- return @thread
212
- else
213
- handle_servers_lopez_mode
214
- end
215
- end
216
-
217
- def handle_servers_lopez_mode
218
- begin
219
- check = @check
220
- sockets = [check] + @binder.ios
221
- pool = @thread_pool
222
-
223
- while @status == :run
224
- begin
225
- ios = IO.select sockets
226
- ios.first.each do |sock|
227
- if sock == check
228
- break if handle_check
229
- else
230
- begin
231
- if io = sock.accept_nonblock
232
- client = Client.new io, nil
233
- pool << client
234
- end
235
- rescue SystemCallError
236
- # nothing
237
- rescue Errno::ECONNABORTED
238
- # client closed the socket even before accept
239
- begin
240
- io.close
241
- rescue
242
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
- end
244
- end
245
- end
246
- end
247
- rescue Object => e
248
- @events.unknown_error self, e, "Listen loop"
249
- end
250
- end
251
-
252
- @events.fire :state, @status
253
-
254
- graceful_shutdown if @status == :stop || @status == :restart
255
-
256
- rescue Exception => e
257
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
258
- STDERR.puts e.backtrace
259
- ensure
260
- begin
261
- @check.close
262
- rescue
263
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
- end
265
-
266
- # Prevent can't modify frozen IOError (RuntimeError)
267
- begin
268
- @notify.close
269
- rescue IOError
270
- # no biggy
271
- end
272
- end
273
-
274
- @events.fire :state, :done
275
- end
276
220
  # Runs the server.
277
221
  #
278
222
  # If +background+ is true (the default) then a thread is spun
279
223
  # up in the background to handle requests. Otherwise requests
280
224
  # are handled synchronously.
281
225
  #
282
- def run(background=true)
226
+ def run(background=true, thread_name: 'srv')
283
227
  BasicSocket.do_not_reverse_lookup = true
284
228
 
285
229
  @events.fire :state, :booting
286
230
 
287
231
  @status = :run
288
232
 
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
233
+ @thread_pool = ThreadPool.new(thread_name, @options) { |a, b| process_client a, b }
301
234
 
302
- process_now = false
303
-
304
- begin
305
- if queue_requests
306
- process_now = client.eagerly_finish
307
- else
308
- client.finish
309
- process_now = true
310
- end
311
- rescue MiniSSL::SSLError => e
312
- ssl_socket = client.io
313
- addr = ssl_socket.peeraddr.last
314
- cert = ssl_socket.peercert
315
-
316
- client.close
317
-
318
- @events.ssl_error self, addr, cert, e
319
- rescue HttpParserError => e
320
- client.write_error(400)
321
- client.close
322
-
323
- @events.parse_error self, client.env, e
324
- rescue HttpParserError501 => e
325
- client.write_error(501)
326
- client.close
327
- @events.parse_error self, client.env, e
328
- rescue ConnectionError, EOFError
329
- client.close
330
- else
331
- if process_now
332
- process_client client, buffer
333
- else
334
- client.set_timeout @first_data_timeout
335
- @reactor.add client
336
- end
337
- end
235
+ if @queue_requests
236
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
237
+ @reactor.run
338
238
  end
339
239
 
340
- @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
341
240
 
342
- if queue_requests
343
- @reactor = Reactor.new self, @thread_pool
344
- @reactor.run_in_thread
345
- end
241
+ @thread_pool.auto_reap! if @options[:reaping_time]
242
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
346
243
 
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
244
+ @check, @notify = Puma::Util.pipe unless @notify
354
245
 
355
246
  @events.fire :state, :running
356
247
 
357
248
  if background
358
249
  @thread = Thread.new do
359
- Puma.set_thread_name "server"
250
+ Puma.set_thread_name thread_name
360
251
  handle_servers
361
252
  end
362
253
  return @thread
@@ -365,75 +256,117 @@ module Puma
365
256
  end
366
257
  end
367
258
 
259
+ # This method is called from the Reactor thread when a queued Client receives data,
260
+ # times out, or when the Reactor is shutting down.
261
+ #
262
+ # It is responsible for ensuring that a request has been completely received
263
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
264
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
265
+ # such as nginx) then the application would be subject to a slow client attack.
266
+ #
267
+ # 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).
268
+ #
269
+ # The method checks to see if it has the full header and body with
270
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
271
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
272
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
273
+ # The Client is then removed from the reactor (return `true`).
274
+ #
275
+ # If a client object times out, a 408 response is written, its connection is closed,
276
+ # and the object is removed from the reactor (return `true`).
277
+ #
278
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
279
+ # ThreadPool, depending on their current state (#can_close?).
280
+ #
281
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
282
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
283
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
284
+ def reactor_wakeup(client)
285
+ shutdown = !@queue_requests
286
+ if client.try_to_finish || (shutdown && !client.can_close?)
287
+ @thread_pool << client
288
+ elsif shutdown || client.timeout == 0
289
+ client.timeout!
290
+ else
291
+ client.set_timeout(@first_data_timeout)
292
+ false
293
+ end
294
+ rescue StandardError => e
295
+ client_error(e, client)
296
+ client.close
297
+ true
298
+ end
299
+
368
300
  def handle_servers
369
301
  begin
370
302
  check = @check
371
303
  sockets = [check] + @binder.ios
372
304
  pool = @thread_pool
373
305
  queue_requests = @queue_requests
306
+ drain = @options[:drain_on_shutdown] ? 0 : nil
374
307
 
375
- remote_addr_value = nil
376
- remote_addr_header = nil
377
-
378
- case @options[:remote_address]
308
+ addr_send_name, addr_value = case @options[:remote_address]
379
309
  when :value
380
- remote_addr_value = @options[:remote_address_value]
310
+ [:peerip=, @options[:remote_address_value]]
381
311
  when :header
382
- remote_addr_header = @options[:remote_address_header]
312
+ [:remote_addr_header=, @options[:remote_address_header]]
313
+ when :proxy_protocol
314
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
315
+ else
316
+ [nil, nil]
383
317
  end
384
318
 
385
- while @status == :run
319
+ while @status == :run || (drain && shutting_down?)
386
320
  begin
387
- ios = IO.select sockets
321
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
322
+ break unless ios
388
323
  ios.first.each do |sock|
389
324
  if sock == check
390
325
  break if handle_check
391
326
  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
327
+ pool.wait_until_not_full
328
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
329
+
330
+ io = begin
331
+ sock.accept_nonblock
332
+ rescue IO::WaitReadable
333
+ next
416
334
  end
335
+ drain += 1 if shutting_down?
336
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
337
+ c.listener = sock
338
+ c.send(addr_send_name, addr_value) if addr_value
339
+ }
417
340
  end
418
341
  end
419
- rescue Object => e
420
- @events.unknown_error self, e, "Listen loop"
342
+ rescue IOError, Errno::EBADF
343
+ # In the case that any of the sockets are unexpectedly close.
344
+ raise
345
+ rescue StandardError => e
346
+ @log_writer.unknown_error e, nil, "Listen loop"
421
347
  end
422
348
  end
423
349
 
350
+ @log_writer.debug "Drained #{drain} additional connections." if drain
424
351
  @events.fire :state, @status
425
352
 
426
- graceful_shutdown if @status == :stop || @status == :restart
427
353
  if queue_requests
428
- @reactor.clear!
354
+ @queue_requests = false
429
355
  @reactor.shutdown
430
356
  end
357
+ graceful_shutdown if @status == :stop || @status == :restart
431
358
  rescue Exception => e
432
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
433
- STDERR.puts e.backtrace
359
+ @log_writer.unknown_error e, nil, "Exception handling servers"
434
360
  ensure
435
- @check.close
436
- @notify.close
361
+ # Errno::EBADF is infrequently raised
362
+ [@check, @notify].each do |io|
363
+ begin
364
+ io.close unless io.closed?
365
+ rescue Errno::EBADF
366
+ end
367
+ end
368
+ @notify = nil
369
+ @check = nil
437
370
  end
438
371
 
439
372
  @events.fire :state, :done
@@ -455,522 +388,149 @@ module Puma
455
388
  return true
456
389
  end
457
390
 
458
- return false
391
+ false
459
392
  end
460
393
 
461
- # Given a connection on +client+, handle the incoming requests.
394
+ # Given a connection on +client+, handle the incoming requests,
395
+ # or queue the connection in the Reactor if no request is available.
396
+ #
397
+ # This method is called from a ThreadPool worker thread.
462
398
  #
463
- # This method support HTTP Keep-Alive so it may, depending on if the client
399
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
464
400
  # indicates that it supports keep alive, wait for another request before
465
401
  # returning.
466
402
  #
403
+ # Return true if one or more requests were processed.
467
404
  def process_client(client, buffer)
405
+ # Advertise this server into the thread
406
+ Thread.current[ThreadLocalKey] = self
407
+
408
+ clean_thread_locals = @options[:clean_thread_locals]
409
+ close_socket = true
410
+
411
+ requests = 0
412
+
468
413
  begin
414
+ if @queue_requests &&
415
+ !client.eagerly_finish
469
416
 
470
- clean_thread_locals = @options[:clean_thread_locals]
471
- close_socket = true
417
+ client.set_timeout(@first_data_timeout)
418
+ if @reactor.add client
419
+ close_socket = false
420
+ return false
421
+ end
422
+ end
472
423
 
473
- requests = 0
424
+ with_force_shutdown(client) do
425
+ client.finish(@first_data_timeout)
426
+ end
474
427
 
475
428
  while true
476
- case handle_request(client, buffer)
429
+ @requests_count += 1
430
+ case handle_request(client, buffer, requests + 1)
477
431
  when false
478
- return
432
+ break
479
433
  when :async
480
434
  close_socket = false
481
- return
435
+ break
482
436
  when true
483
- return unless @queue_requests
484
437
  buffer.reset
485
438
 
486
439
  ThreadPool.clean_thread_locals if clean_thread_locals
487
440
 
488
441
  requests += 1
489
442
 
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
443
+ # As an optimization, try to read the next request from the
444
+ # socket for a short time before returning to the reactor.
445
+ fast_check = @status == :run
446
+
447
+ # Always pass the client back to the reactor after a reasonable
448
+ # number of inline requests if there are other requests pending.
449
+ fast_check = false if requests >= @max_fast_inline &&
450
+ @thread_pool.backlog > 0
451
+
452
+ next_request_ready = with_force_shutdown(client) do
453
+ client.reset(fast_check)
454
+ end
455
+
456
+ unless next_request_ready
457
+ break unless @queue_requests
507
458
  client.set_timeout @persistent_timeout
508
- @reactor.add client
509
- return
459
+ if @reactor.add client
460
+ close_socket = false
461
+ break
462
+ end
510
463
  end
511
464
  end
512
465
  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
466
+ true
544
467
  rescue StandardError => e
545
- lowlevel_error(e, client.env)
546
-
547
- client.write_error(500)
548
-
549
- @events.unknown_error self, e, "Read"
550
-
468
+ client_error(e, client)
469
+ # The ensure tries to close +client+ down
470
+ requests > 0
551
471
  ensure
552
472
  buffer.reset
553
473
 
554
474
  begin
555
475
  client.close if close_socket
556
476
  rescue IOError, SystemCallError
557
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
477
+ Puma::Util.purge_interrupt_queue
558
478
  # Already closed
559
479
  rescue StandardError => e
560
- @events.unknown_error self, e, "Client"
561
- end
562
- end
563
- end
564
-
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)
480
+ @log_writer.unknown_error e, nil, "Client"
576
481
  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
482
  end
896
-
897
- return keep_alive
898
483
  end
899
484
 
900
- def fetch_status_code(status)
901
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
485
+ # Triggers a client timeout if the thread-pool shuts down
486
+ # during execution of the provided block.
487
+ def with_force_shutdown(client, &block)
488
+ @thread_pool.with_force_shutdown(&block)
489
+ rescue ThreadPool::ForceShutdown
490
+ client.timeout!
902
491
  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
492
 
915
- remain = content_length - body.bytesize
493
+ # :nocov:
916
494
 
917
- return StringIO.new(body) if remain <= 0
495
+ # Handle various error types thrown by Client I/O operations.
496
+ def client_error(e, client)
497
+ # Swallow, do not log
498
+ return if [ConnectionError, EOFError].include?(e.class)
918
499
 
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
500
+ lowlevel_error(e, client.env)
501
+ case e
502
+ when MiniSSL::SSLError
503
+ @log_writer.ssl_error e, client.io
504
+ when HttpParserError
505
+ client.write_error(400)
506
+ @log_writer.parse_error e, client
507
+ when HttpParserError501
508
+ client.write_error(501)
509
+ @log_writer.parse_error e, client
923
510
  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)
511
+ client.write_error(500)
512
+ @log_writer.unknown_error e, nil, "Read"
952
513
  end
953
-
954
- stream.rewind
955
-
956
- return stream
957
514
  end
958
515
 
959
516
  # A fallback rack response if +@app+ raises as exception.
960
517
  #
961
- def lowlevel_error(e, env)
518
+ def lowlevel_error(e, env, status=500)
962
519
  if handler = @options[:lowlevel_error_handler]
963
520
  if handler.arity == 1
964
521
  return handler.call(e)
965
- else
522
+ elsif handler.arity == 2
966
523
  return handler.call(e, env)
524
+ else
525
+ return handler.call(e, env, status)
967
526
  end
968
527
  end
969
528
 
970
529
  if @leak_stack_on_error
971
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
530
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
531
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
972
532
  else
973
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
533
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
974
534
  end
975
535
  end
976
536
 
@@ -992,35 +552,13 @@ module Puma
992
552
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
993
553
  end
994
554
 
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
555
  if @status != :restart
1018
556
  @binder.close
1019
557
  end
1020
558
 
1021
559
  if @thread_pool
1022
560
  if timeout = @options[:force_shutdown_after]
1023
- @thread_pool.shutdown timeout.to_i
561
+ @thread_pool.shutdown timeout.to_f
1024
562
  else
1025
563
  @thread_pool.shutdown
1026
564
  end
@@ -1028,18 +566,16 @@ module Puma
1028
566
  end
1029
567
 
1030
568
  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
569
+ @notify << message
570
+ rescue IOError, NoMethodError, Errno::EPIPE
571
+ # The server, in another thread, is shutting down
572
+ Puma::Util.purge_interrupt_queue
573
+ rescue RuntimeError => e
574
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
575
+ if e.message.include?('IOError')
576
+ Puma::Util.purge_interrupt_queue
577
+ else
578
+ raise e
1043
579
  end
1044
580
  end
1045
581
  private :notify_safely
@@ -1057,44 +593,24 @@ module Puma
1057
593
  @thread.join if @thread && sync
1058
594
  end
1059
595
 
1060
- def begin_restart
596
+ def begin_restart(sync=false)
1061
597
  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]
598
+ @thread.join if @thread && sync
1089
599
  end
1090
600
 
1091
601
  def shutting_down?
1092
602
  @status == :stop || @status == :restart
1093
603
  end
1094
604
 
1095
- def possible_header_injection?(header_value)
1096
- HTTP_INJECTION_REGEX =~ header_value.to_s
605
+ # List of methods invoked by #stats.
606
+ # @version 5.0.0
607
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
608
+
609
+ # Returns a hash of stats about the running server for reporting purposes.
610
+ # @version 5.0.0
611
+ # @!attribute [r] stats
612
+ def stats
613
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1097
614
  end
1098
- private :possible_header_injection?
1099
615
  end
1100
616
  end