puma 6.6.0 → 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 +309 -5
- data/README.md +41 -42
- 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 +5 -5
- 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 +11 -16
- data/docs/plugins.md +2 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +4 -3
- data/docs/systemd.md +4 -4
- data/ext/puma_http11/extconf.rb +2 -17
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +18 -8
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +174 -102
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/puma_http11.c +122 -118
- data/lib/puma/app/status.rb +10 -2
- data/lib/puma/binder.rb +12 -10
- data/lib/puma/cli.rb +4 -6
- data/lib/puma/client.rb +205 -131
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +17 -17
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +44 -30
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +173 -58
- data/lib/puma/const.rb +11 -11
- data/lib/puma/control_cli.rb +7 -3
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +225 -108
- data/lib/puma/error_logger.rb +3 -1
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/launcher/bundle_pruner.rb +3 -5
- data/lib/puma/launcher.rb +57 -53
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/minissl.rb +0 -1
- 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} +56 -212
- data/lib/puma/runner.rb +9 -18
- data/lib/puma/server.rb +182 -97
- 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 +170 -99
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/Dockerfile +15 -5
- metadata +19 -7
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/server.rb
CHANGED
|
@@ -11,16 +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
|
-
# This method was private on Ruby 2.4 but became public on Ruby 2.5+:
|
|
22
|
-
Thread.send(:attr_accessor, :puma_server)
|
|
23
|
-
|
|
24
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
|
25
23
|
#
|
|
26
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
|
@@ -32,8 +30,16 @@ module Puma
|
|
|
32
30
|
#
|
|
33
31
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
34
32
|
class Server
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
37
43
|
|
|
38
44
|
attr_reader :options
|
|
39
45
|
attr_reader :thread
|
|
@@ -50,7 +56,6 @@ module Puma
|
|
|
50
56
|
attr_accessor :app
|
|
51
57
|
attr_accessor :binder
|
|
52
58
|
|
|
53
|
-
|
|
54
59
|
# Create a server for the rack app +app+.
|
|
55
60
|
#
|
|
56
61
|
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
|
@@ -77,6 +82,9 @@ module Puma
|
|
|
77
82
|
|
|
78
83
|
@thread = nil
|
|
79
84
|
@thread_pool = nil
|
|
85
|
+
@reactor = nil
|
|
86
|
+
|
|
87
|
+
@env_set_http_version = nil
|
|
80
88
|
|
|
81
89
|
@options = if options.is_a?(UserFileDefaultOptions)
|
|
82
90
|
options
|
|
@@ -94,10 +102,19 @@ module Puma
|
|
|
94
102
|
@min_threads = @options[:min_threads]
|
|
95
103
|
@max_threads = @options[:max_threads]
|
|
96
104
|
@queue_requests = @options[:queue_requests]
|
|
97
|
-
@
|
|
105
|
+
@max_keep_alive = @options[:max_keep_alive]
|
|
98
106
|
@enable_keep_alives = @options[:enable_keep_alives]
|
|
107
|
+
@enable_keep_alives &&= @queue_requests
|
|
99
108
|
@io_selector_backend = @options[:io_selector_backend]
|
|
100
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
|
|
101
118
|
|
|
102
119
|
# make this a hash, since we prefer `key?` over `include?`
|
|
103
120
|
@supported_http_methods =
|
|
@@ -114,7 +131,7 @@ module Puma
|
|
|
114
131
|
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
|
115
132
|
@leak_stack_on_error = @options[:environment] ? temp : true
|
|
116
133
|
|
|
117
|
-
@binder = Binder.new(log_writer)
|
|
134
|
+
@binder = Binder.new(log_writer, @options)
|
|
118
135
|
|
|
119
136
|
ENV['RACK_ENV'] ||= "development"
|
|
120
137
|
|
|
@@ -165,7 +182,6 @@ module Puma
|
|
|
165
182
|
begin
|
|
166
183
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
|
167
184
|
rescue IOError, SystemCallError
|
|
168
|
-
Puma::Util.purge_interrupt_queue
|
|
169
185
|
end
|
|
170
186
|
end
|
|
171
187
|
|
|
@@ -174,7 +190,6 @@ module Puma
|
|
|
174
190
|
begin
|
|
175
191
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
|
176
192
|
rescue IOError, SystemCallError
|
|
177
|
-
Puma::Util.purge_interrupt_queue
|
|
178
193
|
end
|
|
179
194
|
end
|
|
180
195
|
else
|
|
@@ -195,7 +210,6 @@ module Puma
|
|
|
195
210
|
begin
|
|
196
211
|
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
|
197
212
|
rescue IOError, SystemCallError
|
|
198
|
-
Puma::Util.purge_interrupt_queue
|
|
199
213
|
@precheck_closing = false
|
|
200
214
|
false
|
|
201
215
|
else
|
|
@@ -220,7 +234,6 @@ module Puma
|
|
|
220
234
|
@thread_pool&.spawned
|
|
221
235
|
end
|
|
222
236
|
|
|
223
|
-
|
|
224
237
|
# This number represents the number of requests that
|
|
225
238
|
# the server is capable of taking right now.
|
|
226
239
|
#
|
|
@@ -233,11 +246,6 @@ module Puma
|
|
|
233
246
|
@thread_pool&.pool_capacity
|
|
234
247
|
end
|
|
235
248
|
|
|
236
|
-
# @!attribute [r] busy_threads
|
|
237
|
-
def busy_threads
|
|
238
|
-
@thread_pool&.busy_threads
|
|
239
|
-
end
|
|
240
|
-
|
|
241
249
|
# Runs the server.
|
|
242
250
|
#
|
|
243
251
|
# If +background+ is true (the default) then a thread is spun
|
|
@@ -251,10 +259,16 @@ module Puma
|
|
|
251
259
|
|
|
252
260
|
@status = :run
|
|
253
261
|
|
|
254
|
-
@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
|
|
255
265
|
|
|
256
266
|
if @queue_requests
|
|
257
|
-
@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
|
+
}
|
|
258
272
|
@reactor.run
|
|
259
273
|
end
|
|
260
274
|
|
|
@@ -279,17 +293,20 @@ module Puma
|
|
|
279
293
|
# This method is called from the Reactor thread when a queued Client receives data,
|
|
280
294
|
# times out, or when the Reactor is shutting down.
|
|
281
295
|
#
|
|
296
|
+
# While the code lives in the Server, the logic is executed on the reactor thread, independently
|
|
297
|
+
# from the server.
|
|
298
|
+
#
|
|
282
299
|
# It is responsible for ensuring that a request has been completely received
|
|
283
300
|
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
|
284
301
|
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
285
302
|
# such as nginx) then the application would be subject to a slow client attack.
|
|
286
303
|
#
|
|
287
|
-
# 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).
|
|
288
305
|
#
|
|
289
306
|
# The method checks to see if it has the full header and body with
|
|
290
307
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
291
308
|
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
|
292
|
-
# so that a "
|
|
309
|
+
# so that a "processor thread" can pick up the request and begin to execute application logic.
|
|
293
310
|
# The Client is then removed from the reactor (return `true`).
|
|
294
311
|
#
|
|
295
312
|
# If a client object times out, a 408 response is written, its connection is closed,
|
|
@@ -313,11 +330,14 @@ module Puma
|
|
|
313
330
|
end
|
|
314
331
|
rescue StandardError => e
|
|
315
332
|
client_error(e, client)
|
|
316
|
-
client
|
|
333
|
+
close_client_safely(client)
|
|
317
334
|
true
|
|
318
335
|
end
|
|
319
336
|
|
|
320
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
|
+
|
|
321
341
|
begin
|
|
322
342
|
check = @check
|
|
323
343
|
sockets = [check] + @binder.ios
|
|
@@ -364,8 +384,18 @@ module Puma
|
|
|
364
384
|
if sock == check
|
|
365
385
|
break if handle_check
|
|
366
386
|
else
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
369
399
|
|
|
370
400
|
io = begin
|
|
371
401
|
sock.accept_nonblock
|
|
@@ -373,11 +403,10 @@ module Puma
|
|
|
373
403
|
next
|
|
374
404
|
end
|
|
375
405
|
drain += 1 if shutting_down?
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
406
|
+
|
|
407
|
+
client = new_client(io, sock)
|
|
408
|
+
client.send(addr_send_name, addr_value) if addr_value
|
|
409
|
+
pool << client
|
|
381
410
|
end
|
|
382
411
|
end
|
|
383
412
|
rescue IOError, Errno::EBADF
|
|
@@ -388,7 +417,7 @@ module Puma
|
|
|
388
417
|
end
|
|
389
418
|
end
|
|
390
419
|
|
|
391
|
-
@log_writer.debug "Drained #{drain} additional connections." if drain
|
|
420
|
+
@log_writer.debug { "Drained #{drain} additional connections." } if drain
|
|
392
421
|
@events.fire :state, @status
|
|
393
422
|
|
|
394
423
|
if queue_requests
|
|
@@ -414,6 +443,16 @@ module Puma
|
|
|
414
443
|
@events.fire :state, :done
|
|
415
444
|
end
|
|
416
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
|
+
|
|
417
456
|
# :nodoc:
|
|
418
457
|
def handle_check
|
|
419
458
|
cmd = @check.read(1)
|
|
@@ -436,25 +475,20 @@ module Puma
|
|
|
436
475
|
# Given a connection on +client+, handle the incoming requests,
|
|
437
476
|
# or queue the connection in the Reactor if no request is available.
|
|
438
477
|
#
|
|
439
|
-
# This method is called from a ThreadPool
|
|
478
|
+
# This method is called from a ThreadPool processor thread.
|
|
440
479
|
#
|
|
441
480
|
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
442
481
|
# indicates that it supports keep alive, wait for another request before
|
|
443
482
|
# returning.
|
|
444
483
|
#
|
|
445
484
|
# Return true if one or more requests were processed.
|
|
446
|
-
def process_client(client)
|
|
447
|
-
# Advertise this server into the thread
|
|
448
|
-
Thread.current.puma_server = self
|
|
449
|
-
|
|
450
|
-
clean_thread_locals = options[:clean_thread_locals]
|
|
485
|
+
def process_client(processor, client)
|
|
451
486
|
close_socket = true
|
|
452
487
|
|
|
453
488
|
requests = 0
|
|
454
489
|
|
|
455
490
|
begin
|
|
456
|
-
if @queue_requests &&
|
|
457
|
-
!client.eagerly_finish
|
|
491
|
+
if @queue_requests && !client.eagerly_finish
|
|
458
492
|
|
|
459
493
|
client.set_timeout(@first_data_timeout)
|
|
460
494
|
if @reactor.add client
|
|
@@ -467,38 +501,40 @@ module Puma
|
|
|
467
501
|
client.finish(@first_data_timeout)
|
|
468
502
|
end
|
|
469
503
|
|
|
470
|
-
|
|
504
|
+
can_loop = true
|
|
505
|
+
while can_loop
|
|
506
|
+
can_loop = false
|
|
471
507
|
@requests_count += 1
|
|
472
|
-
case handle_request(client, requests + 1)
|
|
473
|
-
when
|
|
474
|
-
break
|
|
508
|
+
case handle_request(processor, client, requests + 1)
|
|
509
|
+
when :close
|
|
475
510
|
when :async
|
|
476
511
|
close_socket = false
|
|
477
|
-
|
|
478
|
-
when true
|
|
479
|
-
ThreadPool.clean_thread_locals if clean_thread_locals
|
|
480
|
-
|
|
512
|
+
when :keep_alive
|
|
481
513
|
requests += 1
|
|
482
514
|
|
|
483
|
-
|
|
484
|
-
# socket for a short time before returning to the reactor.
|
|
485
|
-
fast_check = @status == :run
|
|
486
|
-
|
|
487
|
-
# Always pass the client back to the reactor after a reasonable
|
|
488
|
-
# number of inline requests if there are other requests pending.
|
|
489
|
-
fast_check = false if requests >= @max_fast_inline &&
|
|
490
|
-
@thread_pool.backlog > 0
|
|
515
|
+
client.reset
|
|
491
516
|
|
|
492
|
-
|
|
493
|
-
|
|
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 }
|
|
494
523
|
end
|
|
495
524
|
|
|
496
|
-
|
|
497
|
-
|
|
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
|
|
498
535
|
client.set_timeout @persistent_timeout
|
|
499
536
|
if @reactor.add client
|
|
500
537
|
close_socket = false
|
|
501
|
-
break
|
|
502
538
|
end
|
|
503
539
|
end
|
|
504
540
|
end
|
|
@@ -511,17 +547,21 @@ module Puma
|
|
|
511
547
|
ensure
|
|
512
548
|
client.io_buffer.reset
|
|
513
549
|
|
|
514
|
-
|
|
515
|
-
client.close if close_socket
|
|
516
|
-
rescue IOError, SystemCallError
|
|
517
|
-
Puma::Util.purge_interrupt_queue
|
|
518
|
-
# Already closed
|
|
519
|
-
rescue StandardError => e
|
|
520
|
-
@log_writer.unknown_error e, nil, "Client"
|
|
521
|
-
end
|
|
550
|
+
close_client_safely(client) if close_socket
|
|
522
551
|
end
|
|
523
552
|
end
|
|
524
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
|
+
|
|
525
565
|
# Triggers a client timeout if the thread-pool shuts down
|
|
526
566
|
# during execution of the provided block.
|
|
527
567
|
def with_force_shutdown(client, &block)
|
|
@@ -542,7 +582,7 @@ module Puma
|
|
|
542
582
|
lowlevel_error(e, client.env)
|
|
543
583
|
@log_writer.ssl_error e, client.io
|
|
544
584
|
when HttpParserError
|
|
545
|
-
response_to_error(client, requests, e, 400)
|
|
585
|
+
response_to_error(client, requests, e, client.error_status_code || 400)
|
|
546
586
|
@log_writer.parse_error e, client
|
|
547
587
|
when HttpParserError501
|
|
548
588
|
response_to_error(client, requests, e, 501)
|
|
@@ -575,7 +615,15 @@ module Puma
|
|
|
575
615
|
end
|
|
576
616
|
|
|
577
617
|
def response_to_error(client, requests, err, status_code)
|
|
578
|
-
|
|
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
|
|
579
627
|
prepare_response(status, headers, res_body, requests, client)
|
|
580
628
|
end
|
|
581
629
|
private :response_to_error
|
|
@@ -583,50 +631,28 @@ module Puma
|
|
|
583
631
|
# Wait for all outstanding requests to finish.
|
|
584
632
|
#
|
|
585
633
|
def graceful_shutdown
|
|
586
|
-
if options[:shutdown_debug]
|
|
587
|
-
threads = Thread.list
|
|
588
|
-
total = threads.size
|
|
589
|
-
|
|
590
|
-
pid = Process.pid
|
|
591
|
-
|
|
592
|
-
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
|
593
|
-
|
|
594
|
-
threads.each_with_index do |t,i|
|
|
595
|
-
$stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
|
|
596
|
-
$stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
|
|
597
|
-
end
|
|
598
|
-
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
|
599
|
-
end
|
|
600
|
-
|
|
601
634
|
if @status != :restart
|
|
602
635
|
@binder.close
|
|
603
636
|
end
|
|
604
637
|
|
|
605
|
-
|
|
606
|
-
if timeout = options[:force_shutdown_after]
|
|
607
|
-
@thread_pool.shutdown timeout.to_f
|
|
608
|
-
else
|
|
609
|
-
@thread_pool.shutdown
|
|
610
|
-
end
|
|
611
|
-
end
|
|
638
|
+
@thread_pool.shutdown(options[:force_shutdown_after])
|
|
612
639
|
end
|
|
613
640
|
|
|
614
641
|
def notify_safely(message)
|
|
615
642
|
@notify << message
|
|
616
643
|
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
|
617
644
|
# The server, in another thread, is shutting down
|
|
618
|
-
Puma::Util.purge_interrupt_queue
|
|
619
645
|
rescue RuntimeError => e
|
|
620
646
|
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
|
621
647
|
if e.message.include?('IOError')
|
|
622
|
-
|
|
648
|
+
# ignore
|
|
623
649
|
else
|
|
624
650
|
raise e
|
|
625
651
|
end
|
|
626
652
|
end
|
|
627
653
|
private :notify_safely
|
|
628
654
|
|
|
629
|
-
# Stops the acceptor thread and then causes the
|
|
655
|
+
# Stops the acceptor thread and then causes the processor threads to finish
|
|
630
656
|
# off the request queue before finally exiting.
|
|
631
657
|
|
|
632
658
|
def stop(sync=false)
|
|
@@ -650,7 +676,16 @@ module Puma
|
|
|
650
676
|
|
|
651
677
|
# List of methods invoked by #stats.
|
|
652
678
|
# @version 5.0.0
|
|
653
|
-
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
|
|
654
689
|
|
|
655
690
|
# Returns a hash of stats about the running server for reporting purposes.
|
|
656
691
|
# @version 5.0.0
|
|
@@ -660,9 +695,16 @@ module Puma
|
|
|
660
695
|
stats = @thread_pool&.stats || {}
|
|
661
696
|
stats[:max_threads] = @max_threads
|
|
662
697
|
stats[:requests_count] = @requests_count
|
|
698
|
+
stats[:reactor_max] = @reactor.reactor_max if @reactor
|
|
699
|
+
reset_max
|
|
663
700
|
stats
|
|
664
701
|
end
|
|
665
702
|
|
|
703
|
+
def reset_max
|
|
704
|
+
@reactor.reactor_max = 0 if @reactor
|
|
705
|
+
@thread_pool&.reset_max
|
|
706
|
+
end
|
|
707
|
+
|
|
666
708
|
# below are 'delegations' to binder
|
|
667
709
|
# remove in Puma 7?
|
|
668
710
|
|
|
@@ -680,6 +722,49 @@ module Puma
|
|
|
680
722
|
@binder.add_unix_listener path, umask, mode, backlog
|
|
681
723
|
end
|
|
682
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
|
+
|
|
683
768
|
# @!attribute [r] connected_ports
|
|
684
769
|
def connected_ports
|
|
685
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
|
|