puma 6.0.0 → 6.1.0

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.

data/lib/puma/launcher.rb CHANGED
@@ -59,6 +59,10 @@ module Puma
59
59
 
60
60
  @environment = conf.environment
61
61
 
62
+ if ENV["NOTIFY_SOCKET"]
63
+ @config.plugins.create('systemd')
64
+ end
65
+
62
66
  if @config.options[:bind_to_activated_sockets]
63
67
  @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
68
  @config.options[:binds],
@@ -180,7 +184,6 @@ module Puma
180
184
 
181
185
  setup_signals
182
186
  set_process_title
183
- integrate_with_systemd
184
187
 
185
188
  # This blocks until the server is stopped
186
189
  @runner.run
@@ -311,27 +314,6 @@ module Puma
311
314
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
312
315
  end
313
316
 
314
- # Puma's systemd integration allows Puma to inform systemd:
315
- # 1. when it has successfully started
316
- # 2. when it is starting shutdown
317
- # 3. periodically for a liveness check with a watchdog thread
318
- def integrate_with_systemd
319
- return unless ENV["NOTIFY_SOCKET"]
320
-
321
- begin
322
- require_relative 'systemd'
323
- rescue LoadError
324
- log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
325
- return
326
- end
327
-
328
- log "* Enabling systemd notification integration"
329
-
330
- systemd = Systemd.new(@log_writer, @events)
331
- systemd.hook_events
332
- systemd.start_watchdog
333
- end
334
-
335
317
  def log(str)
336
318
  @log_writer.log(str)
337
319
  end
@@ -80,6 +80,10 @@ module Puma
80
80
  end
81
81
  private :internal_write
82
82
 
83
+ def debug?
84
+ @debug
85
+ end
86
+
83
87
  def debug(str)
84
88
  log("% #{str}") if @debug
85
89
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ # Puma's systemd integration allows Puma to inform systemd:
6
+ # 1. when it has successfully started
7
+ # 2. when it is starting shutdown
8
+ # 3. periodically for a liveness check with a watchdog thread
9
+ # 4. periodically set the status
10
+ Puma::Plugin.create do
11
+ def start(launcher)
12
+ require_relative '../sd_notify'
13
+
14
+ launcher.log_writer.log "* Enabling systemd notification integration"
15
+
16
+ # hook_events
17
+ launcher.events.on_booted { Puma::SdNotify.ready }
18
+ launcher.events.on_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.on_restart { Puma::SdNotify.reloading }
20
+
21
+ # start watchdog
22
+ if Puma::SdNotify.watchdog?
23
+ ping_f = watchdog_sleep_time
24
+
25
+ in_background do
26
+ launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
27
+ loop do
28
+ sleep ping_f
29
+ Puma::SdNotify.watchdog
30
+ end
31
+ end
32
+ end
33
+
34
+ # start status loop
35
+ instance = self
36
+ sleep_time = 1.0
37
+ in_background do
38
+ launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
39
+
40
+ loop do
41
+ sleep sleep_time
42
+ # TODO: error handling?
43
+ Puma::SdNotify.status(instance.status)
44
+ end
45
+ end
46
+ end
47
+
48
+ def status
49
+ if clustered?
50
+ messages = stats[:worker_status].map do |worker|
51
+ common_message(worker[:last_status])
52
+ end.join(',')
53
+
54
+ "Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
55
+ else
56
+ "Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def watchdog_sleep_time
63
+ usec = Integer(ENV["WATCHDOG_USEC"])
64
+
65
+ sec_f = usec / 1_000_000.0
66
+ # "It is recommended that a daemon sends a keep-alive notification message
67
+ # to the service manager every half of the time returned here."
68
+ sec_f / 2
69
+ end
70
+
71
+ def stats
72
+ Puma.stats_hash
73
+ end
74
+
75
+ def clustered?
76
+ stats.has_key?(:workers)
77
+ end
78
+
79
+ def workers
80
+ stats.fetch(:workers, 1)
81
+ end
82
+
83
+ def booted_workers
84
+ stats.fetch(:booted_workers, 1)
85
+ end
86
+
87
+ def common_message(stats)
88
+ "{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
89
+ end
90
+ end
@@ -2,8 +2,23 @@
2
2
 
