puma 6.6.1-java → 7.1.0-java
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 +153 -4
- data/README.md +17 -25
- data/docs/fork_worker.md +5 -5
- data/docs/kubernetes.md +8 -6
- data/docs/restart.md +2 -2
- data/docs/signals.md +9 -9
- data/docs/stats.md +3 -2
- data/ext/puma_http11/extconf.rb +2 -17
- data/ext/puma_http11/mini_ssl.c +18 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +9 -1
- data/ext/puma_http11/puma_http11.c +23 -11
- data/lib/puma/binder.rb +10 -8
- data/lib/puma/cli.rb +3 -5
- data/lib/puma/client.rb +52 -56
- data/lib/puma/cluster/worker.rb +9 -10
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +21 -20
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +89 -43
- data/lib/puma/const.rb +9 -10
- data/lib/puma/control_cli.rb +6 -2
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +133 -85
- 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 +1 -1
- data/lib/puma/launcher.rb +52 -48
- data/lib/puma/minissl.rb +0 -1
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -4
- data/lib/puma/request.rb +33 -24
- data/lib/puma/runner.rb +8 -17
- data/lib/puma/server.rb +111 -61
- data/lib/puma/single.rb +4 -1
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +47 -82
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +2 -2
- metadata +6 -4
data/lib/puma/server.rb
CHANGED
|
@@ -12,15 +12,13 @@ require_relative 'client'
|
|
|
12
12
|
require_relative 'binder'
|
|
13
13
|
require_relative 'util'
|
|
14
14
|
require_relative 'request'
|
|
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,6 +30,14 @@ module Puma
|
|
|
32
30
|
#
|
|
33
31
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
34
32
|
class Server
|
|
33
|
+
module FiberPerRequest
|
|
34
|
+
def handle_request(client, requests)
|
|
35
|
+
Fiber.new do
|
|
36
|
+
super
|
|
37
|
+
end.resume
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
35
41
|
include Puma::Const
|
|
36
42
|
include Request
|
|
37
43
|
|
|
@@ -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,14 @@ module Puma
|
|
|
251
259
|
|
|
252
260
|
@status = :run
|
|
253
261
|
|
|
254
|
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
|
|
255
263
|
|
|
256
264
|
if @queue_requests
|
|
257
|
-
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
265
|
+
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
266
|
+
# Inversion of control, the reactor is calling a method on the server when it
|
|
267
|
+
# is done buffering a request or receives a new request from a keepalive connection.
|
|
268
|
+
self.reactor_wakeup(c)
|
|
269
|
+
}
|
|
258
270
|
@reactor.run
|
|
259
271
|
end
|
|
260
272
|
|
|
@@ -279,6 +291,9 @@ module Puma
|
|
|
279
291
|
# This method is called from the Reactor thread when a queued Client receives data,
|
|
280
292
|
# times out, or when the Reactor is shutting down.
|
|
281
293
|
#
|
|
294
|
+
# While the code lives in the Server, the logic is executed on the reactor thread, independently
|
|
295
|
+
# from the server.
|
|
296
|
+
#
|
|
282
297
|
# It is responsible for ensuring that a request has been completely received
|
|
283
298
|
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
|
284
299
|
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
@@ -313,11 +328,14 @@ module Puma
|
|
|
313
328
|
end
|
|
314
329
|
rescue StandardError => e
|
|
315
330
|
client_error(e, client)
|
|
316
|
-
client
|
|
331
|
+
close_client_safely(client)
|
|
317
332
|
true
|
|
318
333
|
end
|
|
319
334
|
|
|
320
335
|
def handle_servers
|
|
336
|
+
@env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
|
|
337
|
+
Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
|
|
338
|
+
|
|
321
339
|
begin
|
|
322
340
|
check = @check
|
|
323
341
|
sockets = [check] + @binder.ios
|
|
@@ -364,8 +382,18 @@ module Puma
|
|
|
364
382
|
if sock == check
|
|
365
383
|
break if handle_check
|
|
366
384
|
else
|
|
367
|
-
|
|
368
|
-
|
|
385
|
+
# if ThreadPool out_of_band code is running, we don't want to add
|
|
386
|
+
# clients until the code is finished.
|
|
387
|
+
pool.wait_while_out_of_band_running
|
|
388
|
+
|
|
389
|
+
# A well rested herd (cluster) runs faster
|
|
390
|
+
if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
|
|
391
|
+
delay = @cluster_accept_loop_delay.calculate(
|
|
392
|
+
max_threads: @max_threads,
|
|
393
|
+
busy_threads_plus_todo: busy_threads_plus_todo
|
|
394
|
+
)
|
|
395
|
+
sleep(delay)
|
|
396
|
+
end
|
|
369
397
|
|
|
370
398
|
io = begin
|
|
371
399
|
sock.accept_nonblock
|
|
@@ -373,11 +401,9 @@ module Puma
|
|
|
373
401
|
next
|
|
374
402
|
end
|
|
375
403
|
drain += 1 if shutting_down?
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
c.send(addr_send_name, addr_value) if addr_value
|
|
380
|
-
}
|
|
404
|
+
client = new_client(io, sock)
|
|
405
|
+
client.send(addr_send_name, addr_value) if addr_value
|
|
406
|
+
pool << client
|
|
381
407
|
end
|
|
382
408
|
end
|
|
383
409
|
rescue IOError, Errno::EBADF
|
|
@@ -414,6 +440,14 @@ module Puma
|
|
|
414
440
|
@events.fire :state, :done
|
|
415
441
|
end
|
|
416
442
|
|
|
443
|
+
# :nodoc:
|
|
444
|
+
def new_client(io, sock)
|
|
445
|
+
client = Client.new(io, @binder.env(sock))
|
|
446
|
+
client.listener = sock
|
|
447
|
+
client.http_content_length_limit = @http_content_length_limit
|
|
448
|
+
client
|
|
449
|
+
end
|
|
450
|
+
|
|
417
451
|
# :nodoc:
|
|
418
452
|
def handle_check
|
|
419
453
|
cmd = @check.read(1)
|
|
@@ -444,17 +478,12 @@ module Puma
|
|
|
444
478
|
#
|
|
445
479
|
# Return true if one or more requests were processed.
|
|
446
480
|
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]
|
|
451
481
|
close_socket = true
|
|
452
482
|
|
|
453
483
|
requests = 0
|
|
454
484
|
|
|
455
485
|
begin
|
|
456
|
-
if @queue_requests &&
|
|
457
|
-
!client.eagerly_finish
|
|
486
|
+
if @queue_requests && !client.eagerly_finish
|
|
458
487
|
|
|
459
488
|
client.set_timeout(@first_data_timeout)
|
|
460
489
|
if @reactor.add client
|
|
@@ -467,38 +496,40 @@ module Puma
|
|
|
467
496
|
client.finish(@first_data_timeout)
|
|
468
497
|
end
|
|
469
498
|
|
|
470
|
-
|
|
499
|
+
can_loop = true
|
|
500
|
+
while can_loop
|
|
501
|
+
can_loop = false
|
|
471
502
|
@requests_count += 1
|
|
472
503
|
case handle_request(client, requests + 1)
|
|
473
504
|
when false
|
|
474
|
-
break
|
|
475
505
|
when :async
|
|
476
506
|
close_socket = false
|
|
477
|
-
break
|
|
478
507
|
when true
|
|
479
|
-
ThreadPool.clean_thread_locals if clean_thread_locals
|
|
480
|
-
|
|
481
508
|
requests += 1
|
|
482
509
|
|
|
483
|
-
|
|
484
|
-
# socket for a short time before returning to the reactor.
|
|
485
|
-
fast_check = @status == :run
|
|
510
|
+
client.reset
|
|
486
511
|
|
|
487
|
-
#
|
|
488
|
-
#
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
client.reset(fast_check)
|
|
512
|
+
# This indicates data exists in the client read buffer and there may be
|
|
513
|
+
# additional requests on it, so process them
|
|
514
|
+
next_request_ready = if client.has_back_to_back_requests?
|
|
515
|
+
with_force_shutdown(client) { client.process_back_to_back_requests }
|
|
516
|
+
else
|
|
517
|
+
with_force_shutdown(client) { client.eagerly_finish }
|
|
494
518
|
end
|
|
495
519
|
|
|
496
|
-
|
|
497
|
-
|
|
520
|
+
if next_request_ready
|
|
521
|
+
# When Puma has spare threads, allow this one to be monopolized
|
|
522
|
+
# Perf optimization for https://github.com/puma/puma/issues/3788
|
|
523
|
+
if @thread_pool.waiting > 0
|
|
524
|
+
can_loop = true
|
|
525
|
+
else
|
|
526
|
+
@thread_pool << client
|
|
527
|
+
close_socket = false
|
|
528
|
+
end
|
|
529
|
+
elsif @queue_requests
|
|
498
530
|
client.set_timeout @persistent_timeout
|
|
499
531
|
if @reactor.add client
|
|
500
532
|
close_socket = false
|
|
501
|
-
break
|
|
502
533
|
end
|
|
503
534
|
end
|
|
504
535
|
end
|
|
@@ -511,17 +542,21 @@ module Puma
|
|
|
511
542
|
ensure
|
|
512
543
|
client.io_buffer.reset
|
|
513
544
|
|
|
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
|
|
545
|
+
close_client_safely(client) if close_socket
|
|
522
546
|
end
|
|
523
547
|
end
|
|
524
548
|
|
|
549
|
+
# :nodoc:
|
|
550
|
+
def close_client_safely(client)
|
|
551
|
+
client.close
|
|
552
|
+
rescue IOError, SystemCallError
|
|
553
|
+
# Already closed
|
|
554
|
+
rescue MiniSSL::SSLError => e
|
|
555
|
+
@log_writer.ssl_error e, client.io
|
|
556
|
+
rescue StandardError => e
|
|
557
|
+
@log_writer.unknown_error e, nil, "Client"
|
|
558
|
+
end
|
|
559
|
+
|
|
525
560
|
# Triggers a client timeout if the thread-pool shuts down
|
|
526
561
|
# during execution of the provided block.
|
|
527
562
|
def with_force_shutdown(client, &block)
|
|
@@ -615,11 +650,10 @@ module Puma
|
|
|
615
650
|
@notify << message
|
|
616
651
|
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
|
617
652
|
# The server, in another thread, is shutting down
|
|
618
|
-
Puma::Util.purge_interrupt_queue
|
|
619
653
|
rescue RuntimeError => e
|
|
620
654
|
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
|
621
655
|
if e.message.include?('IOError')
|
|
622
|
-
|
|
656
|
+
# ignore
|
|
623
657
|
else
|
|
624
658
|
raise e
|
|
625
659
|
end
|
|
@@ -650,7 +684,16 @@ module Puma
|
|
|
650
684
|
|
|
651
685
|
# List of methods invoked by #stats.
|
|
652
686
|
# @version 5.0.0
|
|
653
|
-
STAT_METHODS = [
|
|
687
|
+
STAT_METHODS = [
|
|
688
|
+
:backlog,
|
|
689
|
+
:running,
|
|
690
|
+
:pool_capacity,
|
|
691
|
+
:busy_threads,
|
|
692
|
+
:backlog_max,
|
|
693
|
+
:max_threads,
|
|
694
|
+
:requests_count,
|
|
695
|
+
:reactor_max,
|
|
696
|
+
].freeze
|
|
654
697
|
|
|
655
698
|
# Returns a hash of stats about the running server for reporting purposes.
|
|
656
699
|
# @version 5.0.0
|
|
@@ -660,9 +703,16 @@ module Puma
|
|
|
660
703
|
stats = @thread_pool&.stats || {}
|
|
661
704
|
stats[:max_threads] = @max_threads
|
|
662
705
|
stats[:requests_count] = @requests_count
|
|
706
|
+
stats[:reactor_max] = @reactor.reactor_max if @reactor
|
|
707
|
+
reset_max
|
|
663
708
|
stats
|
|
664
709
|
end
|
|
665
710
|
|
|
711
|
+
def reset_max
|
|
712
|
+
@reactor.reactor_max = 0 if @reactor
|
|
713
|
+
@thread_pool&.reset_max
|
|
714
|
+
end
|
|
715
|
+
|
|
666
716
|
# below are 'delegations' to binder
|
|
667
717
|
# remove in Puma 7?
|
|
668
718
|
|
data/lib/puma/single.rb
CHANGED
|
@@ -53,9 +53,12 @@ module Puma
|
|
|
53
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
|
|
data/lib/puma/thread_pool.rb
CHANGED
|
@@ -5,6 +5,10 @@ require 'thread'
|
|
|
5
5
|
require_relative 'io_buffer'
|
|
6
6
|
|
|
7
7
|
module Puma
|
|
8
|
+
|
|
9
|
+
# Add `Thread#puma_server` and `Thread#puma_server=`
|
|
10
|
+
Thread.attr_accessor(:puma_server)
|
|
11
|
+
|
|
8
12
|
# Internal Docs for A simple thread pool management object.
|
|
9
13
|
#
|
|
10
14
|
# Each Puma "worker" has a thread pool to process requests.
|
|
@@ -25,19 +29,23 @@ module Puma
|
|
|
25
29
|
# up its work before leaving the thread to die on the vine.
|
|
26
30
|
SHUTDOWN_GRACE_TIME = 5 # seconds
|
|
27
31
|
|
|
32
|
+
attr_reader :out_of_band_running
|
|
33
|
+
|
|
28
34
|
# Maintain a minimum of +min+ and maximum of +max+ threads
|
|
29
35
|
# in the pool.
|
|
30
36
|
#
|
|
31
37
|
# The block passed is the work that will be performed in each
|
|
32
38
|
# thread.
|
|
33
39
|
#
|
|
34
|
-
def initialize(name, options = {}, &block)
|
|
40
|
+
def initialize(name, options = {}, server: nil, &block)
|
|
41
|
+
@server = server
|
|
42
|
+
|
|
35
43
|
@not_empty = ConditionVariable.new
|
|
36
44
|
@not_full = ConditionVariable.new
|
|
37
45
|
@mutex = Mutex.new
|
|
46
|
+
@todo = Queue.new
|
|
38
47
|
|
|
39
|
-
@
|
|
40
|
-
|
|
48
|
+
@backlog_max = 0
|
|
41
49
|
@spawned = 0
|
|
42
50
|
@waiting = 0
|
|
43
51
|
|
|
@@ -50,7 +58,8 @@ module Puma
|
|
|
50
58
|
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
|
51
59
|
@block = block
|
|
52
60
|
@out_of_band = options[:out_of_band]
|
|
53
|
-
@
|
|
61
|
+
@out_of_band_running = false
|
|
62
|
+
@out_of_band_condvar = ConditionVariable.new
|
|
54
63
|
@before_thread_start = options[:before_thread_start]
|
|
55
64
|
@before_thread_exit = options[:before_thread_exit]
|
|
56
65
|
@reaping_time = options[:reaping_time]
|
|
@@ -79,30 +88,37 @@ module Puma
|
|
|
79
88
|
|
|
80
89
|
attr_reader :spawned, :trim_requested, :waiting
|
|
81
90
|
|
|
82
|
-
def self.clean_thread_locals
|
|
83
|
-
Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
|
|
84
|
-
Thread.current[key] = nil unless key == :__recursive_key__
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
91
|
# generate stats hash so as not to perform multiple locks
|
|
89
92
|
# @return [Hash] hash containing stat info from ThreadPool
|
|
90
93
|
def stats
|
|
91
94
|
with_mutex do
|
|
95
|
+
temp = @backlog_max
|
|
96
|
+
@backlog_max = 0
|
|
92
97
|
{ backlog: @todo.size,
|
|
93
98
|
running: @spawned,
|
|
94
99
|
pool_capacity: @waiting + (@max - @spawned),
|
|
95
|
-
busy_threads: @spawned - @waiting + @todo.size
|
|
100
|
+
busy_threads: @spawned - @waiting + @todo.size,
|
|
101
|
+
backlog_max: temp
|
|
96
102
|
}
|
|
97
103
|
end
|
|
98
104
|
end
|
|
99
105
|
|
|
106
|
+
def reset_max
|
|
107
|
+
with_mutex { @backlog_max = 0 }
|
|
108
|
+
end
|
|
109
|
+
|
|
100
110
|
# How many objects have yet to be processed by the pool?
|
|
101
111
|
#
|
|
102
112
|
def backlog
|
|
103
113
|
with_mutex { @todo.size }
|
|
104
114
|
end
|
|
105
115
|
|
|
116
|
+
# The maximum size of the backlog
|
|
117
|
+
#
|
|
118
|
+
def backlog_max
|
|
119
|
+
with_mutex { @backlog_max }
|
|
120
|
+
end
|
|
121
|
+
|
|
106
122
|
# @!attribute [r] pool_capacity
|
|
107
123
|
def pool_capacity
|
|
108
124
|
waiting + (@max - spawned)
|
|
@@ -124,6 +140,9 @@ module Puma
|
|
|
124
140
|
trigger_before_thread_start_hooks
|
|
125
141
|
th = Thread.new(@spawned) do |spawned|
|
|
126
142
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
|
143
|
+
# Advertise server into the thread
|
|
144
|
+
Thread.current.puma_server = @server
|
|
145
|
+
|
|
127
146
|
todo = @todo
|
|
128
147
|
block = @block
|
|
129
148
|
mutex = @mutex
|
|
@@ -159,10 +178,6 @@ module Puma
|
|
|
159
178
|
work = todo.shift
|
|
160
179
|
end
|
|
161
180
|
|
|
162
|
-
if @clean_thread_locals
|
|
163
|
-
ThreadPool.clean_thread_locals
|
|
164
|
-
end
|
|
165
|
-
|
|
166
181
|
begin
|
|
167
182
|
@out_of_band_pending = true if block.call(work)
|
|
168
183
|
rescue Exception => e
|
|
@@ -183,7 +198,7 @@ module Puma
|
|
|
183
198
|
|
|
184
199
|
@before_thread_start.each do |b|
|
|
185
200
|
begin
|
|
186
|
-
b.call
|
|
201
|
+
b[:block].call
|
|
187
202
|
rescue Exception => e
|
|
188
203
|
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
|
189
204
|
end
|
|
@@ -198,7 +213,7 @@ module Puma
|
|
|
198
213
|
|
|
199
214
|
@before_thread_exit.each do |b|
|
|
200
215
|
begin
|
|
201
|
-
b.call
|
|
216
|
+
b[:block].call
|
|
202
217
|
rescue Exception => e
|
|
203
218
|
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
|
204
219
|
end
|
|
@@ -214,16 +229,27 @@ module Puma
|
|
|
214
229
|
|
|
215
230
|
# we execute on idle hook when all threads are free
|
|
216
231
|
return false unless @spawned == @waiting
|
|
217
|
-
|
|
218
|
-
@out_of_band.each
|
|
232
|
+
@out_of_band_running = true
|
|
233
|
+
@out_of_band.each { |b| b[:block].call }
|
|
219
234
|
true
|
|
220
235
|
rescue Exception => e
|
|
221
236
|
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
|
222
237
|
true
|
|
238
|
+
ensure
|
|
239
|
+
@out_of_band_running = false
|
|
240
|
+
@out_of_band_condvar.broadcast
|
|
223
241
|
end
|
|
224
242
|
|
|
225
243
|
private :trigger_out_of_band_hook
|
|
226
244
|
|
|
245
|
+
def wait_while_out_of_band_running
|
|
246
|
+
return unless @out_of_band_running
|
|
247
|
+
|
|
248
|
+
with_mutex do
|
|
249
|
+
@out_of_band_condvar.wait(@mutex) while @out_of_band_running
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
227
253
|
# @version 5.0.0
|
|
228
254
|
def with_mutex(&block)
|
|
229
255
|
@mutex.owned? ?
|
|
@@ -239,6 +265,8 @@ module Puma
|
|
|
239
265
|
end
|
|
240
266
|
|
|
241
267
|
@todo << work
|
|
268
|
+
t = @todo.size
|
|
269
|
+
@backlog_max = t if t > @backlog_max
|
|
242
270
|
|
|
243
271
|
if @waiting < @todo.size and @spawned < @max
|
|
244
272
|
spawn_thread
|
|
@@ -248,69 +276,6 @@ module Puma
|
|
|
248
276
|
end
|
|
249
277
|
end
|
|
250
278
|
|
|
251
|
-
# This method is used by `Puma::Server` to let the server know when
|
|
252
|
-
# the thread pool can pull more requests from the socket and
|
|
253
|
-
# pass to the reactor.
|
|
254
|
-
#
|
|
255
|
-
# The general idea is that the thread pool can only work on a fixed
|
|
256
|
-
# number of requests at the same time. If it is already processing that
|
|
257
|
-
# number of requests then it is at capacity. If another Puma process has
|
|
258
|
-
# spare capacity, then the request can be left on the socket so the other
|
|
259
|
-
# worker can pick it up and process it.
|
|
260
|
-
#
|
|
261
|
-
# For example: if there are 5 threads, but only 4 working on
|
|
262
|
-
# requests, this method will not wait and the `Puma::Server`
|
|
263
|
-
# can pull a request right away.
|
|
264
|
-
#
|
|
265
|
-
# If there are 5 threads and all 5 of them are busy, then it will
|
|
266
|
-
# pause here, and wait until the `not_full` condition variable is
|
|
267
|
-
# signaled, usually this indicates that a request has been processed.
|
|
268
|
-
#
|
|
269
|
-
# It's important to note that even though the server might accept another
|
|
270
|
-
# request, it might not be added to the `@todo` array right away.
|
|
271
|
-
# For example if a slow client has only sent a header, but not a body
|
|
272
|
-
# then the `@todo` array would stay the same size as the reactor works
|
|
273
|
-
# to try to buffer the request. In that scenario the next call to this
|
|
274
|
-
# method would not block and another request would be added into the reactor
|
|
275
|
-
# by the server. This would continue until a fully buffered request
|
|
276
|
-
# makes it through the reactor and can then be processed by the thread pool.
|
|
277
|
-
def wait_until_not_full
|
|
278
|
-
with_mutex do
|
|
279
|
-
while true
|
|
280
|
-
return if @shutdown
|
|
281
|
-
|
|
282
|
-
# If we can still spin up new threads and there
|
|
283
|
-
# is work queued that cannot be handled by waiting
|
|
284
|
-
# threads, then accept more work until we would
|
|
285
|
-
# spin up the max number of threads.
|
|
286
|
-
return if busy_threads < @max
|
|
287
|
-
|
|
288
|
-
@not_full.wait @mutex
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
# @version 5.0.0
|
|
294
|
-
def wait_for_less_busy_worker(delay_s)
|
|
295
|
-
return unless delay_s && delay_s > 0
|
|
296
|
-
|
|
297
|
-
# Ruby MRI does GVL, this can result
|
|
298
|
-
# in processing contention when multiple threads
|
|
299
|
-
# (requests) are running concurrently
|
|
300
|
-
return unless Puma.mri?
|
|
301
|
-
|
|
302
|
-
with_mutex do
|
|
303
|
-
return if @shutdown
|
|
304
|
-
|
|
305
|
-
# do not delay, if we are not busy
|
|
306
|
-
return unless busy_threads > 0
|
|
307
|
-
|
|
308
|
-
# this will be signaled once a request finishes,
|
|
309
|
-
# which can happen earlier than delay
|
|
310
|
-
@not_full.wait @mutex, delay_s
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
|
|
314
279
|
# If there are any free threads in the pool, tell one to go ahead
|
|
315
280
|
# and exit. If +force+ is true, then a trim request is requested
|
|
316
281
|
# even if all threads are being utilized.
|
data/lib/puma/util.rb
CHANGED
|
@@ -10,13 +10,6 @@ module Puma
|
|
|
10
10
|
IO.pipe
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
# An instance method on Thread has been provided to address https://bugs.ruby-lang.org/issues/13632,
|
|
14
|
-
# which currently affects some older versions of Ruby: 2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1
|
|
15
|
-
# Additional context: https://github.com/puma/puma/pull/1345
|
|
16
|
-
def purge_interrupt_queue
|
|
17
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
18
|
-
end
|
|
19
|
-
|
|
20
13
|
# Escapes and unescapes a URI escaped string with
|
|
21
14
|
# +encoding+. +encoding+ will be the target encoding of the string
|
|
22
15
|
# returned, and it defaults to UTF-8
|
data/lib/puma.rb
CHANGED
|
@@ -75,4 +75,14 @@ module Puma
|
|
|
75
75
|
def self.set_thread_name(name)
|
|
76
76
|
Thread.current.name = "puma #{name}"
|
|
77
77
|
end
|
|
78
|
+
|
|
79
|
+
# Shows deprecated warning for renamed methods.
|
|
80
|
+
# @example
|
|
81
|
+
# Puma.deprecate_method_change :on_booted, __callee__, __method__
|
|
82
|
+
#
|
|
83
|
+
def self.deprecate_method_change(method_old, method_caller, method_new)
|
|
84
|
+
if method_old == method_caller
|
|
85
|
+
warn "Use '#{method_new}', '#{method_caller}' is deprecated and will be removed in v8"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
78
88
|
end
|
data/lib/rack/handler/puma.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Puma
|
|
|
32
32
|
|
|
33
33
|
@events = options[:events] || ::Puma::Events.new
|
|
34
34
|
|
|
35
|
-
conf = ::Puma::Configuration.new(options, default_options.merge({events: @events})) do |user_config, file_config, default_config|
|
|
35
|
+
conf = ::Puma::Configuration.new(options, default_options.merge({ events: @events })) do |user_config, file_config, default_config|
|
|
36
36
|
if options.delete(:Verbose)
|
|
37
37
|
begin
|
|
38
38
|
require 'rack/commonlogger' # Rack 1.x
|
|
@@ -72,7 +72,7 @@ module Puma
|
|
|
72
72
|
|
|
73
73
|
log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
|
|
74
74
|
|
|
75
|
-
launcher = ::Puma::Launcher.new(conf, :
|
|
75
|
+
launcher = ::Puma::Launcher.new(conf, log_writer: log_writer, events: @events)
|
|
76
76
|
|
|
77
77
|
yield launcher if block_given?
|
|
78
78
|
begin
|