puma 5.0.4 → 5.6.4

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +322 -48
  3. data/LICENSE +0 -0
  4. data/README.md +95 -24
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +57 -20
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +2 -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 +0 -0
  14. data/docs/jungle/rc.d/README.md +1 -1
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +66 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +28 -0
  20. data/docs/restart.md +7 -7
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +142 -0
  23. data/docs/systemd.md +85 -66
  24. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  25. data/ext/puma_http11/ext_help.h +0 -0
  26. data/ext/puma_http11/extconf.rb +42 -6
  27. data/ext/puma_http11/http11_parser.c +68 -57
  28. data/ext/puma_http11/http11_parser.h +1 -1
  29. data/ext/puma_http11/http11_parser.java.rl +1 -1
  30. data/ext/puma_http11/http11_parser.rl +1 -1
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +226 -88
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
  37. data/ext/puma_http11/puma_http11.c +9 -3
  38. data/lib/puma/app/status.rb +4 -7
  39. data/lib/puma/binder.rb +138 -49
  40. data/lib/puma/cli.rb +18 -4
  41. data/lib/puma/client.rb +113 -31
  42. data/lib/puma/cluster/worker.rb +22 -19
  43. data/lib/puma/cluster/worker_handle.rb +13 -2
  44. data/lib/puma/cluster.rb +75 -33
  45. data/lib/puma/commonlogger.rb +0 -0
  46. data/lib/puma/configuration.rb +21 -2
  47. data/lib/puma/const.rb +17 -8
  48. data/lib/puma/control_cli.rb +76 -71
  49. data/lib/puma/detect.rb +19 -9
  50. data/lib/puma/dsl.rb +225 -31
  51. data/lib/puma/error_logger.rb +12 -5
  52. data/lib/puma/events.rb +18 -3
  53. data/lib/puma/io_buffer.rb +0 -0
  54. data/lib/puma/jruby_restart.rb +0 -0
  55. data/lib/puma/json_serialization.rb +96 -0
  56. data/lib/puma/launcher.rb +56 -7
  57. data/lib/puma/minissl/context_builder.rb +14 -6
  58. data/lib/puma/minissl.rb +72 -40
  59. data/lib/puma/null_io.rb +12 -0
  60. data/lib/puma/plugin/tmp_restart.rb +0 -0
  61. data/lib/puma/plugin.rb +2 -2
  62. data/lib/puma/queue_close.rb +7 -7
  63. data/lib/puma/rack/builder.rb +1 -1
  64. data/lib/puma/rack/urlmap.rb +0 -0
  65. data/lib/puma/rack_default.rb +0 -0
  66. data/lib/puma/reactor.rb +19 -12
  67. data/lib/puma/request.rb +55 -21
  68. data/lib/puma/runner.rb +39 -13
  69. data/lib/puma/server.rb +78 -142
  70. data/lib/puma/single.rb +0 -0
  71. data/lib/puma/state_file.rb +45 -9
  72. data/lib/puma/systemd.rb +46 -0
  73. data/lib/puma/thread_pool.rb +11 -8
  74. data/lib/puma/util.rb +8 -1
  75. data/lib/puma.rb +36 -10
  76. data/lib/rack/handler/puma.rb +1 -0
  77. data/tools/Dockerfile +1 -1
  78. data/tools/trickletest.rb +0 -0
  79. metadata +15 -9
data/lib/puma/request.rb CHANGED
@@ -26,11 +26,12 @@ module Puma
26
26
  # Finally, it'll return +true+ on keep-alive connections.
27
27
  # @param client [Puma::Client]
28
28
  # @param lines [Puma::IOBuffer]
29
+ # @param requests [Integer]
29
30
  # @return [Boolean,:async]
30
31
  #
31
- def handle_request(client, lines)
32
+ def handle_request(client, lines, requests)
32
33
  env = client.env
33
- io = client.io
34
+ io = client.io # io may be a MiniSSL::Socket
34
35
 
35
36
  return false if closed_socket?(io)
36
37
 
