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.
- checksums.yaml +4 -4
- data/History.md +322 -48
- data/LICENSE +0 -0
- data/README.md +95 -24
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +57 -20
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +53 -67
- data/docs/fork_worker.md +2 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +1 -1
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +7 -7
- data/docs/signals.md +11 -10
- data/docs/stats.md +142 -0
- data/docs/systemd.md +85 -66
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +42 -6
- data/ext/puma_http11/http11_parser.c +68 -57
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +226 -88
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
- data/ext/puma_http11/puma_http11.c +9 -3
- data/lib/puma/app/status.rb +4 -7
- data/lib/puma/binder.rb +138 -49
- data/lib/puma/cli.rb +18 -4
- data/lib/puma/client.rb +113 -31
- data/lib/puma/cluster/worker.rb +22 -19
- data/lib/puma/cluster/worker_handle.rb +13 -2
- data/lib/puma/cluster.rb +75 -33
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +21 -2
- data/lib/puma/const.rb +17 -8
- data/lib/puma/control_cli.rb +76 -71
- data/lib/puma/detect.rb +19 -9
- data/lib/puma/dsl.rb +225 -31
- data/lib/puma/error_logger.rb +12 -5
- data/lib/puma/events.rb +18 -3
- data/lib/puma/io_buffer.rb +0 -0
- data/lib/puma/jruby_restart.rb +0 -0
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +56 -7
- data/lib/puma/minissl/context_builder.rb +14 -6
- data/lib/puma/minissl.rb +72 -40
- data/lib/puma/null_io.rb +12 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/queue_close.rb +7 -7
- data/lib/puma/rack/builder.rb +1 -1
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +0 -0
- data/lib/puma/reactor.rb +19 -12
- data/lib/puma/request.rb +55 -21
- data/lib/puma/runner.rb +39 -13
- data/lib/puma/server.rb +78 -142
- data/lib/puma/single.rb +0 -0
- data/lib/puma/state_file.rb +45 -9
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +11 -8
- data/lib/puma/util.rb +8 -1
- data/lib/puma.rb +36 -10
- data/lib/rack/handler/puma.rb +1 -0
- data/tools/Dockerfile +1 -1
- data/tools/trickletest.rb +0 -0
- 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
|
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]
|
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
|
-
|
152
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
289
|
-
!!(
|
306
|
+
def illegal_header_value?(header_value)
|
307
|
+
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
290
308
|
end
|
291
|
-
private :
|
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
|
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
|
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
|
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 "*
|
90
|
-
log "*
|
91
|
-
log "*
|
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
|
-
|
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
|
-
|
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
|
88
|
-
@first_data_timeout
|
89
|
-
@min_threads
|
90
|
-
@max_threads
|
91
|
-
@persistent_timeout
|
92
|
-
@queue_requests
|
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
|
-
|
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
|
-
|
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
|
-
|
147
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
149
148
|
rescue IOError, SystemCallError
|
150
|
-
|
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
|
-
|
156
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
157
157
|
rescue IOError, SystemCallError
|
158
|
-
|
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
|
-
|
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 =
|
177
|
+
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
176
178
|
rescue IOError, SystemCallError
|
177
|
-
|
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
|
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
|
-
|
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
|
-
|
320
|
+
[:peerip=, @options[:remote_address_value]]
|
317
321
|
when :header
|
318
|
-
remote_addr_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
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
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
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|