puma 7.0.4 → 8.0.2
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.
- checksums.yaml +4 -4
- data/History.md +162 -0
- data/README.md +25 -13
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/deployment.md +58 -23
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +5 -12
- data/docs/plugins.md +2 -2
- data/docs/signals.md +10 -10
- data/docs/stats.md +2 -2
- data/docs/systemd.md +3 -3
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/puma_http11.c +101 -109
- data/lib/puma/app/status.rb +10 -2
- data/lib/puma/binder.rb +2 -2
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +138 -102
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +10 -9
- data/lib/puma/cluster/worker_handle.rb +2 -2
- data/lib/puma/cluster.rb +12 -11
- data/lib/puma/cluster_accept_loop_delay.rb +17 -18
- data/lib/puma/configuration.rb +87 -17
- data/lib/puma/const.rb +4 -3
- data/lib/puma/control_cli.rb +1 -1
- data/lib/puma/detect.rb +11 -0
- data/lib/puma/dsl.rb +115 -18
- data/lib/puma/launcher/bundle_pruner.rb +2 -4
- data/lib/puma/launcher.rb +35 -30
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/reactor.rb +3 -12
- data/lib/puma/{request.rb → response.rb} +25 -194
- data/lib/puma/runner.rb +1 -1
- data/lib/puma/server.rb +103 -64
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +2 -2
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +139 -24
- data/lib/rack/handler/puma.rb +1 -1
- data/tools/Dockerfile +13 -5
- metadata +16 -5
- data/ext/puma_http11/ext_help.h +0 -15
|
@@ -12,7 +12,7 @@ module Puma
|
|
|
12
12
|
# #handle_request, which is called in Server#process_client.
|
|
13
13
|
# @version 5.0.3
|
|
14
14
|
#
|
|
15
|
-
module
|
|
15
|
+
module Response # :nodoc:
|
|
16
16
|
|
|
17
17
|
# Single element array body: smaller bodies are written to io_buffer first,
|
|
18
18
|
# then a single write from io_buffer. Larger sizes are written separately.
|
|
@@ -36,43 +36,28 @@ module Puma
|
|
|
36
36
|
# Takes the request contained in +client+, invokes the Rack application to construct
|
|
37
37
|
# the response and writes it back to +client.io+.
|
|
38
38
|
#
|
|
39
|
-
# It'll return +
|
|
39
|
+
# It'll return +:close+ when the connection is closed, this doesn't mean
|
|
40
40
|
# that the response wasn't successful.
|
|
41
41
|
#
|
|
42
|
+
# It'll return +:keep_alive+ if the connection is a pipeline or keep-alive connection.
|
|
43
|
+
# Which may contain additional requests.
|
|
44
|
+
#
|
|
42
45
|
# It'll return +:async+ if the connection remains open but will be handled
|
|
43
46
|
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
44
47
|
#
|
|
45
48
|
# Finally, it'll return +true+ on keep-alive connections.
|
|
49
|
+
# @param processor [Puma::ThreadPool::ProcessorThread]
|
|
46
50
|
# @param client [Puma::Client]
|
|
47
51
|
# @param requests [Integer]
|
|
48
|
-
# @return [
|
|
49
|
-
|
|
50
|
-
def handle_request(client, requests)
|
|
52
|
+
# @return [:close, :keep_alive, :async]
|
|
53
|
+
def handle_request(processor, client, requests)
|
|
51
54
|
env = client.env
|
|
52
55
|
io_buffer = client.io_buffer
|
|
53
56
|
socket = client.io # io may be a MiniSSL::Socket
|
|
54
57
|
app_body = nil
|
|
55
58
|
error = nil
|
|
56
59
|
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
if client.http_content_length_limit_exceeded
|
|
60
|
-
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
normalize_env env, client
|
|
64
|
-
|
|
65
|
-
env[PUMA_SOCKET] = socket
|
|
66
|
-
|
|
67
|
-
if env[HTTPS_KEY] && socket.peercert
|
|
68
|
-
env[PUMA_PEERCERT] = socket.peercert
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
env[HIJACK_P] = true
|
|
72
|
-
env[HIJACK] = client.method :full_hijack
|
|
73
|
-
|
|
74
|
-
env[RACK_INPUT] = client.body
|
|
75
|
-
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
|
60
|
+
return :close if closed_socket?(socket)
|
|
76
61
|
|
|
77
62
|
if @early_hints
|
|
78
63
|
env[EARLY_HINTS] = lambda { |headers|
|
|
@@ -87,22 +72,11 @@ module Puma
|
|
|
87
72
|
}
|
|
88
73
|
end
|
|
89
74
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# A rack extension. If the app writes #call'ables to this
|
|
93
|
-
# array, we will invoke them when the request is done.
|
|
94
|
-
#
|
|
95
|
-
env[RACK_AFTER_REPLY] ||= []
|
|
96
|
-
env[RACK_RESPONSE_FINISHED] ||= []
|
|
75
|
+
env["puma.mark_as_io_bound"] = -> { processor.mark_as_io_thread! }
|
|
97
76
|
|
|
98
77
|
begin
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@app.call(env)
|
|
102
|
-
end
|
|
103
|
-
else
|
|
104
|
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
|
105
|
-
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
|
78
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
|
79
|
+
@app.call(env)
|
|
106
80
|
end
|
|
107
81
|
|
|
108
82
|
# app_body needs to always be closed, hold value in case lowlevel_error
|
|
@@ -134,7 +108,6 @@ module Puma
|
|
|
134
108
|
prepare_response(status, headers, res_body, requests, client)
|
|
135
109
|
ensure
|
|
136
110
|
io_buffer.reset
|
|
137
|
-
uncork_socket client.io
|
|
138
111
|
app_body.close if app_body.respond_to? :close
|
|
139
112
|
client&.tempfile_close
|
|
140
113
|
if after_reply = env[RACK_AFTER_REPLY]
|
|
@@ -167,13 +140,13 @@ module Puma
|
|
|
167
140
|
# a call to `Server#lowlevel_error`
|
|
168
141
|
# @param requests [Integer] number of inline requests handled
|
|
169
142
|
# @param client [Puma::Client]
|
|
170
|
-
# @return [
|
|
143
|
+
# @return [:close, :keep_alive, :async]
|
|
171
144
|
def prepare_response(status, headers, res_body, requests, client)
|
|
172
145
|
env = client.env
|
|
173
146
|
socket = client.io
|
|
174
147
|
io_buffer = client.io_buffer
|
|
175
148
|
|
|
176
|
-
return
|
|
149
|
+
return :close if closed_socket?(socket)
|
|
177
150
|
|
|
178
151
|
# Close the connection after a reasonable number of inline requests
|
|
179
152
|
force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
|
|
@@ -243,8 +216,9 @@ module Puma
|
|
|
243
216
|
|
|
244
217
|
io_buffer << LINE_END
|
|
245
218
|
fast_write_str socket, io_buffer.read_and_reset
|
|
246
|
-
|
|
247
|
-
|
|
219
|
+
|
|
220
|
+
uncork_socket socket.flush
|
|
221
|
+
return keep_alive ? :keep_alive : :close
|
|
248
222
|
end
|
|
249
223
|
else
|
|
250
224
|
if content_length
|
|
@@ -262,32 +236,16 @@ module Puma
|
|
|
262
236
|
# response_hijack.call
|
|
263
237
|
if response_hijack
|
|
264
238
|
fast_write_str socket, io_buffer.read_and_reset
|
|
265
|
-
uncork_socket socket
|
|
239
|
+
uncork_socket socket.flush
|
|
266
240
|
response_hijack.call socket
|
|
267
241
|
return :async
|
|
268
242
|
end
|
|
269
243
|
|
|
270
244
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
|
271
245
|
body.close if close_body
|
|
272
|
-
# if we're shutting down, close keep_alive connections
|
|
273
|
-
!shutting_down? && keep_alive
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
HTTP_ON_VALUES = { "on" => true, HTTPS => true }
|
|
277
|
-
private_constant :HTTP_ON_VALUES
|
|
278
246
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
#
|
|
282
|
-
def default_server_port(env)
|
|
283
|
-
if HTTP_ON_VALUES[env[HTTPS_KEY]] ||
|
|
284
|
-
env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
|
|
285
|
-
env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
|
|
286
|
-
env[HTTP_X_FORWARDED_SSL] == "on"
|
|
287
|
-
PORT_443
|
|
288
|
-
else
|
|
289
|
-
PORT_80
|
|
290
|
-
end
|
|
247
|
+
# if we're shutting down, close keep_alive connections
|
|
248
|
+
!shutting_down? && keep_alive ? :keep_alive : :close
|
|
291
249
|
end
|
|
292
250
|
|
|
293
251
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
|
@@ -404,6 +362,7 @@ module Puma
|
|
|
404
362
|
end
|
|
405
363
|
end
|
|
406
364
|
socket.flush
|
|
365
|
+
uncork_socket socket
|
|
407
366
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
408
367
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
409
368
|
rescue Errno::EPIPE, SystemCallError, IOError
|
|
@@ -412,85 +371,6 @@ module Puma
|
|
|
412
371
|
|
|
413
372
|
private :fast_write_str, :fast_write_response
|
|
414
373
|
|
|
415
|
-
# Given a Hash +env+ for the request read from +client+, add
|
|
416
|
-
# and fixup keys to comply with Rack's env guidelines.
|
|
417
|
-
# @param env [Hash] see Puma::Client#env, from request
|
|
418
|
-
# @param client [Puma::Client] only needed for Client#peerip
|
|
419
|
-
#
|
|
420
|
-
def normalize_env(env, client)
|
|
421
|
-
if host = env[HTTP_HOST]
|
|
422
|
-
# host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
|
|
423
|
-
if colon = host.rindex("]:") # IPV6 with port
|
|
424
|
-
env[SERVER_NAME] = host[0, colon+1]
|
|
425
|
-
env[SERVER_PORT] = host[colon+2, host.bytesize]
|
|
426
|
-
elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
|
|
427
|
-
env[SERVER_NAME] = host[0, colon]
|
|
428
|
-
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
|
429
|
-
else
|
|
430
|
-
env[SERVER_NAME] = host
|
|
431
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
432
|
-
end
|
|
433
|
-
else
|
|
434
|
-
env[SERVER_NAME] = LOCALHOST
|
|
435
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
unless env[REQUEST_PATH]
|
|
439
|
-
# it might be a dumbass full host request header
|
|
440
|
-
uri = begin
|
|
441
|
-
URI.parse(env[REQUEST_URI])
|
|
442
|
-
rescue URI::InvalidURIError
|
|
443
|
-
raise Puma::HttpParserError
|
|
444
|
-
end
|
|
445
|
-
env[REQUEST_PATH] = uri.path
|
|
446
|
-
|
|
447
|
-
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
|
448
|
-
# so only set the env value if there actually is a value.
|
|
449
|
-
env[QUERY_STRING] = uri.query if uri.query
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
|
453
|
-
|
|
454
|
-
# From https://www.ietf.org/rfc/rfc3875 :
|
|
455
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
|
456
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
457
|
-
# may not identify the ultimate source of the request.
|
|
458
|
-
# They identify the client for the immediate request to the
|
|
459
|
-
# server; that client may be a proxy, gateway, or other
|
|
460
|
-
# intermediary acting on behalf of the actual source client."
|
|
461
|
-
#
|
|
462
|
-
|
|
463
|
-
unless env.key?(REMOTE_ADDR)
|
|
464
|
-
begin
|
|
465
|
-
addr = client.peerip
|
|
466
|
-
rescue Errno::ENOTCONN
|
|
467
|
-
# Client disconnects can result in an inability to get the
|
|
468
|
-
# peeraddr from the socket; default to unspec.
|
|
469
|
-
if client.peer_family == Socket::AF_INET6
|
|
470
|
-
addr = UNSPECIFIED_IPV6
|
|
471
|
-
else
|
|
472
|
-
addr = UNSPECIFIED_IPV4
|
|
473
|
-
end
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
# Set unix socket addrs to localhost
|
|
477
|
-
if addr.empty?
|
|
478
|
-
if client.peer_family == Socket::AF_INET6
|
|
479
|
-
addr = LOCALHOST_IPV6
|
|
480
|
-
else
|
|
481
|
-
addr = LOCALHOST_IPV4
|
|
482
|
-
end
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
env[REMOTE_ADDR] = addr
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
# The legacy HTTP_VERSION header can be sent as a client header.
|
|
489
|
-
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
|
490
|
-
env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
|
|
491
|
-
end
|
|
492
|
-
private :normalize_env
|
|
493
|
-
|
|
494
374
|
# @param header_key [#to_s]
|
|
495
375
|
# @return [Boolean]
|
|
496
376
|
#
|
|
@@ -504,56 +384,6 @@ module Puma
|
|
|
504
384
|
def illegal_header_value?(header_value)
|
|
505
385
|
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
|
506
386
|
end
|
|
507
|
-
private :illegal_header_key?, :illegal_header_value?
|
|
508
|
-
|
|
509
|
-
# Fixup any headers with `,` in the name to have `_` now. We emit
|
|
510
|
-
# headers with `,` in them during the parse phase to avoid ambiguity
|
|
511
|
-
# with the `-` to `_` conversion for critical headers. But here for
|
|
512
|
-
# compatibility, we'll convert them back. This code is written to
|
|
513
|
-
# avoid allocation in the common case (ie there are no headers
|
|
514
|
-
# with `,` in their names), that's why it has the extra conditionals.
|
|
515
|
-
#
|
|
516
|
-
# @note If a normalized version of a `,` header already exists, we ignore
|
|
517
|
-
# the `,` version. This prevents clobbering headers managed by proxies
|
|
518
|
-
# but not by clients (Like X-Forwarded-For).
|
|
519
|
-
#
|
|
520
|
-
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
|
521
|
-
# @version 5.0.3
|
|
522
|
-
#
|
|
523
|
-
def req_env_post_parse(env)
|
|
524
|
-
to_delete = nil
|
|
525
|
-
to_add = nil
|
|
526
|
-
|
|
527
|
-
env.each do |k,v|
|
|
528
|
-
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
|
529
|
-
if to_delete
|
|
530
|
-
to_delete << k
|
|
531
|
-
else
|
|
532
|
-
to_delete = [k]
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
new_k = k.tr(",", "_")
|
|
536
|
-
if env.key?(new_k)
|
|
537
|
-
next
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
unless to_add
|
|
541
|
-
to_add = {}
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
to_add[new_k] = v
|
|
545
|
-
end
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
if to_delete # rubocop:disable Style/SafeNavigation
|
|
549
|
-
to_delete.each { |k| env.delete(k) }
|
|
550
|
-
end
|
|
551
|
-
|
|
552
|
-
if to_add
|
|
553
|
-
env.merge! to_add
|
|
554
|
-
end
|
|
555
|
-
end
|
|
556
|
-
private :req_env_post_parse
|
|
557
387
|
|
|
558
388
|
# Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
|
|
559
389
|
# @param headers [Hash] the headers returned by the Rack application
|
|
@@ -650,7 +480,8 @@ module Puma
|
|
|
650
480
|
headers.each do |k, vs|
|
|
651
481
|
next if illegal_header_key?(k)
|
|
652
482
|
|
|
653
|
-
|
|
483
|
+
key = k.downcase
|
|
484
|
+
case key
|
|
654
485
|
when CONTENT_LENGTH2
|
|
655
486
|
next if illegal_header_value?(vs)
|
|
656
487
|
# nil.to_i is 0, nil&.to_i is nil
|
|
@@ -677,10 +508,10 @@ module Puma
|
|
|
677
508
|
if ary
|
|
678
509
|
ary.each do |v|
|
|
679
510
|
next if illegal_header_value?(v)
|
|
680
|
-
io_buffer.append
|
|
511
|
+
io_buffer.append key, colon, v, line_ending
|
|
681
512
|
end
|
|
682
513
|
else
|
|
683
|
-
io_buffer.append
|
|
514
|
+
io_buffer.append key, colon, line_ending
|
|
684
515
|
end
|
|
685
516
|
end
|
|
686
517
|
|
data/lib/puma/runner.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Puma
|
|
|
70
70
|
token = nil if token.empty? || token == 'none'
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
app = Puma::App::Status.new @launcher, token
|
|
73
|
+
app = Puma::App::Status.new @launcher, token: token, data_only: @options[:control_data_only]
|
|
74
74
|
|
|
75
75
|
# A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
|
|
76
76
|
# Use `nil` for events, no hooks in control server
|
data/lib/puma/server.rb
CHANGED
|
@@ -11,7 +11,7 @@ require_relative 'reactor'
|
|
|
11
11
|
require_relative 'client'
|
|
12
12
|
require_relative 'binder'
|
|
13
13
|
require_relative 'util'
|
|
14
|
-
require_relative '
|
|
14
|
+
require_relative 'response'
|
|
15
15
|
require_relative 'configuration'
|
|
16
16
|
require_relative 'cluster_accept_loop_delay'
|
|
17
17
|
|
|
@@ -19,9 +19,6 @@ require 'socket'
|
|
|
19
19
|
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
|
20
20
|
|
|
21
21
|
module Puma
|
|
22
|
-
# Add `Thread#puma_server` and `Thread#puma_server=`
|
|
23
|
-
Thread.attr_accessor(:puma_server)
|
|
24
|
-
|
|
25
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
|
26
23
|
#
|
|
27
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
|
@@ -34,15 +31,15 @@ module Puma
|
|
|
34
31
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
35
32
|
class Server
|
|
36
33
|
module FiberPerRequest
|
|
37
|
-
def handle_request(client, requests)
|
|
34
|
+
def handle_request(processor, client, requests)
|
|
38
35
|
Fiber.new do
|
|
39
36
|
super
|
|
40
37
|
end.resume
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
40
|
|
|
44
|
-
include
|
|
45
|
-
include
|
|
41
|
+
include Const
|
|
42
|
+
include Response
|
|
46
43
|
|
|
47
44
|
attr_reader :options
|
|
48
45
|
attr_reader :thread
|
|
@@ -262,7 +259,9 @@ module Puma
|
|
|
262
259
|
|
|
263
260
|
@status = :run
|
|
264
261
|
|
|
265
|
-
@thread_pool = ThreadPool.new(thread_name, options)
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) do |processor, client|
|
|
263
|
+
process_client(processor, client)
|
|
264
|
+
end
|
|
266
265
|
|
|
267
266
|
if @queue_requests
|
|
268
267
|
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
@@ -302,12 +301,12 @@ module Puma
|
|
|
302
301
|
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
303
302
|
# such as nginx) then the application would be subject to a slow client attack.
|
|
304
303
|
#
|
|
305
|
-
# For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/
|
|
304
|
+
# For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/main/docs/architecture.md).
|
|
306
305
|
#
|
|
307
306
|
# The method checks to see if it has the full header and body with
|
|
308
307
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
309
308
|
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
|
310
|
-
# so that a "
|
|
309
|
+
# so that a "processor thread" can pick up the request and begin to execute application logic.
|
|
311
310
|
# The Client is then removed from the reactor (return `true`).
|
|
312
311
|
#
|
|
313
312
|
# If a client object times out, a 408 response is written, its connection is closed,
|
|
@@ -404,6 +403,7 @@ module Puma
|
|
|
404
403
|
next
|
|
405
404
|
end
|
|
406
405
|
drain += 1 if shutting_down?
|
|
406
|
+
|
|
407
407
|
client = new_client(io, sock)
|
|
408
408
|
client.send(addr_send_name, addr_value) if addr_value
|
|
409
409
|
pool << client
|
|
@@ -417,7 +417,7 @@ module Puma
|
|
|
417
417
|
end
|
|
418
418
|
end
|
|
419
419
|
|
|
420
|
-
@log_writer.debug "Drained #{drain} additional connections." if drain
|
|
420
|
+
@log_writer.debug { "Drained #{drain} additional connections." } if drain
|
|
421
421
|
@events.fire :state, @status
|
|
422
422
|
|
|
423
423
|
if queue_requests
|
|
@@ -447,7 +447,9 @@ module Puma
|
|
|
447
447
|
def new_client(io, sock)
|
|
448
448
|
client = Client.new(io, @binder.env(sock))
|
|
449
449
|
client.listener = sock
|
|
450
|
+
client.env_set_http_version = @env_set_http_version
|
|
450
451
|
client.http_content_length_limit = @http_content_length_limit
|
|
452
|
+
client.supported_http_methods = @supported_http_methods
|
|
451
453
|
client
|
|
452
454
|
end
|
|
453
455
|
|
|
@@ -473,17 +475,14 @@ module Puma
|
|
|
473
475
|
# Given a connection on +client+, handle the incoming requests,
|
|
474
476
|
# or queue the connection in the Reactor if no request is available.
|
|
475
477
|
#
|
|
476
|
-
# This method is called from a ThreadPool
|
|
478
|
+
# This method is called from a ThreadPool processor thread.
|
|
477
479
|
#
|
|
478
480
|
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
479
481
|
# indicates that it supports keep alive, wait for another request before
|
|
480
482
|
# returning.
|
|
481
483
|
#
|
|
482
484
|
# Return true if one or more requests were processed.
|
|
483
|
-
def process_client(client)
|
|
484
|
-
# Advertise this server into the thread
|
|
485
|
-
Thread.current.puma_server = self
|
|
486
|
-
|
|
485
|
+
def process_client(processor, client)
|
|
487
486
|
close_socket = true
|
|
488
487
|
|
|
489
488
|
requests = 0
|
|
@@ -502,31 +501,41 @@ module Puma
|
|
|
502
501
|
client.finish(@first_data_timeout)
|
|
503
502
|
end
|
|
504
503
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
504
|
+
can_loop = true
|
|
505
|
+
while can_loop
|
|
506
|
+
can_loop = false
|
|
507
|
+
@requests_count += 1
|
|
508
|
+
case handle_request(processor, client, requests + 1)
|
|
509
|
+
when :close
|
|
510
|
+
when :async
|
|
511
|
+
close_socket = false
|
|
512
|
+
when :keep_alive
|
|
513
|
+
requests += 1
|
|
512
514
|
|
|
513
|
-
|
|
515
|
+
client.reset
|
|
514
516
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
517
|
+
# This indicates data exists in the client read buffer and there may be
|
|
518
|
+
# additional requests on it, so process them
|
|
519
|
+
next_request_ready = if client.has_back_to_back_requests?
|
|
520
|
+
with_force_shutdown(client) { client.process_back_to_back_requests }
|
|
521
|
+
else
|
|
522
|
+
with_force_shutdown(client) { client.eagerly_finish }
|
|
523
|
+
end
|
|
522
524
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
525
|
+
if next_request_ready
|
|
526
|
+
# When Puma has spare threads, allow this one to be monopolized
|
|
527
|
+
# Perf optimization for https://github.com/puma/puma/issues/3788
|
|
528
|
+
if @thread_pool.waiting > 0
|
|
529
|
+
can_loop = true
|
|
530
|
+
else
|
|
531
|
+
@thread_pool << client
|
|
532
|
+
close_socket = false
|
|
533
|
+
end
|
|
534
|
+
elsif @queue_requests
|
|
535
|
+
client.set_timeout @persistent_timeout
|
|
536
|
+
if @reactor.add client
|
|
537
|
+
close_socket = false
|
|
538
|
+
end
|
|
530
539
|
end
|
|
531
540
|
end
|
|
532
541
|
end
|
|
@@ -573,7 +582,7 @@ module Puma
|
|
|
573
582
|
lowlevel_error(e, client.env)
|
|
574
583
|
@log_writer.ssl_error e, client.io
|
|
575
584
|
when HttpParserError
|
|
576
|
-
response_to_error(client, requests, e, 400)
|
|
585
|
+
response_to_error(client, requests, e, client.error_status_code || 400)
|
|
577
586
|
@log_writer.parse_error e, client
|
|
578
587
|
when HttpParserError501
|
|
579
588
|
response_to_error(client, requests, e, 501)
|
|
@@ -606,7 +615,15 @@ module Puma
|
|
|
606
615
|
end
|
|
607
616
|
|
|
608
617
|
def response_to_error(client, requests, err, status_code)
|
|
609
|
-
|
|
618
|
+
# @todo remove sometime later
|
|
619
|
+
if status_code == 413
|
|
620
|
+
status = 413
|
|
621
|
+
res_body = ["Payload Too Large"]
|
|
622
|
+
headers = {}
|
|
623
|
+
headers[CONTENT_LENGTH2] = 17
|
|
624
|
+
else
|
|
625
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
|
626
|
+
end
|
|
610
627
|
prepare_response(status, headers, res_body, requests, client)
|
|
611
628
|
end
|
|
612
629
|
private :response_to_error
|
|
@@ -614,32 +631,11 @@ module Puma
|
|
|
614
631
|
# Wait for all outstanding requests to finish.
|
|
615
632
|
#
|
|
616
633
|
def graceful_shutdown
|
|
617
|
-
if options[:shutdown_debug]
|
|
618
|
-
threads = Thread.list
|
|
619
|
-
total = threads.size
|
|
620
|
-
|
|
621
|
-
pid = Process.pid
|
|
622
|
-
|
|
623
|
-
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
|
624
|
-
|
|
625
|
-
threads.each_with_index do |t,i|
|
|
626
|
-
$stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
|
|
627
|
-
$stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
|
|
628
|
-
end
|
|
629
|
-
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
|
630
|
-
end
|
|
631
|
-
|
|
632
634
|
if @status != :restart
|
|
633
635
|
@binder.close
|
|
634
636
|
end
|
|
635
637
|
|
|
636
|
-
|
|
637
|
-
if timeout = options[:force_shutdown_after]
|
|
638
|
-
@thread_pool.shutdown timeout.to_f
|
|
639
|
-
else
|
|
640
|
-
@thread_pool.shutdown
|
|
641
|
-
end
|
|
642
|
-
end
|
|
638
|
+
@thread_pool.shutdown(options[:force_shutdown_after])
|
|
643
639
|
end
|
|
644
640
|
|
|
645
641
|
def notify_safely(message)
|
|
@@ -656,7 +652,7 @@ module Puma
|
|
|
656
652
|
end
|
|
657
653
|
private :notify_safely
|
|
658
654
|
|
|
659
|
-
# Stops the acceptor thread and then causes the
|
|
655
|
+
# Stops the acceptor thread and then causes the processor threads to finish
|
|
660
656
|
# off the request queue before finally exiting.
|
|
661
657
|
|
|
662
658
|
def stop(sync=false)
|
|
@@ -706,7 +702,7 @@ module Puma
|
|
|
706
702
|
|
|
707
703
|
def reset_max
|
|
708
704
|
@reactor.reactor_max = 0 if @reactor
|
|
709
|
-
@thread_pool
|
|
705
|
+
@thread_pool&.reset_max
|
|
710
706
|
end
|
|
711
707
|
|
|
712
708
|
# below are 'delegations' to binder
|
|
@@ -726,6 +722,49 @@ module Puma
|
|
|
726
722
|
@binder.add_unix_listener path, umask, mode, backlog
|
|
727
723
|
end
|
|
728
724
|
|
|
725
|
+
# Updates the minimum and maximum number of threads in the thread pool.
|
|
726
|
+
#
|
|
727
|
+
# This method allows dynamic adjustment of the thread pool size while the server
|
|
728
|
+
# is running. It validates the provided values and updates both the thread pool
|
|
729
|
+
# and the server's thread configuration.
|
|
730
|
+
#
|
|
731
|
+
# @param min [Integer] The minimum number of threads to maintain in the pool.
|
|
732
|
+
# Defaults to the current minimum if not specified. Must be greater than 0
|
|
733
|
+
# and less than or equal to max.
|
|
734
|
+
# @param max [Integer] The maximum number of threads allowed in the pool.
|
|
735
|
+
# Defaults to the current maximum if not specified. Must be greater than or
|
|
736
|
+
# equal to min.
|
|
737
|
+
#
|
|
738
|
+
# @return [void]
|
|
739
|
+
#
|
|
740
|
+
# @note If validation fails, a warning message is logged and no changes are made.
|
|
741
|
+
#
|
|
742
|
+
# @example Update both min and max threads
|
|
743
|
+
# server.update_thread_pool_min_max(min: 2, max: 8)
|
|
744
|
+
#
|
|
745
|
+
# @example Update only the minimum threads
|
|
746
|
+
# server.update_thread_pool_min_max(min: 4)
|
|
747
|
+
#
|
|
748
|
+
# @example Update only the maximum threads
|
|
749
|
+
# server.update_thread_pool_min_max(max: 16)
|
|
750
|
+
#
|
|
751
|
+
def update_thread_pool_min_max(min: @min_threads, max: @max_threads)
|
|
752
|
+
if min > max
|
|
753
|
+
@log_writer.log "`min' value cannot be greater than `max' value."
|
|
754
|
+
return
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
if min < 0
|
|
758
|
+
@log_writer.log "`min' value cannot be less than 0"
|
|
759
|
+
return
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
@thread_pool&.with_mutex do
|
|
763
|
+
@thread_pool.min, @thread_pool.max = min, max
|
|
764
|
+
@min_threads, @max_threads = min, max
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
729
768
|
# @!attribute [r] connected_ports
|
|
730
769
|
def connected_ports
|
|
731
770
|
@binder.connected_ports
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Puma
|
|
2
|
+
# ServerPluginControl provides a control interface for server plugins to
|
|
3
|
+
# interact with and manage server settings dynamically.
|
|
4
|
+
#
|
|
5
|
+
# This class acts as a facade between plugins and the Puma server,
|
|
6
|
+
# allowing plugins to safely modify server configuration and thread pool
|
|
7
|
+
# settings without direct access to the server's internal state.
|
|
8
|
+
#
|
|
9
|
+
class ServerPluginControl
|
|
10
|
+
def initialize(server)
|
|
11
|
+
@server = server
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns the maximum number of threads in the thread pool.
|
|
15
|
+
def max_threads
|
|
16
|
+
@server.max_threads
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns the minimum number of threads in the thread pool.
|
|
20
|
+
def min_threads
|
|
21
|
+
@server.min_threads
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Updates the minimum and maximum number of threads in the thread pool.
|
|
25
|
+
#
|
|
26
|
+
# @see Puma::Server#update_thread_pool_min_max
|
|
27
|
+
#
|
|
28
|
+
def update_thread_pool_min_max(min: max_threads, max: min_threads)
|
|
29
|
+
@server.update_thread_pool_min_max(min: min, max: max)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/puma/single.rb
CHANGED
data/lib/puma/state_file.rb
CHANGED
|
@@ -32,10 +32,11 @@ module Puma
|
|
|
32
32
|
"#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
+
|
|
35
36
|
if permission
|
|
36
|
-
File.write path, contents, mode: 'wb:UTF-8'
|
|
37
|
-
else
|
|
38
37
|
File.write path, contents, mode: 'wb:UTF-8', perm: permission
|
|
38
|
+
else
|
|
39
|
+
File.write path, contents, mode: 'wb:UTF-8'
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|