@@ -50,7 +51,7 @@ module Puma
50
51
  head = env[REQUEST_METHOD] == HEAD
51
52
 
52
53
  env[RACK_INPUT] = body
53
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
+ env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
55
 
55
56
  if @early_hints
56
57
  env[EARLY_HINTS] = lambda { |headers|
@@ -110,7 +111,7 @@ module Puma
110
111
 
111
112
  cork_socket io
112
113
 
113
- str_headers(env, status, headers, res_info, lines)
114
+ str_headers(env, status, headers, res_info, lines, requests, client)
114
115
 
115
116
  line_ending = LINE_END
116
117
 
@@ -148,8 +149,9 @@ module Puma
148
149
  res_body.each do |part|
149
150
  next if part.bytesize.zero?
150
151
  if chunked
151
- str = part.bytesize.to_s(16) << line_ending << part << line_ending
152
- fast_write io, str
152
+ fast_write io, (part.bytesize.to_s(16) << line_ending)
153
+ fast_write io, part # part may have different encoding
154
+ fast_write io, line_ending
153
155
  else
154
156
  fast_write io, part
155
157
  end
@@ -165,16 +167,21 @@ module Puma
165
167
  end
166
168
 
167
169
  ensure
168
- uncork_socket io
169
-
170
- body.close
171
- client.tempfile.unlink if client.tempfile
172
- res_body.close if res_body.respond_to? :close
170
+ begin
171
+ uncork_socket io
172
+
173
+ body.close
174
+ client.tempfile.unlink if client.tempfile
175
+ ensure
176
+ # Whatever happens, we MUST call `close` on the response body.
177
+ # Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
178
+ res_body.close if res_body.respond_to? :close
179
+ end
173
180
 
174
181
  after_reply.each { |o| o.call }
175
182
  end
176
183
 
177
- return res_info[:keep_alive]
184
+ res_info[:keep_alive]
178
185
  end
179
186
 
180
187
  # @param env [Hash] see Puma::Client#env, from request
@@ -199,7 +206,7 @@ module Puma
199
206
  begin
200
207
  n = io.syswrite str
201
208
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
202
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
209
+ unless io.wait_writable WRITE_TIMEOUT
203
210
  raise ConnectionError, "Socket timeout writing data"
204
211
  end
205
212
 
@@ -230,7 +237,11 @@ module Puma
230
237
  #
231
238
  def normalize_env(env, client)
232
239
  if host = env[HTTP_HOST]
233
- if colon = host.index(":")
240
+ # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
241
+ if colon = host.rindex("]:") # IPV6 with port
242
+ env[SERVER_NAME] = host[0, colon+1]
243
+ env[SERVER_PORT] = host[colon+2, host.bytesize]
244
+ elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
234
245
  env[SERVER_NAME] = host[0, colon]
235
246
  env[SERVER_PORT] = host[colon+1, host.bytesize]
236
247
  else
@@ -282,13 +293,20 @@ module Puma
282
293
  end
283
294
  # private :normalize_env
284
295
 
296
+ # @param header_key [#to_s]
297
+ # @return [Boolean]
298
+ #
299
+ def illegal_header_key?(header_key)
300
+ !!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
301
+ end
302
+
285
303
  # @param header_value [#to_s]
286
304
  # @return [Boolean]
287
305
  #
288
- def possible_header_injection?(header_value)
289
- !!(HTTP_INJECTION_REGEX =~ header_value.to_s)
306
+ def illegal_header_value?(header_value)
307
+ !!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
290
308
  end
291
- private :possible_header_injection?
309
+ private :illegal_header_key?, :illegal_header_value?
292
310
 
293
311
  # Fixup any headers with `,` in the name to have `_` now. We emit
294
312
  # headers with `,` in them during the parse phase to avoid ambiguity
@@ -334,9 +352,11 @@ module Puma
334
352
  def str_early_hints(headers)
335
353
  eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
336
354
  headers.each_pair do |k, vs|
355
+ next if illegal_header_key?(k)
356
+
337
357
  if vs.respond_to?(:to_s) && !vs.to_s.empty?
338
358
  vs.to_s.split(NEWLINE).each do |v|
339
- next if possible_header_injection?(v)
359
+ next if illegal_header_value?(v)
340
360
  eh_str << "#{k}: #{v}\r\n"
341
361
  end
342
362
  else
@@ -353,9 +373,11 @@ module Puma
353
373
  # @param headers [Hash] the headers returned by the Rack application
354
374
  # @param res_info [Hash] used to pass info between this method and #handle_request
355
375
  # @param lines [Puma::IOBuffer] modified inn place
376
+ # @param requests [Integer] number of inline requests handled
377
+ # @param client [Puma::Client]
356
378
  # @version 5.0.3
357
379
  #
358
- def str_headers(env, status, headers, res_info, lines)
380
+ def str_headers(env, status, headers, res_info, lines, requests, client)
359
381
  line_ending = LINE_END
360
382
  colon = COLON
361
383
 
@@ -396,12 +418,22 @@ module Puma
396
418
  # if running without request queueing
397
419
  res_info[:keep_alive] &&= @queue_requests
398
420
 
421
+ # Close the connection after a reasonable number of inline requests
422
+ # if the server is at capacity and the listener has a new connection ready.
423
+ # This allows Puma to service connections fairly when the number
424
+ # of concurrent connections exceeds the size of the threadpool.
425
+ res_info[:keep_alive] &&= requests < @max_fast_inline ||
426
+ @thread_pool.busy_threads < @max_threads ||
427
+ !client.listener.to_io.wait_readable(0)
428
+
399
429
  res_info[:response_hijack] = nil
400
430
 
401
431
  headers.each do |k, vs|
432
+ next if illegal_header_key?(k)
433
+
402
434
  case k.downcase
403
435
  when CONTENT_LENGTH2
404
- next if possible_header_injection?(vs)
436
+ next if illegal_header_value?(vs)
405
437
  res_info[:content_length] = vs
406
438
  next
407
439
  when TRANSFER_ENCODING
@@ -410,11 +442,13 @@ module Puma
410
442
  when HIJACK
411
443
  res_info[:response_hijack] = vs
412
444
  next
445
+ when BANNED_HEADER_KEY
446
+ next
413
447
  end
414
448
 
415
449
  if vs.respond_to?(:to_s) && !vs.to_s.empty?
416
450
  vs.to_s.split(NEWLINE).each do |v|
417
- next if possible_header_injection?(v)
451
+ next if illegal_header_value?(v)
418
452
  lines.append k, colon, v, line_ending
419
453
  end
420
454
  else
data/lib/puma/runner.rb CHANGED
@@ -15,6 +15,16 @@ module Puma
15
15
  @app = nil
16
16
  @control = nil
17
17
  @started_at = Time.now
18
+ @wakeup = nil
19
+ end
20
+
21
+ def wakeup!
22
+ return unless @wakeup
23
+
24
+ @wakeup.write "!" unless @wakeup.closed?
25
+
26
+ rescue SystemCallError, IOError
27
+ Puma::Util.purge_interrupt_queue
18
28
  end
19
29
 
20
30
  def development?
@@ -55,11 +65,11 @@ module Puma
55
65
  app = Puma::App::Status.new @launcher, token
56
66
 
57
67
  control = Puma::Server.new app, @launcher.events,
58
- { min_threads: 0, max_threads: 1 }
68
+ { min_threads: 0, max_threads: 1, queue_requests: false }
59
69
 
60
70
  control.binder.parse [str], self, 'Starting control server'
61
71
 
62
- control.run
72
+ control.run thread_name: 'ctl'
63
73
  @control = control
64
74
  end
65
75
 
@@ -84,11 +94,19 @@ module Puma
84
94
  def output_header(mode)
85
95
  min_t = @options[:min_threads]
86
96
  max_t = @options[:max_threads]
97
+ environment = @options[:environment]
87
98
 
88
99
  log "Puma starting in #{mode} mode..."
89
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
90
- log "* Min threads: #{min_t}, max threads: #{max_t}"
91
- log "* Environment: #{ENV['RACK_ENV']}"
100
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
101
+ log "* Min threads: #{min_t}"
102
+ log "* Max threads: #{max_t}"
103
+ log "* Environment: #{environment}"
104
+
105
+ if mode == "cluster"
106
+ log "* Master PID: #{Process.pid}"
107
+ else
108
+ log "* PID: #{Process.pid}"
109
+ end
92
110
  end
93
111
 
94
112
  def redirected_io?
@@ -101,23 +119,24 @@ module Puma
101
119
  append = @options[:redirect_append]
102
120
 
103
121
  if stdout
104
- unless Dir.exist?(File.dirname(stdout))
105
- raise "Cannot redirect STDOUT to #{stdout}"
106
- end
122
+ ensure_output_directory_exists(stdout, 'STDOUT')
107
123
 
108
124
  STDOUT.reopen stdout, (append ? "a" : "w")
109
- STDOUT.sync = true
110
125
  STDOUT.puts "=== puma startup: #{Time.now} ==="
126
+ STDOUT.flush unless STDOUT.sync
111
127
  end
112
128
 
113
129
  if stderr
114
- unless Dir.exist?(File.dirname(stderr))
115
- raise "Cannot redirect STDERR to #{stderr}"
116
- end
130
+ ensure_output_directory_exists(stderr, 'STDERR')
117
131
 
118
132
  STDERR.reopen stderr, (append ? "a" : "w")
119
- STDERR.sync = true
120
133
  STDERR.puts "=== puma startup: #{Time.now} ==="
134
+ STDERR.flush unless STDERR.sync
135
+ end
136
+
137
+ if @options[:mutate_stdout_and_stderr_to_sync_on_write]
138
+ STDOUT.sync = true
139
+ STDERR.sync = true
121
140
  end
122
141
  end
123
142
 
@@ -147,5 +166,12 @@ module Puma
147
166
  server.inherit_binder @launcher.binder
148
167
  server
149
168
  end
169
+
170
+ private
171
+ def ensure_output_directory_exists(path, io_name)
172
+ unless Dir.exist?(File.dirname(path))
173
+ raise "Cannot redirect #{io_name} to #{path}"
174
+ end
175
+ end
150
176
  end
151
177
  end
data/lib/puma/server.rb CHANGED
@@ -14,6 +14,7 @@ require 'puma/io_buffer'
14
14
  require 'puma/request'
15
15
 
16
16
  require 'socket'
17
+ require 'io/wait'
17
18
  require 'forwardable'
18
19
 
19
20
  module Puma
@@ -84,12 +85,14 @@ module Puma
84
85
 
85
86
  @options = options
86
87
 
87
- @early_hints = options.fetch :early_hints, nil
88
- @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
- @min_threads = options.fetch :min_threads, 0
90
- @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
- @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
- @queue_requests = options.fetch :queue_requests, true
88
+ @early_hints = options.fetch :early_hints, nil
89
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
90
+ @min_threads = options.fetch :min_threads, 0
91
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
92
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
93
+ @queue_requests = options.fetch :queue_requests, true
94
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
95
+ @io_selector_backend = options.fetch :io_selector_backend, :auto
93
96
 
94
97
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
95
98
  @leak_stack_on_error = @options[:environment] ? temp : true
@@ -118,17 +121,13 @@ module Puma
118
121
  # :nodoc:
119
122
  # @version 5.0.0
120
123
  def tcp_cork_supported?
121
- RbConfig::CONFIG['host_os'] =~ /linux/ &&
122
- Socket.const_defined?(:IPPROTO_TCP) &&
123
- Socket.const_defined?(:TCP_CORK)
124
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
124
125
  end
125
126
 
126
127
  # :nodoc:
127
128
  # @version 5.0.0
128
129
  def closed_socket_supported?
129
- RbConfig::CONFIG['host_os'] =~ /linux/ &&
130
- Socket.const_defined?(:IPPROTO_TCP) &&
131
- Socket.const_defined?(:TCP_INFO)
130
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
132
131
  end
133
132
  private :tcp_cork_supported?
134
133
  private :closed_socket_supported?
@@ -136,26 +135,27 @@ module Puma
136
135
 
137
136
  # On Linux, use TCP_CORK to better control how the TCP stack
138
137
  # packetizes our stream. This improves both latency and throughput.
138
+ # socket parameter may be an MiniSSL::Socket, so use to_io
139
139
  #
140
140
  if tcp_cork_supported?
141
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
142
-
143
141
  # 6 == Socket::IPPROTO_TCP
144
142
  # 3 == TCP_CORK
145
143
  # 1/0 == turn on/off
146
144
  def cork_socket(socket)
145
+ skt = socket.to_io
147
146
  begin
148
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
147
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
149
148
  rescue IOError, SystemCallError
150
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
+ Puma::Util.purge_interrupt_queue
151
150
  end
152
151
  end
153
152
 
154
153
  def uncork_socket(socket)
154
+ skt = socket.to_io
155
155
  begin
156
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
156
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
157
157
  rescue IOError, SystemCallError
158
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
158
+ Puma::Util.purge_interrupt_queue
159
159
  end
160
160
  end
161
161
  else
@@ -167,14 +167,16 @@ module Puma
167
167
  end
168
168
 
169
169
  if closed_socket_supported?
170
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
171
+
170
172
  def closed_socket?(socket)
171
- return false unless socket.kind_of? TCPSocket
172
- return false unless @precheck_closing
173
+ skt = socket.to_io
174
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
173
175
 
174
176
  begin
175
- tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
177
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
176
178
  rescue IOError, SystemCallError
177
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
179
+ Puma::Util.purge_interrupt_queue
178
180
  @precheck_closing = false
179
181
  false
180
182
  else
@@ -218,7 +220,7 @@ module Puma
218
220
  # up in the background to handle requests. Otherwise requests
219
221
  # are handled synchronously.
220
222
  #
221
- def run(background=true)
223
+ def run(background=true, thread_name: 'srv')
222
224
  BasicSocket.do_not_reverse_lookup = true
223
225
 
224
226
  @events.fire :state, :booting
@@ -226,6 +228,7 @@ module Puma
226
228
  @status = :run
227
229
 
228
230
  @thread_pool = ThreadPool.new(
231
+ thread_name,
229
232
  @min_threads,
230
233
  @max_threads,
231
234
  ::Puma::IOBuffer,
@@ -236,7 +239,7 @@ module Puma
236
239
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
237
240
 
238
241
  if @queue_requests
239
- @reactor = Reactor.new(&method(:reactor_wakeup))
242
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
240
243
  @reactor.run
241
244
  end
242
245
 
@@ -254,7 +257,7 @@ module Puma
254
257
 
255
258
  if background
256
259
  @thread = Thread.new do
257
- Puma.set_thread_name "server"
260
+ Puma.set_thread_name thread_name
258
261
  handle_servers
259
262
  end
260
263
  return @thread
@@ -294,6 +297,9 @@ module Puma
294
297
  @thread_pool << client
295
298
  elsif shutdown || client.timeout == 0
296
299
  client.timeout!
300
+ else
301
+ client.set_timeout(@first_data_timeout)
302
+ false
297
303
  end
298
304
  rescue StandardError => e
299
305
  client_error(e, client)
@@ -307,47 +313,51 @@ module Puma
307
313
  sockets = [check] + @binder.ios
308
314
  pool = @thread_pool
309
315
  queue_requests = @queue_requests
316
+ drain = @options[:drain_on_shutdown] ? 0 : nil
310
317
 
311
- remote_addr_value = nil
312
- remote_addr_header = nil
313
-
314
- case @options[:remote_address]
318
+ addr_send_name, addr_value = case @options[:remote_address]
315
319
  when :value
316
- remote_addr_value = @options[:remote_address_value]
320
+ [:peerip=, @options[:remote_address_value]]
317
321
  when :header
318
- remote_addr_header = @options[:remote_address_header]
322
+ [:remote_addr_header=, @options[:remote_address_header]]
323
+ when :proxy_protocol
324
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
325
+ else
326
+ [nil, nil]
319
327
  end
320
328
 
321
- while @status == :run
329
+ while @status == :run || (drain && shutting_down?)
322
330
  begin
323
- ios = IO.select sockets
331
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
332
+ break unless ios
324
333
  ios.first.each do |sock|
325
334
  if sock == check
326
335
  break if handle_check
327
336
  else
328
337
  pool.wait_until_not_full
329
- pool.wait_for_less_busy_worker(
330
- @options[:wait_for_less_busy_worker].to_f)
338
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
331
339
 
332
340
  io = begin
333
341
  sock.accept_nonblock
334
342
  rescue IO::WaitReadable
335
343
  next
336
344
  end
337
- client = Client.new io, @binder.env(sock)
338
- if remote_addr_value
339
- client.peerip = remote_addr_value
340
- elsif remote_addr_header
341
- client.remote_addr_header = remote_addr_header
342
- end
343
- pool << client
345
+ drain += 1 if shutting_down?
346
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
347
+ c.listener = sock
348
+ c.send(addr_send_name, addr_value) if addr_value
349
+ }
344
350
  end
345
351
  end
346
- rescue Object => e
352
+ rescue IOError, Errno::EBADF
353
+ # In the case that any of the sockets are unexpectedly close.
354
+ raise
355
+ rescue StandardError => e
347
356
  @events.unknown_error e, nil, "Listen loop"
348
357
  end
349
358
  end
350
359
 
360
+ @events.debug "Drained #{drain} additional connections." if drain
351
361
  @events.fire :state, @status
352
362
 
353
363
  if queue_requests
@@ -358,13 +368,14 @@ module Puma
358
368
  rescue Exception => e
359
369
  @events.unknown_error e, nil, "Exception handling servers"
360
370
  ensure
361
- begin
362
- @check.close unless @check.closed?
363
- rescue Errno::EBADF, RuntimeError
364
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
365
- # Errno::EBADF is infrequently raised
371
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
+ # Errno::EBADF is infrequently raised
373
+ [@check, @notify].each do |io|
374
+ begin
375
+ io.close unless io.closed?
376
+ rescue Errno::EBADF, RuntimeError
377
+ end
366
378
  end
367
- @notify.close
368
379
  @notify = nil
369
380
  @check = nil
370
381
  end
@@ -388,7 +399,7 @@ module Puma
388
399
  return true
389
400
  end
390
401
 
391
- return false
402
+ false
392
403
  end
393
404
 
394
405
  # Given a connection on +client+, handle the incoming requests,
@@ -427,7 +438,7 @@ module Puma
427
438
 
428
439
  while true
429
440
  @requests_count += 1
430
- case handle_request(client, buffer)
441
+ case handle_request(client, buffer, requests + 1)
431
442
  when false
432
443
  break
433
444
  when :async
@@ -440,18 +451,17 @@ module Puma
440
451
 
441
452
  requests += 1
442
453
 
443
- check_for_more_data = @status == :run
454
+ # As an optimization, try to read the next request from the
455
+ # socket for a short time before returning to the reactor.
456
+ fast_check = @status == :run
444
457
 
445
- if requests >= MAX_FAST_INLINE
446
- # This will mean that reset will only try to use the data it already
447
- # has buffered and won't try to read more data. What this means is that
448
- # every client, independent of their request speed, gets treated like a slow
449
- # one once every MAX_FAST_INLINE requests.
450
- check_for_more_data = false
451
- end
458
+ # Always pass the client back to the reactor after a reasonable
459
+ # number of inline requests if there are other requests pending.
460
+ fast_check = false if requests >= @max_fast_inline &&
461
+ @thread_pool.backlog > 0
452
462
 
453
463
  next_request_ready = with_force_shutdown(client) do
454
- client.reset(check_for_more_data)
464
+ client.reset(fast_check)
455
465
  end
456
466
 
457
467
  unless next_request_ready
@@ -475,7 +485,7 @@ module Puma
475
485
  begin
476
486
  client.close if close_socket
477
487
  rescue IOError, SystemCallError
478
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
479
489
  # Already closed
480
490
  rescue StandardError => e
481
491
  @events.unknown_error e, nil, "Client"
@@ -493,62 +503,6 @@ module Puma
493
503
 
494
504
  # :nocov:
495
505
 
496
- # Given the request +env+ from +client+ and the partial body +body+
497
- # plus a potential Content-Length value +cl+, finish reading
498
- # the body and return it.
499
- #
500
- # If the body is larger than MAX_BODY, a Tempfile object is used
501
- # for the body, otherwise a StringIO is used.
502
- # @deprecated 6.0.0
503
- #
504
- def read_body(env, client, body, cl)
505
- content_length = cl.to_i
506
-
507
- remain = content_length - body.bytesize
508
-
509
- return StringIO.new(body) if remain <= 0
510
-
511
- # Use a Tempfile if there is a lot of data left
512
- if remain > MAX_BODY
513
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
514
- stream.binmode
515
- else
516
- # The body[0,0] trick is to get an empty string in the same
517
- # encoding as body.
518
- stream = StringIO.new body[0,0]
519
- end
520
-
521
- stream.write body
522
-
523
- # Read an odd sized chunk so we can read even sized ones
524
- # after this
525
- chunk = client.readpartial(remain % CHUNK_SIZE)
526
-
527
- # No chunk means a closed socket
528
- unless chunk
529
- stream.close
530
- return nil
531
- end
532
-
533
- remain -= stream.write(chunk)
534
-
535
- # Read the rest of the chunks
536
- while remain > 0
537
- chunk = client.readpartial(CHUNK_SIZE)
538
- unless chunk
539
- stream.close
540
- return nil
541
- end
542
-
543
- remain -= stream.write(chunk)
544
- end
545
-
546
- stream.rewind
547
-
548
- return stream
549
- end
550
- # :nocov:
551
-
552
506
  # Handle various error types thrown by Client I/O operations.
553
507
  def client_error(e, client)
554
508
  # Swallow, do not log
@@ -561,6 +515,9 @@ module Puma
561
515
  when HttpParserError
562
516
  client.write_error(400)
563
517
  @events.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @events.parse_error e, client
564
521
  else
565
522
  client.write_error(500)
566
523
  @events.unknown_error e, nil, "Read"
@@ -581,7 +538,8 @@ module Puma
581
538
  end
582
539
 
583
540
  if @leak_stack_on_error
584
- [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
541
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
585
543
  else
586
544
  [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
587
545
  end
@@ -605,28 +563,6 @@ module Puma
605
563
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
606
564
  end
607
565
 
608
- if @options[:drain_on_shutdown]
609
- count = 0
610
-
611
- while true
612
- ios = IO.select @binder.ios, nil, nil, 0
613
- break unless ios
614
-
615
- ios.first.each do |sock|
616
- begin
617
- if io = sock.accept_nonblock
618
- count += 1
619
- client = Client.new io, @binder.env(sock)
620
- @thread_pool << client
621
- end
622
- rescue SystemCallError
623
- end
624
- end
625
- end
626
-
627
- @events.debug "Drained #{count} additional connections."
628
- end
629
-
630
566
  if @status != :restart
631
567
  @binder.close
632
568
  end
@@ -644,11 +580,11 @@ module Puma
644
580
  @notify << message
645
581
  rescue IOError, NoMethodError, Errno::EPIPE
646
582
  # The server, in another thread, is shutting down
647
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
583
+ Puma::Util.purge_interrupt_queue
648
584
  rescue RuntimeError => e
649
585
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
650
586
  if e.message.include?('IOError')
651
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
587
+ Puma::Util.purge_interrupt_queue
652
588
  else
653
589
  raise e
654
590
  end
data/lib/puma/single.rb CHANGED
File without changes