puma 5.6.7 → 6.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +269 -13
- data/README.md +78 -29
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/systemd.md +1 -2
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +11 -8
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +122 -18
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -53
- data/lib/puma/cli.rb +16 -18
- data/lib/puma/client.rb +59 -19
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +33 -30
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +78 -58
- data/lib/puma/const.rb +129 -92
- data/lib/puma/control_cli.rb +15 -11
- data/lib/puma/detect.rb +4 -0
- data/lib/puma/dsl.rb +237 -56
- data/lib/puma/error_logger.rb +18 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +102 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +24 -12
- data/lib/puma/minissl.rb +99 -11
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +365 -170
- data/lib/puma/runner.rb +56 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +116 -89
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/thread_pool.rb +57 -19
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +9 -10
- data/lib/rack/handler/puma.rb +113 -86
- metadata +9 -5
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
- data/lib/rack/version_restriction.rb +0 -15
data/lib/puma/server.rb
CHANGED
@@ -2,20 +2,19 @@
|
|
2
2
|
|
3
3
|
require 'stringio'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
require_relative 'thread_pool'
|
6
|
+
require_relative 'const'
|
7
|
+
require_relative 'log_writer'
|
8
|
+
require_relative 'events'
|
9
|
+
require_relative 'null_io'
|
10
|
+
require_relative 'reactor'
|
11
|
+
require_relative 'client'
|
12
|
+
require_relative 'binder'
|
13
|
+
require_relative 'util'
|
14
|
+
require_relative 'request'
|
15
15
|
|
16
16
|
require 'socket'
|
17
|
-
require 'io/wait'
|
18
|
-
require 'forwardable'
|
17
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
19
18
|
|
20
19
|
module Puma
|
21
20
|
|
@@ -30,39 +29,30 @@ module Puma
|
|
30
29
|
#
|
31
30
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
32
31
|
class Server
|
33
|
-
|
34
32
|
include Puma::Const
|
35
33
|
include Request
|
36
|
-
extend Forwardable
|
37
34
|
|
38
35
|
attr_reader :thread
|
36
|
+
attr_reader :log_writer
|
39
37
|
attr_reader :events
|
40
38
|
attr_reader :min_threads, :max_threads # for #stats
|
41
39
|
attr_reader :requests_count # @version 5.0.0
|
42
|
-
attr_reader :log_writer # to help with backports
|
43
40
|
|
44
41
|
# @todo the following may be deprecated in the future
|
45
42
|
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
46
43
|
:leak_stack_on_error,
|
47
44
|
:persistent_timeout, :reaping_time
|
48
45
|
|
49
|
-
# @deprecated v6.0.0
|
50
|
-
attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
|
51
|
-
:leak_stack_on_error, :min_threads, :max_threads,
|
52
|
-
:persistent_timeout, :reaping_time
|
53
|
-
|
54
46
|
attr_accessor :app
|
55
47
|
attr_accessor :binder
|
56
48
|
|
57
|
-
|
58
|
-
:add_unix_listener, :connected_ports
|
59
|
-
|
60
|
-
ThreadLocalKey = :puma_server
|
49
|
+
THREAD_LOCAL_KEY = :puma_server
|
61
50
|
|
62
51
|
# Create a server for the rack app +app+.
|
63
52
|
#
|
64
|
-
# +
|
65
|
-
#
|
53
|
+
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
54
|
+
#
|
55
|
+
# +events+ is a Puma::Events object used to notify application status events.
|
66
56
|
#
|
67
57
|
# Server#run returns a thread that you can join on to wait for the server
|
68
58
|
# to do its work.
|
@@ -71,35 +61,54 @@ module Puma
|
|
71
61
|
# and have default values set via +fetch+. Normally the values are set via
|
72
62
|
# `::Puma::Configuration.puma_default_options`.
|
73
63
|
#
|
74
|
-
|
64
|
+
# @note The `events` parameter is set to nil, and set to `Events.new` in code.
|
65
|
+
# Often `options` needs to be passed, but `events` does not. Using nil allows
|
66
|
+
# calling code to not require events.rb.
|
67
|
+
#
|
68
|
+
def initialize(app, events = nil, options = {})
|
75
69
|
@app = app
|
76
|
-
@events = events
|
77
|
-
@log_writer = events
|
70
|
+
@events = events || Events.new
|
78
71
|
|
79
72
|
@check, @notify = nil
|
80
73
|
@status = :stop
|
81
74
|
|
82
|
-
@auto_trim_time = 30
|
83
|
-
@reaping_time = 1
|
84
|
-
|
85
75
|
@thread = nil
|
86
76
|
@thread_pool = nil
|
87
77
|
|
88
|
-
@options = options
|
78
|
+
@options = if options.is_a?(UserFileDefaultOptions)
|
79
|
+
options
|
80
|
+
else
|
81
|
+
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
82
|
+
end
|
89
83
|
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
95
|
-
@
|
96
|
-
@
|
97
|
-
@
|
84
|
+
@log_writer = @options.fetch :log_writer, LogWriter.stdio
|
85
|
+
@early_hints = @options[:early_hints]
|
86
|
+
@first_data_timeout = @options[:first_data_timeout]
|
87
|
+
@persistent_timeout = @options[:persistent_timeout]
|
88
|
+
@idle_timeout = @options[:idle_timeout]
|
89
|
+
@min_threads = @options[:min_threads]
|
90
|
+
@max_threads = @options[:max_threads]
|
91
|
+
@queue_requests = @options[:queue_requests]
|
92
|
+
@max_fast_inline = @options[:max_fast_inline]
|
93
|
+
@io_selector_backend = @options[:io_selector_backend]
|
94
|
+
@http_content_length_limit = @options[:http_content_length_limit]
|
95
|
+
|
96
|
+
# make this a hash, since we prefer `key?` over `include?`
|
97
|
+
@supported_http_methods =
|
98
|
+
if @options[:supported_http_methods] == :any
|
99
|
+
:any
|
100
|
+
else
|
101
|
+
if (ary = @options[:supported_http_methods])
|
102
|
+
ary
|
103
|
+
else
|
104
|
+
SUPPORTED_HTTP_METHODS
|
105
|
+
end.sort.product([nil]).to_h.freeze
|
106
|
+
end
|
98
107
|
|
99
108
|
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
100
109
|
@leak_stack_on_error = @options[:environment] ? temp : true
|
101
110
|
|
102
|
-
@binder = Binder.new(
|
111
|
+
@binder = Binder.new(log_writer)
|
103
112
|
|
104
113
|
ENV['RACK_ENV'] ||= "development"
|
105
114
|
|
@@ -117,7 +126,7 @@ module Puma
|
|
117
126
|
class << self
|
118
127
|
# @!attribute [r] current
|
119
128
|
def current
|
120
|
-
Thread.current[
|
129
|
+
Thread.current[THREAD_LOCAL_KEY]
|
121
130
|
end
|
122
131
|
|
123
132
|
# :nodoc:
|
@@ -195,12 +204,12 @@ module Puma
|
|
195
204
|
|
196
205
|
# @!attribute [r] backlog
|
197
206
|
def backlog
|
198
|
-
@thread_pool
|
207
|
+
@thread_pool&.backlog
|
199
208
|
end
|
200
209
|
|
201
210
|
# @!attribute [r] running
|
202
211
|
def running
|
203
|
-
@thread_pool
|
212
|
+
@thread_pool&.spawned
|
204
213
|
end
|
205
214
|
|
206
215
|
|
@@ -213,7 +222,7 @@ module Puma
|
|
213
222
|
# value would be 4 until it finishes processing.
|
214
223
|
# @!attribute [r] pool_capacity
|
215
224
|
def pool_capacity
|
216
|
-
@thread_pool
|
225
|
+
@thread_pool&.pool_capacity
|
217
226
|
end
|
218
227
|
|
219
228
|
# Runs the server.
|
@@ -229,29 +238,16 @@ module Puma
|
|
229
238
|
|
230
239
|
@status = :run
|
231
240
|
|
232
|
-
@thread_pool = ThreadPool.new(
|
233
|
-
thread_name,
|
234
|
-
@min_threads,
|
235
|
-
@max_threads,
|
236
|
-
::Puma::IOBuffer,
|
237
|
-
&method(:process_client)
|
238
|
-
)
|
239
|
-
|
240
|
-
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
241
|
-
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
241
|
+
@thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
|
242
242
|
|
243
243
|
if @queue_requests
|
244
|
-
@reactor = Reactor.new(@io_selector_backend
|
244
|
+
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
|
245
245
|
@reactor.run
|
246
246
|
end
|
247
247
|
|
248
|
-
if @reaping_time
|
249
|
-
@thread_pool.auto_reap!(@reaping_time)
|
250
|
-
end
|
251
248
|
|
252
|
-
if @
|
253
|
-
|
254
|
-
end
|
249
|
+
@thread_pool.auto_reap! if @options[:reaping_time]
|
250
|
+
@thread_pool.auto_trim! if @options[:auto_trim_time]
|
255
251
|
|
256
252
|
@check, @notify = Puma::Util.pipe unless @notify
|
257
253
|
|
@@ -330,8 +326,12 @@ module Puma
|
|
330
326
|
|
331
327
|
while @status == :run || (drain && shutting_down?)
|
332
328
|
begin
|
333
|
-
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 :
|
334
|
-
|
329
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
|
330
|
+
unless ios
|
331
|
+
@status = :stop unless shutting_down?
|
332
|
+
break
|
333
|
+
end
|
334
|
+
|
335
335
|
ios.first.each do |sock|
|
336
336
|
if sock == check
|
337
337
|
break if handle_check
|
@@ -347,6 +347,7 @@ module Puma
|
|
347
347
|
drain += 1 if shutting_down?
|
348
348
|
pool << Client.new(io, @binder.env(sock)).tap { |c|
|
349
349
|
c.listener = sock
|
350
|
+
c.http_content_length_limit = @http_content_length_limit
|
350
351
|
c.send(addr_send_name, addr_value) if addr_value
|
351
352
|
}
|
352
353
|
end
|
@@ -355,11 +356,11 @@ module Puma
|
|
355
356
|
# In the case that any of the sockets are unexpectedly close.
|
356
357
|
raise
|
357
358
|
rescue StandardError => e
|
358
|
-
@
|
359
|
+
@log_writer.unknown_error e, nil, "Listen loop"
|
359
360
|
end
|
360
361
|
end
|
361
362
|
|
362
|
-
@
|
363
|
+
@log_writer.debug "Drained #{drain} additional connections." if drain
|
363
364
|
@events.fire :state, @status
|
364
365
|
|
365
366
|
if queue_requests
|
@@ -368,14 +369,13 @@ module Puma
|
|
368
369
|
end
|
369
370
|
graceful_shutdown if @status == :stop || @status == :restart
|
370
371
|
rescue Exception => e
|
371
|
-
@
|
372
|
+
@log_writer.unknown_error e, nil, "Exception handling servers"
|
372
373
|
ensure
|
373
|
-
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
374
374
|
# Errno::EBADF is infrequently raised
|
375
375
|
[@check, @notify].each do |io|
|
376
376
|
begin
|
377
377
|
io.close unless io.closed?
|
378
|
-
rescue Errno::EBADF
|
378
|
+
rescue Errno::EBADF
|
379
379
|
end
|
380
380
|
end
|
381
381
|
@notify = nil
|
@@ -414,9 +414,9 @@ module Puma
|
|
414
414
|
# returning.
|
415
415
|
#
|
416
416
|
# Return true if one or more requests were processed.
|
417
|
-
def process_client(client
|
417
|
+
def process_client(client)
|
418
418
|
# Advertise this server into the thread
|
419
|
-
Thread.current[
|
419
|
+
Thread.current[THREAD_LOCAL_KEY] = self
|
420
420
|
|
421
421
|
clean_thread_locals = @options[:clean_thread_locals]
|
422
422
|
close_socket = true
|
@@ -440,15 +440,13 @@ module Puma
|
|
440
440
|
|
441
441
|
while true
|
442
442
|
@requests_count += 1
|
443
|
-
case handle_request(client,
|
443
|
+
case handle_request(client, requests + 1)
|
444
444
|
when false
|
445
445
|
break
|
446
446
|
when :async
|
447
447
|
close_socket = false
|
448
448
|
break
|
449
449
|
when true
|
450
|
-
buffer.reset
|
451
|
-
|
452
450
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
453
451
|
|
454
452
|
requests += 1
|
@@ -478,11 +476,11 @@ module Puma
|
|
478
476
|
end
|
479
477
|
true
|
480
478
|
rescue StandardError => e
|
481
|
-
client_error(e, client)
|
479
|
+
client_error(e, client, requests)
|
482
480
|
# The ensure tries to close +client+ down
|
483
481
|
requests > 0
|
484
482
|
ensure
|
485
|
-
|
483
|
+
client.io_buffer.reset
|
486
484
|
|
487
485
|
begin
|
488
486
|
client.close if close_socket
|
@@ -490,7 +488,7 @@ module Puma
|
|
490
488
|
Puma::Util.purge_interrupt_queue
|
491
489
|
# Already closed
|
492
490
|
rescue StandardError => e
|
493
|
-
@
|
491
|
+
@log_writer.unknown_error e, nil, "Client"
|
494
492
|
end
|
495
493
|
end
|
496
494
|
end
|
@@ -506,23 +504,23 @@ module Puma
|
|
506
504
|
# :nocov:
|
507
505
|
|
508
506
|
# Handle various error types thrown by Client I/O operations.
|
509
|
-
def client_error(e, client)
|
507
|
+
def client_error(e, client, requests = 1)
|
510
508
|
# Swallow, do not log
|
511
509
|
return if [ConnectionError, EOFError].include?(e.class)
|
512
510
|
|
513
|
-
lowlevel_error(e, client.env)
|
514
511
|
case e
|
515
512
|
when MiniSSL::SSLError
|
516
|
-
|
513
|
+
lowlevel_error(e, client.env)
|
514
|
+
@log_writer.ssl_error e, client.io
|
517
515
|
when HttpParserError
|
518
|
-
client
|
519
|
-
@
|
516
|
+
response_to_error(client, requests, e, 400)
|
517
|
+
@log_writer.parse_error e, client
|
520
518
|
when HttpParserError501
|
521
|
-
client
|
522
|
-
@
|
519
|
+
response_to_error(client, requests, e, 501)
|
520
|
+
@log_writer.parse_error e, client
|
523
521
|
else
|
524
|
-
client
|
525
|
-
@
|
522
|
+
response_to_error(client, requests, e, 500)
|
523
|
+
@log_writer.unknown_error e, nil, "Read"
|
526
524
|
end
|
527
525
|
end
|
528
526
|
|
@@ -543,10 +541,17 @@ module Puma
|
|
543
541
|
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
544
542
|
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
545
543
|
else
|
546
|
-
[status, {}, ["
|
544
|
+
[status, {}, [""]]
|
547
545
|
end
|
548
546
|
end
|
549
547
|
|
548
|
+
def response_to_error(client, requests, err, status_code)
|
549
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
550
|
+
prepare_response(status, headers, res_body, requests, client)
|
551
|
+
client.write_error(status_code)
|
552
|
+
end
|
553
|
+
private :response_to_error
|
554
|
+
|
550
555
|
# Wait for all outstanding requests to finish.
|
551
556
|
#
|
552
557
|
def graceful_shutdown
|
@@ -580,7 +585,7 @@ module Puma
|
|
580
585
|
|
581
586
|
def notify_safely(message)
|
582
587
|
@notify << message
|
583
|
-
rescue IOError, NoMethodError, Errno::EPIPE
|
588
|
+
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
584
589
|
# The server, in another thread, is shutting down
|
585
590
|
Puma::Util.purge_interrupt_queue
|
586
591
|
rescue RuntimeError => e
|
@@ -625,5 +630,27 @@ module Puma
|
|
625
630
|
def stats
|
626
631
|
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
627
632
|
end
|
633
|
+
|
634
|
+
# below are 'delegations' to binder
|
635
|
+
# remove in Puma 7?
|
636
|
+
|
637
|
+
|
638
|
+
def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
|
639
|
+
@binder.add_tcp_listener host, port, optimize_for_latency, backlog
|
640
|
+
end
|
641
|
+
|
642
|
+
def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
|
643
|
+
backlog = 1024)
|
644
|
+
@binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
|
645
|
+
end
|
646
|
+
|
647
|
+
def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
|
648
|
+
@binder.add_unix_listener path, umask, mode, backlog
|
649
|
+
end
|
650
|
+
|
651
|
+
# @!attribute [r] connected_ports
|
652
|
+
def connected_ports
|
653
|
+
@binder.connected_ports
|
654
|
+
end
|
628
655
|
end
|
629
656
|
end
|
data/lib/puma/single.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative 'runner'
|
4
|
+
require_relative 'detect'
|
5
|
+
require_relative 'plugin'
|
6
6
|
|
7
7
|
module Puma
|
8
8
|
# This class is instantiated by the `Puma::Launcher` and used
|
@@ -16,26 +16,26 @@ module Puma
|
|
16
16
|
# @!attribute [r] stats
|
17
17
|
def stats
|
18
18
|
{
|
19
|
-
started_at: @started_at
|
20
|
-
}.merge(@server.stats)
|
19
|
+
started_at: utc_iso8601(@started_at)
|
20
|
+
}.merge(@server.stats).merge(super)
|
21
21
|
end
|
22
22
|
|
23
23
|
def restart
|
24
|
-
@server
|
24
|
+
@server&.begin_restart
|
25
25
|
end
|
26
26
|
|
27
27
|
def stop
|
28
|
-
@server
|
28
|
+
@server&.stop false
|
29
29
|
end
|
30
30
|
|
31
31
|
def halt
|
32
|
-
@server
|
32
|
+
@server&.halt
|
33
33
|
end
|
34
34
|
|
35
35
|
def stop_blocked
|
36
36
|
log "- Gracefully stopping, waiting for requests to finish"
|
37
|
-
@control
|
38
|
-
@server
|
37
|
+
@control&.stop true
|
38
|
+
@server&.stop true
|
39
39
|
end
|
40
40
|
|
41
41
|
def run
|
@@ -55,7 +55,9 @@ module Puma
|
|
55
55
|
log "Use Ctrl-C to stop"
|
56
56
|
redirect_io
|
57
57
|
|
58
|
-
@
|
58
|
+
@events.fire_on_booted!
|
59
|
+
|
60
|
+
debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
|
59
61
|
|
60
62
|
begin
|
61
63
|
server_thread.join
|
data/lib/puma/state_file.rb
CHANGED
@@ -15,15 +15,12 @@ module Puma
|
|
15
15
|
|
16
16
|
ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
|
17
17
|
|
18
|
-
# @deprecated 6.0.0
|
19
|
-
FIELDS = ALLOWED_FIELDS
|
20
|
-
|
21
18
|
def initialize
|
22
19
|
@options = {}
|
23
20
|
end
|
24
21
|
|
25
22
|
def save(path, permission = nil)
|
26
|
-
contents = "---\n"
|
23
|
+
contents = +"---\n"
|
27
24
|
@options.each do |k,v|
|
28
25
|
next unless ALLOWED_FIELDS.include? k
|
29
26
|
case v
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'thread'
|
4
4
|
|
5
|
+
require_relative 'io_buffer'
|
6
|
+
|
5
7
|
module Puma
|
6
8
|
# Internal Docs for A simple thread pool management object.
|
7
9
|
#
|
@@ -29,7 +31,7 @@ module Puma
|
|
29
31
|
# The block passed is the work that will be performed in each
|
30
32
|
# thread.
|
31
33
|
#
|
32
|
-
def initialize(name,
|
34
|
+
def initialize(name, options = {}, &block)
|
33
35
|
@not_empty = ConditionVariable.new
|
34
36
|
@not_full = ConditionVariable.new
|
35
37
|
@mutex = Mutex.new
|
@@ -40,10 +42,19 @@ module Puma
|
|
40
42
|
@waiting = 0
|
41
43
|
|
42
44
|
@name = name
|
43
|
-
@min = Integer(
|
44
|
-
@max = Integer(
|
45
|
+
@min = Integer(options[:min_threads])
|
46
|
+
@max = Integer(options[:max_threads])
|
47
|
+
# Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
|
48
|
+
# to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
|
49
|
+
# makes stubbing constants difficult.
|
50
|
+
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
45
51
|
@block = block
|
46
|
-
@
|
52
|
+
@out_of_band = options[:out_of_band]
|
53
|
+
@clean_thread_locals = options[:clean_thread_locals]
|
54
|
+
@before_thread_start = options[:before_thread_start]
|
55
|
+
@before_thread_exit = options[:before_thread_exit]
|
56
|
+
@reaping_time = options[:reaping_time]
|
57
|
+
@auto_trim_time = options[:auto_trim_time]
|
47
58
|
|
48
59
|
@shutdown = false
|
49
60
|
|
@@ -62,14 +73,11 @@ module Puma
|
|
62
73
|
end
|
63
74
|
end
|
64
75
|
|
65
|
-
@clean_thread_locals = false
|
66
76
|
@force_shutdown = false
|
67
77
|
@shutdown_mutex = Mutex.new
|
68
78
|
end
|
69
79
|
|
70
80
|
attr_reader :spawned, :trim_requested, :waiting
|
71
|
-
attr_accessor :clean_thread_locals
|
72
|
-
attr_accessor :out_of_band_hook # @version 5.0.0
|
73
81
|
|
74
82
|
def self.clean_thread_locals
|
75
83
|
Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
|
@@ -101,6 +109,7 @@ module Puma
|
|
101
109
|
def spawn_thread
|
102
110
|
@spawned += 1
|
103
111
|
|
112
|
+
trigger_before_thread_start_hooks
|
104
113
|
th = Thread.new(@spawned) do |spawned|
|
105
114
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
106
115
|
todo = @todo
|
@@ -109,8 +118,6 @@ module Puma
|
|
109
118
|
not_empty = @not_empty
|
110
119
|
not_full = @not_full
|
111
120
|
|
112
|
-
extra = @extra.map { |i| i.new }
|
113
|
-
|
114
121
|
while true
|
115
122
|
work = nil
|
116
123
|
|
@@ -121,6 +128,7 @@ module Puma
|
|
121
128
|
@spawned -= 1
|
122
129
|
@workers.delete th
|
123
130
|
not_full.signal
|
131
|
+
trigger_before_thread_exit_hooks
|
124
132
|
Thread.exit
|
125
133
|
end
|
126
134
|
|
@@ -144,7 +152,7 @@ module Puma
|
|
144
152
|
end
|
145
153
|
|
146
154
|
begin
|
147
|
-
@out_of_band_pending = true if block.call(work
|
155
|
+
@out_of_band_pending = true if block.call(work)
|
148
156
|
rescue Exception => e
|
149
157
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
150
158
|
end
|
@@ -158,14 +166,44 @@ module Puma
|
|
158
166
|
|
159
167
|
private :spawn_thread
|
160
168
|
|
169
|
+
def trigger_before_thread_start_hooks
|
170
|
+
return unless @before_thread_start&.any?
|
171
|
+
|
172
|
+
@before_thread_start.each do |b|
|
173
|
+
begin
|
174
|
+
b.call
|
175
|
+
rescue Exception => e
|
176
|
+
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
private :trigger_before_thread_start_hooks
|
183
|
+
|
184
|
+
def trigger_before_thread_exit_hooks
|
185
|
+
return unless @before_thread_exit&.any?
|
186
|
+
|
187
|
+
@before_thread_exit.each do |b|
|
188
|
+
begin
|
189
|
+
b.call
|
190
|
+
rescue Exception => e
|
191
|
+
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
|
197
|
+
private :trigger_before_thread_exit_hooks
|
198
|
+
|
161
199
|
# @version 5.0.0
|
162
200
|
def trigger_out_of_band_hook
|
163
|
-
return false unless
|
201
|
+
return false unless @out_of_band&.any?
|
164
202
|
|
165
203
|
# we execute on idle hook when all threads are free
|
166
204
|
return false unless @spawned == @waiting
|
167
205
|
|
168
|
-
|
206
|
+
@out_of_band.each(&:call)
|
169
207
|
true
|
170
208
|
rescue Exception => e
|
171
209
|
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
@@ -319,12 +357,12 @@ module Puma
|
|
319
357
|
end
|
320
358
|
end
|
321
359
|
|
322
|
-
def auto_trim!(timeout
|
360
|
+
def auto_trim!(timeout=@auto_trim_time)
|
323
361
|
@auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
|
324
362
|
@auto_trim.start!
|
325
363
|
end
|
326
364
|
|
327
|
-
def auto_reap!(timeout
|
365
|
+
def auto_reap!(timeout=@reaping_time)
|
328
366
|
@reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
|
329
367
|
@reaper.start!
|
330
368
|
end
|
@@ -344,8 +382,8 @@ module Puma
|
|
344
382
|
|
345
383
|
# Tell all threads in the pool to exit and wait for them to finish.
|
346
384
|
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
347
|
-
# Next, wait an extra +
|
348
|
-
# Finally, wait
|
385
|
+
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
386
|
+
# threads. Finally, wait 1 second for remaining threads to exit.
|
349
387
|
#
|
350
388
|
def shutdown(timeout=-1)
|
351
389
|
threads = with_mutex do
|
@@ -354,8 +392,8 @@ module Puma
|
|
354
392
|
@not_empty.broadcast
|
355
393
|
@not_full.broadcast
|
356
394
|
|
357
|
-
@auto_trim
|
358
|
-
@reaper
|
395
|
+
@auto_trim&.stop
|
396
|
+
@reaper&.stop
|
359
397
|
# dup workers so that we join them all safely
|
360
398
|
@workers.dup
|
361
399
|
end
|
@@ -382,7 +420,7 @@ module Puma
|
|
382
420
|
t.raise ForceShutdown if t[:with_force_shutdown]
|
383
421
|
end
|
384
422
|
end
|
385
|
-
join.call(
|
423
|
+
join.call(@shutdown_grace_time)
|
386
424
|
|
387
425
|
# If threads are _still_ running, forcefully kill them and wait to finish.
|
388
426
|
threads.each(&:kill)
|
data/lib/puma/util.rb
CHANGED
@@ -39,17 +39,6 @@ module Puma
|
|
39
39
|
end
|
40
40
|
module_function :unescape, :escape
|
41
41
|
|
42
|
-
# @version 5.0.0
|
43
|
-
def nakayoshi_gc(events)
|
44
|
-
events.log "! Promoting existing objects to old generation..."
|
45
|
-
4.times { GC.start(full_mark: false) }
|
46
|
-
if GC.respond_to?(:compact)
|
47
|
-
events.log "! Compacting..."
|
48
|
-
GC.compact
|
49
|
-
end
|
50
|
-
events.log "! Friendly fork preparation complete."
|
51
|
-
end
|
52
|
-
|
53
42
|
DEFAULT_SEP = /[&;] */n
|
54
43
|
|
55
44
|
# Stolen from Mongrel, with some small modifications:
|