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