3
3
  require_relative '../rack/handler/puma'
4
4
 
5
- module Rack::Handler
6
- def self.default(options = {})
7
- Rack::Handler::Puma
5
+ # rackup was removed in Rack 3, it is now a separate gem
6
+ if Object.const_defined? :Rackup
7
+ module Rackup
8
+ module Handler
9
+ def self.default(options = {})
10
+ ::Rackup::Handler::Puma
11
+ end
12
+ end
8
13
  end
14
+ elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
15
+ module Rack
16
+ module Handler
17
+ def self.default(options = {})
18
+ ::Rack::Handler::Puma
19
+ end
20
+ end
21
+ end
22
+ else
23
+ raise "Rack 3 must be used with the Rackup gem"
9
24
  end
data/lib/puma/reactor.rb CHANGED
@@ -50,7 +50,7 @@ module Puma
50
50
  @input << client
51
51
  @selector.wakeup
52
52
  true
53
- rescue ClosedQueueError
53
+ rescue ClosedQueueError, IOError # Ignore if selector is already closed
54
54
  false
55
55
  end
56
56
 
data/lib/puma/request.rb CHANGED
@@ -14,15 +14,18 @@ module Puma
14
14
  #
15
15
  module Request # :nodoc:
16
16
 
17
- # determines whether to write body to io_buffer first, or straight to socket
18
- # also fixes max size of chunked body read when bosy is an IO.
19
- BODY_LEN_MAX = 1_024 * 256
17
+ # Single element array body: smaller bodies are written to io_buffer first,
18
+ # then a single write from io_buffer. Larger sizes are written separately.
19
+ # Also fixes max size of chunked file body read.
20
+ BODY_LEN_MAX = 1_024 * 256
20
21
 
21
- # size divide for using copy_stream on body
22
+ # File body: smaller bodies are combined with io_buffer, then written to
23
+ # socket. Larger bodies are written separately using `copy_stream`
22
24
  IO_BODY_MAX = 1_024 * 64
23
25
 
24
- # max size for io_buffer, force write when exceeded
25
- IO_BUFFER_LEN_MAX = 1_024 * 1_024 * 4
26
+ # Array body: elements are collected in io_buffer. When io_buffer's size
27
+ # exceeds value, they are written to the socket.
28
+ IO_BUFFER_LEN_MAX = 1_024 * 512
26
29
 
27
30
  SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
28
31
 
@@ -41,16 +44,22 @@ module Puma
41
44
  #
42
45
  # Finally, it'll return +true+ on keep-alive connections.
43
46
  # @param client [Puma::Client]
44
- # @param io_buffer [Puma::IOBuffer]
45
47
  # @param requests [Integer]
46
48
  # @return [Boolean,:async]
47
49
  #
48
- def handle_request(client, io_buffer, requests)
50
+ def handle_request(client, requests)
49
51
  env = client.env
52
+ io_buffer = client.io_buffer
50
53
  socket = client.io # io may be a MiniSSL::Socket
54
+ app_body = nil
55
+
51
56
 
52
57
  return false if closed_socket?(socket)
53
58
 
59
+ if client.http_content_length_limit_exceeded
60
+ return prepare_response(413, {}, ["Payload Too Large"], requests, client)
61
+ end
62
+
54
63
  normalize_env env, client
55
64
 
56
65
  env[PUMA_SOCKET] = socket
@@ -85,14 +94,18 @@ module Puma
85
94
 
86
95
  begin
87
96
  if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
88
- status, headers, res_body = @thread_pool.with_force_shutdown do
97
+ status, headers, app_body = @thread_pool.with_force_shutdown do
89
98
  @app.call(env)
90
99
  end
91
100
  else
92
101
  @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
93
- status, headers, res_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
102
+ status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
94
103
  end
95
104
 
