puma 6.4.3 → 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 +448 -8
- data/README.md +110 -51
- 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/fork_worker.md +11 -1
- 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/java_options.md +54 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +6 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +11 -5
- data/docs/systemd.md +14 -5
- data/ext/puma_http11/extconf.rb +20 -32
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +29 -9
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +194 -101
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/puma_http11.c +125 -118
- data/lib/puma/app/status.rb +11 -3
- data/lib/puma/binder.rb +22 -12
- data/lib/puma/cli.rb +11 -9
- data/lib/puma/client.rb +233 -136
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +24 -21
- data/lib/puma/cluster/worker_handle.rb +38 -8
- data/lib/puma/cluster.rb +74 -48
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +197 -64
- data/lib/puma/const.rb +23 -12
- data/lib/puma/control_cli.rb +11 -7
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +483 -127
- data/lib/puma/error_logger.rb +7 -5
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher/bundle_pruner.rb +3 -5
- data/lib/puma/launcher.rb +76 -59
- data/lib/puma/log_writer.rb +17 -11
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -1
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -13
- data/lib/puma/{request.rb → response.rb} +57 -209
- data/lib/puma/runner.rb +15 -17
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +200 -104
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +7 -4
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +179 -96
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +11 -8
- data/tools/Dockerfile +15 -5
- metadata +26 -16
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/server.rb
CHANGED
|
@@ -11,13 +11,14 @@ 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
|
+
require_relative 'configuration'
|
|
16
|
+
require_relative 'cluster_accept_loop_delay'
|
|
15
17
|
|
|
16
18
|
require 'socket'
|
|
17
19
|
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
|
18
20
|
|
|
19
21
|
module Puma
|
|
20
|
-
|
|
21
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
|
22
23
|
#
|
|
23
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
|
@@ -29,9 +30,18 @@ module Puma
|
|
|
29
30
|
#
|
|
30
31
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
31
32
|
class Server
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
module FiberPerRequest
|
|
34
|
+
def handle_request(processor, client, requests)
|
|
35
|
+
Fiber.new do
|
|
36
|
+
super
|
|
37
|
+
end.resume
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
include Const
|
|
42
|
+
include Response
|
|
34
43
|
|
|
44
|
+
attr_reader :options
|
|
35
45
|
attr_reader :thread
|
|
36
46
|
attr_reader :log_writer
|
|
37
47
|
attr_reader :events
|
|
@@ -46,8 +56,6 @@ module Puma
|
|
|
46
56
|
attr_accessor :app
|
|
47
57
|
attr_accessor :binder
|
|
48
58
|
|
|
49
|
-
THREAD_LOCAL_KEY = :puma_server
|
|
50
|
-
|
|
51
59
|
# Create a server for the rack app +app+.
|
|
52
60
|
#
|
|
53
61
|
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
|
@@ -74,6 +82,9 @@ module Puma
|
|
|
74
82
|
|
|
75
83
|
@thread = nil
|
|
76
84
|
@thread_pool = nil
|
|
85
|
+
@reactor = nil
|
|
86
|
+
|
|
87
|
+
@env_set_http_version = nil
|
|
77
88
|
|
|
78
89
|
@options = if options.is_a?(UserFileDefaultOptions)
|
|
79
90
|
options
|
|
@@ -91,9 +102,19 @@ module Puma
|
|
|
91
102
|
@min_threads = @options[:min_threads]
|
|
92
103
|
@max_threads = @options[:max_threads]
|
|
93
104
|
@queue_requests = @options[:queue_requests]
|
|
94
|
-
@
|
|
105
|
+
@max_keep_alive = @options[:max_keep_alive]
|
|
106
|
+
@enable_keep_alives = @options[:enable_keep_alives]
|
|
107
|
+
@enable_keep_alives &&= @queue_requests
|
|
95
108
|
@io_selector_backend = @options[:io_selector_backend]
|
|
96
109
|
@http_content_length_limit = @options[:http_content_length_limit]
|
|
110
|
+
@cluster_accept_loop_delay = ClusterAcceptLoopDelay.new(
|
|
111
|
+
workers: @options[:workers],
|
|
112
|
+
max_delay: @options[:wait_for_less_busy_worker] || 0 # Real default is in Configuration::DEFAULTS, this is for unit testing
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if @options[:fiber_per_request]
|
|
116
|
+
singleton_class.prepend(FiberPerRequest)
|
|
117
|
+
end
|
|
97
118
|
|
|
98
119
|
# make this a hash, since we prefer `key?` over `include?`
|
|
99
120
|
@supported_http_methods =
|
|
@@ -110,7 +131,7 @@ module Puma
|
|
|
110
131
|
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
|
111
132
|
@leak_stack_on_error = @options[:environment] ? temp : true
|
|
112
133
|
|
|
113
|
-
@binder = Binder.new(log_writer)
|
|
134
|
+
@binder = Binder.new(log_writer, @options)
|
|
114
135
|
|
|
115
136
|
ENV['RACK_ENV'] ||= "development"
|
|
116
137
|
|
|
@@ -130,7 +151,7 @@ module Puma
|
|
|
130
151
|
class << self
|
|
131
152
|
# @!attribute [r] current
|
|
132
153
|
def current
|
|
133
|
-
Thread.current
|
|
154
|
+
Thread.current.puma_server
|
|
134
155
|
end
|
|
135
156
|
|
|
136
157
|
# :nodoc:
|
|
@@ -161,7 +182,6 @@ module Puma
|
|
|
161
182
|
begin
|
|
162
183
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
|
163
184
|
rescue IOError, SystemCallError
|
|
164
|
-
Puma::Util.purge_interrupt_queue
|
|
165
185
|
end
|
|
166
186
|
end
|
|
167
187
|
|
|
@@ -170,7 +190,6 @@ module Puma
|
|
|
170
190
|
begin
|
|
171
191
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
|
172
192
|
rescue IOError, SystemCallError
|
|
173
|
-
Puma::Util.purge_interrupt_queue
|
|
174
193
|
end
|
|
175
194
|
end
|
|
176
195
|
else
|
|
@@ -191,7 +210,6 @@ module Puma
|
|
|
191
210
|
begin
|
|
192
211
|
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
|
193
212
|
rescue IOError, SystemCallError
|
|
194
|
-
Puma::Util.purge_interrupt_queue
|
|
195
213
|
@precheck_closing = false
|
|
196
214
|
false
|
|
197
215
|
else
|
|
@@ -216,7 +234,6 @@ module Puma
|
|
|
216
234
|
@thread_pool&.spawned
|
|
217
235
|
end
|
|
218
236
|
|
|
219
|
-
|
|
220
237
|
# This number represents the number of requests that
|
|
221
238
|
# the server is capable of taking right now.
|
|
222
239
|
#
|
|
@@ -242,16 +259,21 @@ module Puma
|
|
|
242
259
|
|
|
243
260
|
@status = :run
|
|
244
261
|
|
|
245
|
-
@thread_pool = ThreadPool.new(thread_name,
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) do |processor, client|
|
|
263
|
+
process_client(processor, client)
|
|
264
|
+
end
|
|
246
265
|
|
|
247
266
|
if @queue_requests
|
|
248
|
-
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
267
|
+
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
268
|
+
# Inversion of control, the reactor is calling a method on the server when it
|
|
269
|
+
# is done buffering a request or receives a new request from a keepalive connection.
|
|
270
|
+
self.reactor_wakeup(c)
|
|
271
|
+
}
|
|
249
272
|
@reactor.run
|
|
250
273
|
end
|
|
251
274
|
|
|
252
|
-
|
|
253
|
-
@thread_pool.
|
|
254
|
-
@thread_pool.auto_trim! if @options[:auto_trim_time]
|
|
275
|
+
@thread_pool.auto_reap! if options[:reaping_time]
|
|
276
|
+
@thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
|
|
255
277
|
|
|
256
278
|
@check, @notify = Puma::Util.pipe unless @notify
|
|
257
279
|
|
|
@@ -271,17 +293,20 @@ module Puma
|
|
|
271
293
|
# This method is called from the Reactor thread when a queued Client receives data,
|
|
272
294
|
# times out, or when the Reactor is shutting down.
|
|
273
295
|
#
|
|
296
|
+
# While the code lives in the Server, the logic is executed on the reactor thread, independently
|
|
297
|
+
# from the server.
|
|
298
|
+
#
|
|
274
299
|
# It is responsible for ensuring that a request has been completely received
|
|
275
300
|
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
|
276
301
|
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
277
302
|
# such as nginx) then the application would be subject to a slow client attack.
|
|
278
303
|
#
|
|
279
|
-
# 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).
|
|
280
305
|
#
|
|
281
306
|
# The method checks to see if it has the full header and body with
|
|
282
307
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
283
308
|
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
|
284
|
-
# so that a "
|
|
309
|
+
# so that a "processor thread" can pick up the request and begin to execute application logic.
|
|
285
310
|
# The Client is then removed from the reactor (return `true`).
|
|
286
311
|
#
|
|
287
312
|
# If a client object times out, a 408 response is written, its connection is closed,
|
|
@@ -305,25 +330,28 @@ module Puma
|
|
|
305
330
|
end
|
|
306
331
|
rescue StandardError => e
|
|
307
332
|
client_error(e, client)
|
|
308
|
-
client
|
|
333
|
+
close_client_safely(client)
|
|
309
334
|
true
|
|
310
335
|
end
|
|
311
336
|
|
|
312
337
|
def handle_servers
|
|
338
|
+
@env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
|
|
339
|
+
Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
|
|
340
|
+
|
|
313
341
|
begin
|
|
314
342
|
check = @check
|
|
315
343
|
sockets = [check] + @binder.ios
|
|
316
344
|
pool = @thread_pool
|
|
317
345
|
queue_requests = @queue_requests
|
|
318
|
-
drain =
|
|
346
|
+
drain = options[:drain_on_shutdown] ? 0 : nil
|
|
319
347
|
|
|
320
|
-
addr_send_name, addr_value = case
|
|
348
|
+
addr_send_name, addr_value = case options[:remote_address]
|
|
321
349
|
when :value
|
|
322
|
-
[:peerip=,
|
|
350
|
+
[:peerip=, options[:remote_address_value]]
|
|
323
351
|
when :header
|
|
324
|
-
[:remote_addr_header=,
|
|
352
|
+
[:remote_addr_header=, options[:remote_address_header]]
|
|
325
353
|
when :proxy_protocol
|
|
326
|
-
[:expect_proxy_proto=,
|
|
354
|
+
[:expect_proxy_proto=, options[:remote_address_proxy_protocol]]
|
|
327
355
|
else
|
|
328
356
|
[nil, nil]
|
|
329
357
|
end
|
|
@@ -336,7 +364,7 @@ module Puma
|
|
|
336
364
|
@idle_timeout_reached = true
|
|
337
365
|
|
|
338
366
|
if @clustered
|
|
339
|
-
@worker_write << "
|
|
367
|
+
@worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
|
|
340
368
|
next
|
|
341
369
|
else
|
|
342
370
|
@log_writer.log "- Idle timeout reached"
|
|
@@ -349,15 +377,25 @@ module Puma
|
|
|
349
377
|
|
|
350
378
|
if @idle_timeout_reached && @clustered
|
|
351
379
|
@idle_timeout_reached = false
|
|
352
|
-
@worker_write << "
|
|
380
|
+
@worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
|
|
353
381
|
end
|
|
354
382
|
|
|
355
383
|
ios.first.each do |sock|
|
|
356
384
|
if sock == check
|
|
357
385
|
break if handle_check
|
|
358
386
|
else
|
|
359
|
-
|
|
360
|
-
|
|
387
|
+
# if ThreadPool out_of_band code is running, we don't want to add
|
|
388
|
+
# clients until the code is finished.
|
|
389
|
+
pool.wait_while_out_of_band_running
|
|
390
|
+
|
|
391
|
+
# A well rested herd (cluster) runs faster
|
|
392
|
+
if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
|
|
393
|
+
delay = @cluster_accept_loop_delay.calculate(
|
|
394
|
+
max_threads: @max_threads,
|
|
395
|
+
busy_threads_plus_todo: busy_threads_plus_todo
|
|
396
|
+
)
|
|
397
|
+
sleep(delay)
|
|
398
|
+
end
|
|
361
399
|
|
|
362
400
|
io = begin
|
|
363
401
|
sock.accept_nonblock
|
|
@@ -365,11 +403,10 @@ module Puma
|
|
|
365
403
|
next
|
|
366
404
|
end
|
|
367
405
|
drain += 1 if shutting_down?
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
406
|
+
|
|
407
|
+
client = new_client(io, sock)
|
|
408
|
+
client.send(addr_send_name, addr_value) if addr_value
|
|
409
|
+
pool << client
|
|
373
410
|
end
|
|
374
411
|
end
|
|
375
412
|
rescue IOError, Errno::EBADF
|
|
@@ -380,7 +417,7 @@ module Puma
|
|
|
380
417
|
end
|
|
381
418
|
end
|
|
382
419
|
|
|
383
|
-
@log_writer.debug "Drained #{drain} additional connections." if drain
|
|
420
|
+
@log_writer.debug { "Drained #{drain} additional connections." } if drain
|
|
384
421
|
@events.fire :state, @status
|
|
385
422
|
|
|
386
423
|
if queue_requests
|
|
@@ -406,6 +443,16 @@ module Puma
|
|
|
406
443
|
@events.fire :state, :done
|
|
407
444
|
end
|
|
408
445
|
|
|
446
|
+
# :nodoc:
|
|
447
|
+
def new_client(io, sock)
|
|
448
|
+
client = Client.new(io, @binder.env(sock))
|
|
449
|
+
client.listener = sock
|
|
450
|
+
client.env_set_http_version = @env_set_http_version
|
|
451
|
+
client.http_content_length_limit = @http_content_length_limit
|
|
452
|
+
client.supported_http_methods = @supported_http_methods
|
|
453
|
+
client
|
|
454
|
+
end
|
|
455
|
+
|
|
409
456
|
# :nodoc:
|
|
410
457
|
def handle_check
|
|
411
458
|
cmd = @check.read(1)
|
|
@@ -428,25 +475,20 @@ module Puma
|
|
|
428
475
|
# Given a connection on +client+, handle the incoming requests,
|
|
429
476
|
# or queue the connection in the Reactor if no request is available.
|
|
430
477
|
#
|
|
431
|
-
# This method is called from a ThreadPool
|
|
478
|
+
# This method is called from a ThreadPool processor thread.
|
|
432
479
|
#
|
|
433
480
|
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
434
481
|
# indicates that it supports keep alive, wait for another request before
|
|
435
482
|
# returning.
|
|
436
483
|
#
|
|
437
484
|
# Return true if one or more requests were processed.
|
|
438
|
-
def process_client(client)
|
|
439
|
-
# Advertise this server into the thread
|
|
440
|
-
Thread.current[THREAD_LOCAL_KEY] = self
|
|
441
|
-
|
|
442
|
-
clean_thread_locals = @options[:clean_thread_locals]
|
|
485
|
+
def process_client(processor, client)
|
|
443
486
|
close_socket = true
|
|
444
487
|
|
|
445
488
|
requests = 0
|
|
446
489
|
|
|
447
490
|
begin
|
|
448
|
-
if @queue_requests &&
|
|
449
|
-
!client.eagerly_finish
|
|
491
|
+
if @queue_requests && !client.eagerly_finish
|
|
450
492
|
|
|
451
493
|
client.set_timeout(@first_data_timeout)
|
|
452
494
|
if @reactor.add client
|
|
@@ -459,38 +501,40 @@ module Puma
|
|
|
459
501
|
client.finish(@first_data_timeout)
|
|
460
502
|
end
|
|
461
503
|
|
|
462
|
-
|
|
504
|
+
can_loop = true
|
|
505
|
+
while can_loop
|
|
506
|
+
can_loop = false
|
|
463
507
|
@requests_count += 1
|
|
464
|
-
case handle_request(client, requests + 1)
|
|
465
|
-
when
|
|
466
|
-
break
|
|
508
|
+
case handle_request(processor, client, requests + 1)
|
|
509
|
+
when :close
|
|
467
510
|
when :async
|
|
468
511
|
close_socket = false
|
|
469
|
-
|
|
470
|
-
when true
|
|
471
|
-
ThreadPool.clean_thread_locals if clean_thread_locals
|
|
472
|
-
|
|
512
|
+
when :keep_alive
|
|
473
513
|
requests += 1
|
|
474
514
|
|
|
475
|
-
|
|
476
|
-
# socket for a short time before returning to the reactor.
|
|
477
|
-
fast_check = @status == :run
|
|
478
|
-
|
|
479
|
-
# Always pass the client back to the reactor after a reasonable
|
|
480
|
-
# number of inline requests if there are other requests pending.
|
|
481
|
-
fast_check = false if requests >= @max_fast_inline &&
|
|
482
|
-
@thread_pool.backlog > 0
|
|
515
|
+
client.reset
|
|
483
516
|
|
|
484
|
-
|
|
485
|
-
|
|
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 }
|
|
486
523
|
end
|
|
487
524
|
|
|
488
|
-
|
|
489
|
-
|
|
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
|
|
490
535
|
client.set_timeout @persistent_timeout
|
|
491
536
|
if @reactor.add client
|
|
492
537
|
close_socket = false
|
|
493
|
-
break
|
|
494
538
|
end
|
|
495
539
|
end
|
|
496
540
|
end
|
|
@@ -503,17 +547,21 @@ module Puma
|
|
|
503
547
|
ensure
|
|
504
548
|
client.io_buffer.reset
|
|
505
549
|
|
|
506
|
-
|
|
507
|
-
client.close if close_socket
|
|
508
|
-
rescue IOError, SystemCallError
|
|
509
|
-
Puma::Util.purge_interrupt_queue
|
|
510
|
-
# Already closed
|
|
511
|
-
rescue StandardError => e
|
|
512
|
-
@log_writer.unknown_error e, nil, "Client"
|
|
513
|
-
end
|
|
550
|
+
close_client_safely(client) if close_socket
|
|
514
551
|
end
|
|
515
552
|
end
|
|
516
553
|
|
|
554
|
+
# :nodoc:
|
|
555
|
+
def close_client_safely(client)
|
|
556
|
+
client.close
|
|
557
|
+
rescue IOError, SystemCallError
|
|
558
|
+
# Already closed
|
|
559
|
+
rescue MiniSSL::SSLError => e
|
|
560
|
+
@log_writer.ssl_error e, client.io
|
|
561
|
+
rescue StandardError => e
|
|
562
|
+
@log_writer.unknown_error e, nil, "Client"
|
|
563
|
+
end
|
|
564
|
+
|
|
517
565
|
# Triggers a client timeout if the thread-pool shuts down
|
|
518
566
|
# during execution of the provided block.
|
|
519
567
|
def with_force_shutdown(client, &block)
|
|
@@ -534,7 +582,7 @@ module Puma
|
|
|
534
582
|
lowlevel_error(e, client.env)
|
|
535
583
|
@log_writer.ssl_error e, client.io
|
|
536
584
|
when HttpParserError
|
|
537
|
-
response_to_error(client, requests, e, 400)
|
|
585
|
+
response_to_error(client, requests, e, client.error_status_code || 400)
|
|
538
586
|
@log_writer.parse_error e, client
|
|
539
587
|
when HttpParserError501
|
|
540
588
|
response_to_error(client, requests, e, 501)
|
|
@@ -548,7 +596,7 @@ module Puma
|
|
|
548
596
|
# A fallback rack response if +@app+ raises as exception.
|
|
549
597
|
#
|
|
550
598
|
def lowlevel_error(e, env, status=500)
|
|
551
|
-
if handler =
|
|
599
|
+
if handler = options[:lowlevel_error_handler]
|
|
552
600
|
if handler.arity == 1
|
|
553
601
|
return handler.call(e)
|
|
554
602
|
elsif handler.arity == 2
|
|
@@ -567,59 +615,44 @@ module Puma
|
|
|
567
615
|
end
|
|
568
616
|
|
|
569
617
|
def response_to_error(client, requests, err, status_code)
|
|
570
|
-
|
|
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
|
|
571
627
|
prepare_response(status, headers, res_body, requests, client)
|
|
572
|
-
client.write_error(status_code)
|
|
573
628
|
end
|
|
574
629
|
private :response_to_error
|
|
575
630
|
|
|
576
631
|
# Wait for all outstanding requests to finish.
|
|
577
632
|
#
|
|
578
633
|
def graceful_shutdown
|
|
579
|
-
if @options[:shutdown_debug]
|
|
580
|
-
threads = Thread.list
|
|
581
|
-
total = threads.size
|
|
582
|
-
|
|
583
|
-
pid = Process.pid
|
|
584
|
-
|
|
585
|
-
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
|
586
|
-
|
|
587
|
-
threads.each_with_index do |t,i|
|
|
588
|
-
$stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
|
|
589
|
-
$stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
|
|
590
|
-
end
|
|
591
|
-
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
|
592
|
-
end
|
|
593
|
-
|
|
594
634
|
if @status != :restart
|
|
595
635
|
@binder.close
|
|
596
636
|
end
|
|
597
637
|
|
|
598
|
-
|
|
599
|
-
if timeout = @options[:force_shutdown_after]
|
|
600
|
-
@thread_pool.shutdown timeout.to_f
|
|
601
|
-
else
|
|
602
|
-
@thread_pool.shutdown
|
|
603
|
-
end
|
|
604
|
-
end
|
|
638
|
+
@thread_pool.shutdown(options[:force_shutdown_after])
|
|
605
639
|
end
|
|
606
640
|
|
|
607
641
|
def notify_safely(message)
|
|
608
642
|
@notify << message
|
|
609
643
|
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
|
610
644
|
# The server, in another thread, is shutting down
|
|
611
|
-
Puma::Util.purge_interrupt_queue
|
|
612
645
|
rescue RuntimeError => e
|
|
613
646
|
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
|
614
647
|
if e.message.include?('IOError')
|
|
615
|
-
|
|
648
|
+
# ignore
|
|
616
649
|
else
|
|
617
650
|
raise e
|
|
618
651
|
end
|
|
619
652
|
end
|
|
620
653
|
private :notify_safely
|
|
621
654
|
|
|
622
|
-
# Stops the acceptor thread and then causes the
|
|
655
|
+
# Stops the acceptor thread and then causes the processor threads to finish
|
|
623
656
|
# off the request queue before finally exiting.
|
|
624
657
|
|
|
625
658
|
def stop(sync=false)
|
|
@@ -643,13 +676,33 @@ module Puma
|
|
|
643
676
|
|
|
644
677
|
# List of methods invoked by #stats.
|
|
645
678
|
# @version 5.0.0
|
|
646
|
-
STAT_METHODS = [
|
|
679
|
+
STAT_METHODS = [
|
|
680
|
+
:backlog,
|
|
681
|
+
:running,
|
|
682
|
+
:pool_capacity,
|
|
683
|
+
:busy_threads,
|
|
684
|
+
:backlog_max,
|
|
685
|
+
:max_threads,
|
|
686
|
+
:requests_count,
|
|
687
|
+
:reactor_max,
|
|
688
|
+
].freeze
|
|
647
689
|
|
|
648
690
|
# Returns a hash of stats about the running server for reporting purposes.
|
|
649
691
|
# @version 5.0.0
|
|
650
692
|
# @!attribute [r] stats
|
|
693
|
+
# @return [Hash] hash containing stat info from `Server` and `ThreadPool`
|
|
651
694
|
def stats
|
|
652
|
-
|
|
695
|
+
stats = @thread_pool&.stats || {}
|
|
696
|
+
stats[:max_threads] = @max_threads
|
|
697
|
+
stats[:requests_count] = @requests_count
|
|
698
|
+
stats[:reactor_max] = @reactor.reactor_max if @reactor
|
|
699
|
+
reset_max
|
|
700
|
+
stats
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def reset_max
|
|
704
|
+
@reactor.reactor_max = 0 if @reactor
|
|
705
|
+
@thread_pool&.reset_max
|
|
653
706
|
end
|
|
654
707
|
|
|
655
708
|
# below are 'delegations' to binder
|
|
@@ -669,6 +722,49 @@ module Puma
|
|
|
669
722
|
@binder.add_unix_listener path, umask, mode, backlog
|
|
670
723
|
end
|
|
671
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
|
+
|
|
672
768
|
# @!attribute [r] connected_ports
|
|
673
769
|
def connected_ports
|
|
674
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
|
@@ -17,7 +17,7 @@ module Puma
|
|
|
17
17
|
def stats
|
|
18
18
|
{
|
|
19
19
|
started_at: utc_iso8601(@started_at)
|
|
20
|
-
}.merge(@server
|
|
20
|
+
}.merge(@server&.stats || {}).merge(super)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def restart
|
|
@@ -49,13 +49,16 @@ module Puma
|
|
|
49
49
|
|
|
50
50
|
start_control
|
|
51
51
|
|
|
52
|
-
@server =
|
|
53
|
-
server_thread = server.run
|
|
52
|
+
@server = start_server
|
|
53
|
+
server_thread = @server.run
|
|
54
54
|
|
|
55
55
|
log "Use Ctrl-C to stop"
|
|
56
|
+
|
|
57
|
+
warn_ruby_mn_threads
|
|
58
|
+
|
|
56
59
|
redirect_io
|
|
57
60
|
|
|
58
|
-
@events.
|
|
61
|
+
@events.fire_after_booted!
|
|
59
62
|
|
|
60
63
|
debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
|
|
61
64
|
|
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
|
|