105
+ # app_body needs to always be closed, hold value in case lowlevel_error
106
+ # is called
107
+ res_body = app_body
108
+
96
109
  return :async if client.hijacked
97
110
 
98
111
  status = status.to_i
@@ -114,60 +127,83 @@ module Puma
114
127
 
115
128
  status, headers, res_body = lowlevel_error(e, env, 500)
116
129
  end
117
- prepare_response(status, headers, res_body, io_buffer, requests, client)
130
+ prepare_response(status, headers, res_body, requests, client)
131
+ ensure
132
+ io_buffer.reset
133
+ uncork_socket client.io
134
+ app_body.close if app_body.respond_to? :close
135
+ client.tempfile&.unlink
136
+ after_reply = env[RACK_AFTER_REPLY] || []
137
+ begin
138
+ after_reply.each { |o| o.call }
139
+ rescue StandardError => e
140
+ @log_writer.debug_error e
141
+ end unless after_reply.empty?
118
142
  end
119
143
 
120
144
  # Assembles the headers and prepares the body for actually sending the
121
- # response via #fast_write_response.
145
+ # response via `#fast_write_response`.
122
146
  #
123
147
  # @param status [Integer] the status returned by the Rack application
124
148
  # @param headers [Hash] the headers returned by the Rack application
125
- # @param app_body [Array] the body returned by the Rack application or
126
- # a call to `lowlevel_error`
127
- # @param io_buffer [Puma::IOBuffer] modified in place
149
+ # @param res_body [Array] the body returned by the Rack application or
150
+ # a call to `Server#lowlevel_error`
128
151
  # @param requests [Integer] number of inline requests handled
129
152
  # @param client [Puma::Client]
130
- # @return [Boolean,:async]
131
- def prepare_response(status, headers, app_body, io_buffer, requests, client)
153
+ # @return [Boolean,:async] keep-alive status or `:async`
154
+ def prepare_response(status, headers, res_body, requests, client)
132
155
  env = client.env
133
- socket = client.io
134
-
135
- after_reply = env[RACK_AFTER_REPLY] || []
156
+ socket = client.io
157
+ io_buffer = client.io_buffer
136
158
 
137
159
  return false if closed_socket?(socket)
138
160
 
139
- resp_info = str_headers(env, status, headers, app_body, io_buffer, requests, client)
161
+ # Close the connection after a reasonable number of inline requests
162
+ # if the server is at capacity and the listener has a new connection ready.
163
+ # This allows Puma to service connections fairly when the number
164
+ # of concurrent connections exceeds the size of the threadpool.
165
+ force_keep_alive = requests < @max_fast_inline ||
166
+ @thread_pool.busy_threads < @max_threads ||
167
+ !client.listener.to_io.wait_readable(0)
168
+
169
+ resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
170
+
171
+ close_body = false
140
172
 
141
173
  # below converts app_body into body, dependent on app_body's characteristics, and
142
174
  # resp_info[:content_length] will be set if it can be determined
143
175
  if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
144
- if app_body.respond_to?(:to_ary)
145
- length = 0
146
- if array_body = app_body.to_ary
147
- body = array_body.map { |part| length += part.bytesize; part }
148
- elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
149
- length = app_body.size
150
- elsif app_body.respond_to?(:each)
151
- body = []
152
- app_body.each { |part| length += part.bytesize; body << part }
153
- end
154
- resp_info[:content_length] = length
155
- elsif app_body.is_a?(File) && app_body.respond_to?(:size)
156
- resp_info[:content_length] = app_body.size
157
- body = app_body
158
- elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
159
- File.readable?(fn = app_body.to_path)
176
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) && array_body.is_a?(Array)
177
+ body = array_body
178
+ resp_info[:content_length] = body.sum(&:bytesize)
179
+ elsif res_body.is_a?(File) && res_body.respond_to?(:size)
180
+ body = res_body
181
+ resp_info[:content_length] = body.size
182
+ elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
183
+ File.readable?(fn = res_body.to_path)
160
184
  body = File.open fn, 'rb'
161
185
  resp_info[:content_length] = body.size
186
+ close_body = true
162
187
  else
163
- body = app_body
188
+ body = res_body
164
189
  end
165
- elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
166
- File.readable?(fn = app_body.to_path)
190
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
191
+ File.readable?(fn = res_body.to_path)
167
192
  body = File.open fn, 'rb'
168
193
  resp_info[:content_length] = body.size
194
+ close_body = true
195
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
196
+ res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
197
+ # Sprockets::Asset
198
+ resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
199
+ if res_body.to_hash[:source] # use each to return @source
200
+ body = res_body
201
+ else # avoid each and use a File object
202
+ body = File.open fn, 'rb'
203
+ close_body = true
204
+ end
169
205
  else
170
- body = app_body
206
+ body = res_body
171
207
  end
172
208
 
173
209
  line_ending = LINE_END
@@ -175,8 +211,8 @@ module Puma
175
211
  content_length = resp_info[:content_length]
176
212
  keep_alive = resp_info[:keep_alive]
177
213
 
178
- if app_body && !app_body.respond_to?(:each)
179
- response_hijack = app_body
214
+ if res_body && !res_body.respond_to?(:each)
215
+ response_hijack = res_body
180
216
  else
181
217
  response_hijack = resp_info[:response_hijack]
182
218
  end
@@ -184,44 +220,40 @@ module Puma
184
220
  cork_socket socket
185
221
 
186
222
  if resp_info[:no_body]
187
- if content_length and status != 204
223
+ # 101 (Switching Protocols) doesn't return here or have content_length,
224
+ # it should be using `response_hijack`
225
+ unless status == 101
226
+ if content_length && status != 204
227
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
228
+ end
229
+
230
+ io_buffer << LINE_END
231
+ fast_write_str socket, io_buffer.read_and_reset
232
+ socket.flush
233
+ return keep_alive
234
+ end
235
+ else
236
+ if content_length
188
237
  io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
238
+ chunked = false
239
+ elsif !response_hijack && resp_info[:allow_chunked]
240
+ io_buffer << TRANSFER_ENCODING_CHUNKED
241
+ chunked = true
189
242
  end
190
-
191
- io_buffer << LINE_END
192
- fast_write_str socket, io_buffer.to_s
193
- return keep_alive
194
- end
195
- if content_length
196
- io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
197
- chunked = false
198
- elsif !response_hijack and resp_info[:allow_chunked]
199
- io_buffer << TRANSFER_ENCODING_CHUNKED
200
- chunked = true
201
243
  end
202
244
 
203
245
  io_buffer << line_ending
204
246
 
205
247
  if response_hijack
206
- fast_write_str socket, io_buffer.to_s
248
+ fast_write_str socket, io_buffer.read_and_reset
249
+ uncork_socket socket
207
250
  response_hijack.call socket
208
251
  return :async
209
252
  end
210
253
 
211
254
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
255
+ body.close if close_body
212
256
  keep_alive
213
- ensure
214
- io_buffer.reset
215
- resp_info = nil
216
- uncork_socket socket
217
- app_body.close if app_body.respond_to? :close
218
- client.tempfile&.unlink
219
-
220
- begin
221
- after_reply.each { |o| o.call }
222
- rescue StandardError => e
223
- @log_writer.debug_error e
224
- end unless after_reply.empty?
225
257
  end
226
258
 
227
259
  # @param env [Hash] see Puma::Client#env, from request
@@ -237,10 +269,10 @@ module Puma
237
269
 
238
270
  # Used to write 'early hints', 'no body' responses, 'hijacked' responses,
239
271
  # and body segments (called by `fast_write_response`).
240
- # Writes a string to an io (normally `Client#io`) using `write_nonblock`.
272
+ # Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
241
273
  # Large strings may not be written in one pass, especially if `io` is a
242
274
  # `MiniSSL::Socket`.
243
- # @param io [#write_nonblock] the io to write to
275
+ # @param socket [#write_nonblock] the request/response socket
244
276
  # @param str [String] the string written to the io
245
277
  # @raise [ConnectionError]
246
278
  #
@@ -249,7 +281,7 @@ module Puma
249
281
  byte_size = str.bytesize
250
282
  while n < byte_size
251
283
  begin
252
- n += socket.syswrite(n.zero? ? str : str.byteslice(n..-1))
284
+ n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
253
285
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
254
286
  unless socket.wait_writable WRITE_TIMEOUT
255
287
  raise ConnectionError, SOCKET_WRITE_ERR_MSG
@@ -267,39 +299,41 @@ module Puma
267
299
  # @param socket [#write] the response socket
268
300
  # @param body [Enumerable, File] the body object
269
301
  # @param io_buffer [Puma::IOBuffer] contains headers
270
- # @param chunk [Boolean]
302
+ # @param chunked [Boolean]
303
+ # @paramn content_length [Integer
271
304
  # @raise [ConnectionError]
272
305
  #
273
306
  def fast_write_response(socket, body, io_buffer, chunked, content_length)
274
- if body.is_a?(::File) || body.respond_to?(:read) || body.respond_to?(:readpartial)
307
+ if body.is_a?(::File) && body.respond_to?(:read)
275
308
  if chunked # would this ever happen?
276
- while part = body.read(BODY_LEN_MAX)
277
- io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
309
+ while chunk = body.read(BODY_LEN_MAX)
310
+ io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
278
311
  end
279
- io_buffer << CLOSE_CHUNKED
280
- fast_write_str socket, io_buffer.to_s
312
+ fast_write_str socket, CLOSE_CHUNKED
281
313
  else
282
314
  if content_length <= IO_BODY_MAX
283
- io_buffer.write body.sysread(content_length)
284
- fast_write_str socket, io_buffer.to_s
315
+ io_buffer.write body.read(content_length)
316
+ fast_write_str socket, io_buffer.read_and_reset
285
317
  else
286
- fast_write_str socket, io_buffer.to_s
318
+ fast_write_str socket, io_buffer.read_and_reset
287
319
  IO.copy_stream body, socket
288
320
  end
289
321
  end
290
- body.close
291
322
  elsif body.is_a?(::Array) && body.length == 1
292
- body_first = body.first
293
- if body_first.is_a?(::String) && body_first.bytesize >= BODY_LEN_MAX
323
+ body_first = nil
324
+ # using body_first = body.first causes issues?
325
+ body.each { |str| body_first ||= str }
326
+
327
+ if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
328
+ # smaller body, write to io_buffer first
329
+ io_buffer.write body_first
330
+ fast_write_str socket, io_buffer.read_and_reset
331
+ else
294
332
  # large body, write both header & body to socket
295
- fast_write_str socket, io_buffer.to_s
333
+ fast_write_str socket, io_buffer.read_and_reset
296
334
  fast_write_str socket, body_first
297
- else
298
- # smaller body, write to stream first
299
- io_buffer.write body_first
300
- fast_write_str socket, io_buffer.to_s
301
335
  end
302
- else
336
+ elsif body.is_a?(::Array)
303
337
  # for array bodies, flush io_buffer to socket when size is greater than
304
338
  # IO_BUFFER_LEN_MAX
305
339
  if chunked
@@ -307,8 +341,7 @@ module Puma
307
341
  next if (byte_size = part.bytesize).zero?
308
342
  io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
309
343
  if io_buffer.length > IO_BUFFER_LEN_MAX
310
- fast_write_str socket, io_buffer.to_s
311
- io_buffer.reset
344
+ fast_write_str socket, io_buffer.read_and_reset
312
345
  end
313
346
  end
314
347
  io_buffer.write CLOSE_CHUNKED
@@ -317,13 +350,31 @@ module Puma
317
350
  next if part.bytesize.zero?
318
351
  io_buffer.write part
319
352
  if io_buffer.length > IO_BUFFER_LEN_MAX
320
- fast_write_str socket, io_buffer.to_s
321
- io_buffer.reset
353
+ fast_write_str socket, io_buffer.read_and_reset
322
354
  end
323
355
  end
324
356
  end
325
- fast_write_str(socket, io_buffer.to_s) unless io_buffer.length.zero?
357
+ # may write last body part for non-chunked, also headers if array is empty
358
+ fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
359
+ else
360
+ # for enum bodies
361
+ fast_write_str socket, io_buffer.read_and_reset
362
+ if chunked
363
+ body.each do |part|
364
+ next if (byte_size = part.bytesize).zero?
365
+ fast_write_str socket, (byte_size.to_s(16) << LINE_END)
366
+ fast_write_str socket, part
367
+ fast_write_str socket, LINE_END
368
+ end
369
+ fast_write_str socket, CLOSE_CHUNKED
370
+ else
371
+ body.each do |part|
372
+ next if part.bytesize.zero?
373
+ fast_write_str socket, part
374
+ end
375
+ end
326
376
  end
377
+ socket.flush
327
378
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
328
379
  raise ConnectionError, SOCKET_WRITE_ERR_MSG
329
380
  rescue Errno::EPIPE, SystemCallError, IOError
@@ -436,7 +487,7 @@ module Puma
436
487
  to_add = nil
437
488
 
438
489
  env.each do |k,v|
439
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
490
+ if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
440
491
  if to_delete
441
492
  to_delete << k
442
493
  else
@@ -496,12 +547,13 @@ module Puma
496
547
  # @param content_length [Integer,nil] content length if it can be determined from the
497
548
  # response body
498
549
  # @param io_buffer [Puma::IOBuffer] modified inn place
499
- # @param requests [Integer] number of inline requests handled
500
- # @param client [Puma::Client]
550
+ # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
551
+ # status and `@max_fast_inline`
501
552
  # @return [Hash] resp_info
502
553
  # @version 5.0.3
503
554
  #
504
- def str_headers(env, status, headers, res_body, io_buffer, requests, client)
555
+ def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
556
+
505
557
  line_ending = LINE_END
506
558
  colon = COLON
507
559
 
@@ -544,13 +596,8 @@ module Puma
544
596
  # if running without request queueing
545
597
  resp_info[:keep_alive] &&= @queue_requests
546
598
 
547
- # Close the connection after a reasonable number of inline requests
548
- # if the server is at capacity and the listener has a new connection ready.
549
- # This allows Puma to service connections fairly when the number
550
- # of concurrent connections exceeds the size of the threadpool.
551
- resp_info[:keep_alive] &&= requests < @max_fast_inline ||
552
- @thread_pool.busy_threads < @max_threads ||
553
- !client.listener.to_io.wait_readable(0)
599
+ # see prepare_response
600
+ resp_info[:keep_alive] &&= force_keep_alive
554
601
 
555
602
  resp_info[:response_hijack] = nil
556
603
 
@@ -560,7 +607,8 @@ module Puma
560
607
  case k.downcase
561
608
  when CONTENT_LENGTH2
562
609
  next if illegal_header_value?(vs)
563
- resp_info[:content_length] = vs
610
+ # nil.to_i is 0, nil&.to_i is nil
611
+ resp_info[:content_length] = vs&.to_i
564
612
  next
565
613
  when TRANSFER_ENCODING
566
614
  resp_info[:allow_chunked] = false
data/lib/puma/runner.rb CHANGED
@@ -182,6 +182,10 @@ module Puma
182
182
  end
183
183
  end
184
184
 
185
+ def utc_iso8601(val)
186
+ "#{val.utc.strftime '%FT%T'}Z"
187
+ end
188
+
185
189
  def stats
186
190
  {
187
191
  versions: {
@@ -194,5 +198,12 @@ module Puma
194
198
  }
195
199
  }
196
200
  end
201
+
202
+ # this method call should always be guarded by `@log_writer.debug?`
203
+ def debug_loaded_extensions(str)
204
+ @log_writer.debug "────────────────────────────────── #{str}"
205
+ re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
206
+ $LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
207
+ end
197
208
  end
198
209
  end