puma 2.1.1 → 7.2.1
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 +7 -0
- data/History.md +3273 -0
- data/LICENSE +23 -20
- data/README.md +388 -100
- data/bin/puma-wild +25 -0
- data/bin/pumactl +1 -1
- data/docs/architecture.md +74 -0
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +137 -0
- data/docs/fork_worker.md +41 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +9 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +73 -0
- data/docs/nginx.md +5 -5
- data/docs/plugins.md +42 -0
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +65 -0
- data/docs/signals.md +98 -0
- data/docs/stats.md +148 -0
- data/docs/systemd.md +253 -0
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +59 -2
- data/ext/puma_http11/http11_parser.c +319 -487
- data/ext/puma_http11/http11_parser.h +15 -13
- data/ext/puma_http11/http11_parser.java.rl +27 -43
- data/ext/puma_http11/http11_parser.rl +27 -24
- data/ext/puma_http11/http11_parser_common.rl +8 -8
- data/ext/puma_http11/mini_ssl.c +696 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +145 -113
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +139 -172
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
- data/ext/puma_http11/puma_http11.c +183 -175
- data/lib/puma/app/status.rb +77 -29
- data/lib/puma/binder.rb +349 -119
- data/lib/puma/cli.rb +164 -769
- data/lib/puma/client.rb +591 -132
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +127 -0
- data/lib/puma/cluster.rb +634 -0
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +115 -0
- data/lib/puma/configuration.rb +370 -202
- data/lib/puma/const.rb +266 -115
- data/lib/puma/control_cli.rb +219 -120
- data/lib/puma/detect.rb +45 -2
- data/lib/puma/dsl.rb +1496 -0
- data/lib/puma/error_logger.rb +115 -0
- data/lib/puma/events.rb +44 -60
- data/lib/puma/io_buffer.rb +48 -5
- data/lib/puma/jruby_restart.rb +2 -51
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +502 -0
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +96 -0
- data/lib/puma/minissl.rb +355 -37
- data/lib/puma/null_io.rb +79 -12
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +102 -133
- data/lib/puma/request.rb +703 -0
- data/lib/puma/runner.rb +211 -0
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +544 -436
- data/lib/puma/single.rb +72 -0
- data/lib/puma/state_file.rb +69 -0
- data/lib/puma/thread_pool.rb +282 -56
- data/lib/puma/util.rb +125 -0
- data/lib/puma.rb +80 -6
- data/lib/rack/handler/puma.rb +125 -47
- data/tools/Dockerfile +26 -0
- data/tools/trickletest.rb +44 -0
- metadata +77 -148
- data/COPYING +0 -55
- data/Gemfile +0 -10
- data/History.txt +0 -291
- data/Manifest.txt +0 -60
- data/Rakefile +0 -121
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/ext_help.h +0 -15
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/capistrano.rb +0 -34
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -154
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -207
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -332
- data/tools/jungle/init.d/run-puma +0 -3
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -52
data/lib/puma/single.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'runner'
|
|
4
|
+
require_relative 'detect'
|
|
5
|
+
require_relative 'plugin'
|
|
6
|
+
|
|
7
|
+
module Puma
|
|
8
|
+
# This class is instantiated by the `Puma::Launcher` and used
|
|
9
|
+
# to boot and serve a Ruby application when no puma "workers" are needed
|
|
10
|
+
# i.e. only using "threaded" mode. For example `$ puma -t 1:5`
|
|
11
|
+
#
|
|
12
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
|
13
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
|
14
|
+
# that this inherits from.
|
|
15
|
+
class Single < Runner
|
|
16
|
+
# @!attribute [r] stats
|
|
17
|
+
def stats
|
|
18
|
+
{
|
|
19
|
+
started_at: utc_iso8601(@started_at)
|
|
20
|
+
}.merge(@server&.stats || {}).merge(super)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def restart
|
|
24
|
+
@server&.begin_restart
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop
|
|
28
|
+
@server&.stop false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def halt
|
|
32
|
+
@server&.halt
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def stop_blocked
|
|
36
|
+
log "- Gracefully stopping, waiting for requests to finish"
|
|
37
|
+
@control&.stop true
|
|
38
|
+
@server&.stop true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def run
|
|
42
|
+
output_header "single"
|
|
43
|
+
|
|
44
|
+
load_and_bind
|
|
45
|
+
|
|
46
|
+
Plugins.fire_background
|
|
47
|
+
|
|
48
|
+
@launcher.write_state
|
|
49
|
+
|
|
50
|
+
start_control
|
|
51
|
+
|
|
52
|
+
@server = start_server
|
|
53
|
+
server_thread = @server.run
|
|
54
|
+
|
|
55
|
+
log "Use Ctrl-C to stop"
|
|
56
|
+
|
|
57
|
+
warn_ruby_mn_threads
|
|
58
|
+
|
|
59
|
+
redirect_io
|
|
60
|
+
|
|
61
|
+
@events.fire_after_booted!
|
|
62
|
+
|
|
63
|
+
debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
server_thread.join
|
|
67
|
+
rescue Interrupt
|
|
68
|
+
# Swallow it
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
|
|
5
|
+
# Puma::Launcher uses StateFile to write a yaml file for use with Puma::ControlCLI.
|
|
6
|
+
#
|
|
7
|
+
# In previous versions of Puma, YAML was used to read/write the state file.
|
|
8
|
+
# Since Puma is similar to Bundler/RubyGems in that it may load before one's app
|
|
9
|
+
# does, minimizing the dependencies that may be shared with the app is desired.
|
|
10
|
+
#
|
|
11
|
+
# At present, it only works with numeric and string values. It is still a valid
|
|
12
|
+
# yaml file, and the CI tests parse it with Psych.
|
|
13
|
+
#
|
|
14
|
+
class StateFile
|
|
15
|
+
|
|
16
|
+
ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@options = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def save(path, permission = nil)
|
|
23
|
+
contents = +"---\n"
|
|
24
|
+
@options.each do |k,v|
|
|
25
|
+
next unless ALLOWED_FIELDS.include? k
|
|
26
|
+
case v
|
|
27
|
+
when Numeric
|
|
28
|
+
contents << "#{k}: #{v}\n"
|
|
29
|
+
when String
|
|
30
|
+
next if v.strip.empty?
|
|
31
|
+
contents << (k == 'running_from' || v.to_s.include?(' ') ?
|
|
32
|
+
"#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if permission
|
|
37
|
+
File.write path, contents, mode: 'wb:UTF-8', perm: permission
|
|
38
|
+
else
|
|
39
|
+
File.write path, contents, mode: 'wb:UTF-8'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load(path)
|
|
44
|
+
File.read(path).lines.each do |line|
|
|
45
|
+
next if line.start_with? '#'
|
|
46
|
+
k,v = line.split ':', 2
|
|
47
|
+
next unless v && ALLOWED_FIELDS.include?(k)
|
|
48
|
+
v = v.strip
|
|
49
|
+
@options[k] =
|
|
50
|
+
case v
|
|
51
|
+
when '' then nil
|
|
52
|
+
when /\A\d+\z/ then v.to_i
|
|
53
|
+
when /\A\d+\.\d+\z/ then v.to_f
|
|
54
|
+
else v.gsub(/\A"|"\z/, '')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
ALLOWED_FIELDS.each do |f|
|
|
60
|
+
define_method f.to_sym do
|
|
61
|
+
@options[f]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
define_method :"#{f}=" do |v|
|
|
65
|
+
@options[f] = v
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/puma/thread_pool.rb
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thread'
|
|
2
4
|
|
|
5
|
+
require_relative 'io_buffer'
|
|
6
|
+
|
|
3
7
|
module Puma
|
|
4
|
-
|
|
8
|
+
|
|
9
|
+
# Add `Thread#puma_server` and `Thread#puma_server=`
|
|
10
|
+
Thread.attr_accessor(:puma_server)
|
|
11
|
+
|
|
12
|
+
# Internal Docs for A simple thread pool management object.
|
|
13
|
+
#
|
|
14
|
+
# Each Puma "worker" has a thread pool to process requests.
|
|
15
|
+
#
|
|
16
|
+
# First a connection to a client is made in `Puma::Server`. It is wrapped in a
|
|
17
|
+
# `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
|
|
18
|
+
# the whole request is buffered into memory. Once the request is ready, it is passed into
|
|
19
|
+
# a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
|
|
5
20
|
#
|
|
21
|
+
# Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
|
|
22
|
+
# and processes it.
|
|
6
23
|
class ThreadPool
|
|
24
|
+
class ForceShutdown < RuntimeError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# How long, after raising the ForceShutdown of a thread during
|
|
28
|
+
# forced shutdown mode, to wait for the thread to try and finish
|
|
29
|
+
# up its work before leaving the thread to die on the vine.
|
|
30
|
+
SHUTDOWN_GRACE_TIME = 5 # seconds
|
|
31
|
+
|
|
32
|
+
attr_reader :out_of_band_running
|
|
7
33
|
|
|
8
34
|
# Maintain a minimum of +min+ and maximum of +max+ threads
|
|
9
35
|
# in the pool.
|
|
@@ -11,39 +37,97 @@ module Puma
|
|
|
11
37
|
# The block passed is the work that will be performed in each
|
|
12
38
|
# thread.
|
|
13
39
|
#
|
|
14
|
-
def initialize(
|
|
15
|
-
@
|
|
16
|
-
@mutex = Mutex.new
|
|
40
|
+
def initialize(name, options = {}, server: nil, &block)
|
|
41
|
+
@server = server
|
|
17
42
|
|
|
18
|
-
@
|
|
43
|
+
@not_empty = ConditionVariable.new
|
|
44
|
+
@not_full = ConditionVariable.new
|
|
45
|
+
@mutex = Mutex.new
|
|
46
|
+
@todo = Queue.new
|
|
19
47
|
|
|
48
|
+
@backlog_max = 0
|
|
20
49
|
@spawned = 0
|
|
21
50
|
@waiting = 0
|
|
22
51
|
|
|
23
|
-
@
|
|
24
|
-
@
|
|
25
|
-
@
|
|
26
|
-
|
|
52
|
+
@name = name
|
|
53
|
+
@min = Integer(options[:min_threads])
|
|
54
|
+
@max = Integer(options[:max_threads])
|
|
55
|
+
# Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
|
|
56
|
+
# to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
|
|
57
|
+
# makes stubbing constants difficult.
|
|
58
|
+
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
|
59
|
+
@block = block
|
|
60
|
+
@out_of_band = options[:out_of_band]
|
|
61
|
+
@out_of_band_running = false
|
|
62
|
+
@out_of_band_condvar = ConditionVariable.new
|
|
63
|
+
@before_thread_start = options[:before_thread_start]
|
|
64
|
+
@before_thread_exit = options[:before_thread_exit]
|
|
65
|
+
@reaping_time = options[:reaping_time]
|
|
66
|
+
@auto_trim_time = options[:auto_trim_time]
|
|
27
67
|
|
|
28
68
|
@shutdown = false
|
|
29
69
|
|
|
30
70
|
@trim_requested = 0
|
|
71
|
+
@out_of_band_pending = false
|
|
31
72
|
|
|
32
73
|
@workers = []
|
|
33
74
|
|
|
34
75
|
@auto_trim = nil
|
|
76
|
+
@reaper = nil
|
|
35
77
|
|
|
36
78
|
@mutex.synchronize do
|
|
37
|
-
min.times
|
|
79
|
+
@min.times do
|
|
80
|
+
spawn_thread
|
|
81
|
+
@not_full.wait(@mutex)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
@force_shutdown = false
|
|
86
|
+
@shutdown_mutex = Mutex.new
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
attr_reader :spawned, :trim_requested, :waiting
|
|
90
|
+
|
|
91
|
+
# generate stats hash so as not to perform multiple locks
|
|
92
|
+
# @return [Hash] hash containing stat info from ThreadPool
|
|
93
|
+
def stats
|
|
94
|
+
with_mutex do
|
|
95
|
+
temp = @backlog_max
|
|
96
|
+
@backlog_max = 0
|
|
97
|
+
{ backlog: @todo.size,
|
|
98
|
+
running: @spawned,
|
|
99
|
+
pool_capacity: @waiting + (@max - @spawned),
|
|
100
|
+
busy_threads: @spawned - @waiting + @todo.size,
|
|
101
|
+
backlog_max: temp
|
|
102
|
+
}
|
|
38
103
|
end
|
|
39
104
|
end
|
|
40
105
|
|
|
41
|
-
|
|
106
|
+
def reset_max
|
|
107
|
+
with_mutex { @backlog_max = 0 }
|
|
108
|
+
end
|
|
42
109
|
|
|
43
110
|
# How many objects have yet to be processed by the pool?
|
|
44
111
|
#
|
|
45
112
|
def backlog
|
|
46
|
-
|
|
113
|
+
with_mutex { @todo.size }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The maximum size of the backlog
|
|
117
|
+
#
|
|
118
|
+
def backlog_max
|
|
119
|
+
with_mutex { @backlog_max }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @!attribute [r] pool_capacity
|
|
123
|
+
def pool_capacity
|
|
124
|
+
waiting + (@max - spawned)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @!attribute [r] busy_threads
|
|
128
|
+
# @version 5.0.0
|
|
129
|
+
def busy_threads
|
|
130
|
+
with_mutex { @spawned - @waiting + @todo.size }
|
|
47
131
|
end
|
|
48
132
|
|
|
49
133
|
# :nodoc:
|
|
@@ -53,48 +137,52 @@ module Puma
|
|
|
53
137
|
def spawn_thread
|
|
54
138
|
@spawned += 1
|
|
55
139
|
|
|
56
|
-
|
|
140
|
+
trigger_before_thread_start_hooks
|
|
141
|
+
th = Thread.new(@spawned) do |spawned|
|
|
142
|
+
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
|
143
|
+
# Advertise server into the thread
|
|
144
|
+
Thread.current.puma_server = @server
|
|
145
|
+
|
|
57
146
|
todo = @todo
|
|
58
147
|
block = @block
|
|
59
148
|
mutex = @mutex
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
extra = @extra.map { |i| i.new }
|
|
149
|
+
not_empty = @not_empty
|
|
150
|
+
not_full = @not_full
|
|
63
151
|
|
|
64
152
|
while true
|
|
65
153
|
work = nil
|
|
66
154
|
|
|
67
|
-
continue = true
|
|
68
|
-
|
|
69
155
|
mutex.synchronize do
|
|
70
156
|
while todo.empty?
|
|
71
157
|
if @trim_requested > 0
|
|
72
158
|
@trim_requested -= 1
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
continue = false
|
|
79
|
-
break
|
|
159
|
+
@spawned -= 1
|
|
160
|
+
@workers.delete th
|
|
161
|
+
not_full.signal
|
|
162
|
+
trigger_before_thread_exit_hooks
|
|
163
|
+
Thread.exit
|
|
80
164
|
end
|
|
81
165
|
|
|
82
166
|
@waiting += 1
|
|
83
|
-
|
|
84
|
-
|
|
167
|
+
if @out_of_band_pending && trigger_out_of_band_hook
|
|
168
|
+
@out_of_band_pending = false
|
|
169
|
+
end
|
|
170
|
+
not_full.signal
|
|
171
|
+
begin
|
|
172
|
+
not_empty.wait mutex
|
|
173
|
+
ensure
|
|
174
|
+
@waiting -= 1
|
|
175
|
+
end
|
|
85
176
|
end
|
|
86
177
|
|
|
87
|
-
work = todo.
|
|
178
|
+
work = todo.shift
|
|
88
179
|
end
|
|
89
180
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
mutex.synchronize do
|
|
96
|
-
@spawned -= 1
|
|
97
|
-
@workers.delete th
|
|
181
|
+
begin
|
|
182
|
+
@out_of_band_pending = true if block.call(work)
|
|
183
|
+
rescue Exception => e
|
|
184
|
+
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
|
185
|
+
end
|
|
98
186
|
end
|
|
99
187
|
end
|
|
100
188
|
|
|
@@ -105,40 +193,126 @@ module Puma
|
|
|
105
193
|
|
|
106
194
|
private :spawn_thread
|
|
107
195
|
|
|
196
|
+
def trigger_before_thread_start_hooks
|
|
197
|
+
return unless @before_thread_start&.any?
|
|
198
|
+
|
|
199
|
+
@before_thread_start.each do |b|
|
|
200
|
+
begin
|
|
201
|
+
b[:block].call
|
|
202
|
+
rescue Exception => e
|
|
203
|
+
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private :trigger_before_thread_start_hooks
|
|
210
|
+
|
|
211
|
+
def trigger_before_thread_exit_hooks
|
|
212
|
+
return unless @before_thread_exit&.any?
|
|
213
|
+
|
|
214
|
+
@before_thread_exit.each do |b|
|
|
215
|
+
begin
|
|
216
|
+
b[:block].call
|
|
217
|
+
rescue Exception => e
|
|
218
|
+
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
nil
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private :trigger_before_thread_exit_hooks
|
|
225
|
+
|
|
226
|
+
# @version 5.0.0
|
|
227
|
+
def trigger_out_of_band_hook
|
|
228
|
+
return false unless @out_of_band&.any?
|
|
229
|
+
|
|
230
|
+
# we execute on idle hook when all threads are free
|
|
231
|
+
return false unless @spawned == @waiting
|
|
232
|
+
@out_of_band_running = true
|
|
233
|
+
@out_of_band.each { |b| b[:block].call }
|
|
234
|
+
true
|
|
235
|
+
rescue Exception => e
|
|
236
|
+
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
|
237
|
+
true
|
|
238
|
+
ensure
|
|
239
|
+
@out_of_band_running = false
|
|
240
|
+
@out_of_band_condvar.broadcast
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private :trigger_out_of_band_hook
|
|
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
|
+
|
|
253
|
+
# @version 5.0.0
|
|
254
|
+
def with_mutex(&block)
|
|
255
|
+
@mutex.owned? ?
|
|
256
|
+
yield :
|
|
257
|
+
@mutex.synchronize(&block)
|
|
258
|
+
end
|
|
259
|
+
|
|
108
260
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
|
109
261
|
def <<(work)
|
|
110
|
-
|
|
262
|
+
with_mutex do
|
|
111
263
|
if @shutdown
|
|
112
264
|
raise "Unable to add work while shutting down"
|
|
113
265
|
end
|
|
114
266
|
|
|
115
267
|
@todo << work
|
|
268
|
+
t = @todo.size
|
|
269
|
+
@backlog_max = t if t > @backlog_max
|
|
116
270
|
|
|
117
|
-
if @waiting
|
|
271
|
+
if @waiting < @todo.size and @spawned < @max
|
|
118
272
|
spawn_thread
|
|
119
273
|
end
|
|
120
274
|
|
|
121
|
-
@
|
|
275
|
+
@not_empty.signal
|
|
122
276
|
end
|
|
123
277
|
end
|
|
124
278
|
|
|
125
|
-
# If
|
|
279
|
+
# If there are any free threads in the pool, tell one to go ahead
|
|
126
280
|
# and exit. If +force+ is true, then a trim request is requested
|
|
127
281
|
# even if all threads are being utilized.
|
|
128
282
|
#
|
|
129
283
|
def trim(force=false)
|
|
130
|
-
|
|
131
|
-
|
|
284
|
+
with_mutex do
|
|
285
|
+
free = @waiting - @todo.size
|
|
286
|
+
if (force or free > 0) and @spawned - @trim_requested > @min
|
|
132
287
|
@trim_requested += 1
|
|
133
|
-
@
|
|
288
|
+
@not_empty.signal
|
|
134
289
|
end
|
|
135
290
|
end
|
|
136
291
|
end
|
|
137
292
|
|
|
138
|
-
|
|
139
|
-
|
|
293
|
+
# If there are dead threads in the pool make them go away while decreasing
|
|
294
|
+
# spawned counter so that new healthy threads could be created again.
|
|
295
|
+
def reap
|
|
296
|
+
with_mutex do
|
|
297
|
+
dead_workers = @workers.reject(&:alive?)
|
|
298
|
+
|
|
299
|
+
dead_workers.each do |worker|
|
|
300
|
+
worker.kill
|
|
301
|
+
@spawned -= 1
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
@workers.delete_if do |w|
|
|
305
|
+
dead_workers.include?(w)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
class Automaton
|
|
311
|
+
def initialize(pool, timeout, thread_name, message)
|
|
140
312
|
@pool = pool
|
|
141
313
|
@timeout = timeout
|
|
314
|
+
@thread_name = thread_name
|
|
315
|
+
@message = message
|
|
142
316
|
@running = false
|
|
143
317
|
end
|
|
144
318
|
|
|
@@ -146,8 +320,9 @@ module Puma
|
|
|
146
320
|
@running = true
|
|
147
321
|
|
|
148
322
|
@thread = Thread.new do
|
|
323
|
+
Puma.set_thread_name @thread_name
|
|
149
324
|
while @running
|
|
150
|
-
@pool.
|
|
325
|
+
@pool.public_send(@message)
|
|
151
326
|
sleep @timeout
|
|
152
327
|
end
|
|
153
328
|
end
|
|
@@ -159,24 +334,75 @@ module Puma
|
|
|
159
334
|
end
|
|
160
335
|
end
|
|
161
336
|
|
|
162
|
-
def auto_trim!(timeout
|
|
163
|
-
@auto_trim =
|
|
337
|
+
def auto_trim!(timeout=@auto_trim_time)
|
|
338
|
+
@auto_trim = Automaton.new(self, timeout, "#{@name} tp trim", :trim)
|
|
164
339
|
@auto_trim.start!
|
|
165
340
|
end
|
|
166
341
|
|
|
342
|
+
def auto_reap!(timeout=@reaping_time)
|
|
343
|
+
@reaper = Automaton.new(self, timeout, "#{@name} tp reap", :reap)
|
|
344
|
+
@reaper.start!
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Allows ThreadPool::ForceShutdown to be raised within the
|
|
348
|
+
# provided block if the thread is forced to shutdown during execution.
|
|
349
|
+
def with_force_shutdown
|
|
350
|
+
t = Thread.current
|
|
351
|
+
@shutdown_mutex.synchronize do
|
|
352
|
+
raise ForceShutdown if @force_shutdown
|
|
353
|
+
t[:with_force_shutdown] = true
|
|
354
|
+
end
|
|
355
|
+
yield
|
|
356
|
+
ensure
|
|
357
|
+
t[:with_force_shutdown] = false
|
|
358
|
+
end
|
|
359
|
+
|
|
167
360
|
# Tell all threads in the pool to exit and wait for them to finish.
|
|
361
|
+
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
|
362
|
+
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
|
363
|
+
# threads. Finally, wait 1 second for remaining threads to exit.
|
|
168
364
|
#
|
|
169
|
-
def shutdown
|
|
170
|
-
|
|
365
|
+
def shutdown(timeout=-1)
|
|
366
|
+
threads = with_mutex do
|
|
171
367
|
@shutdown = true
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
@
|
|
368
|
+
@trim_requested = @spawned
|
|
369
|
+
@not_empty.broadcast
|
|
370
|
+
@not_full.broadcast
|
|
371
|
+
|
|
372
|
+
@auto_trim&.stop
|
|
373
|
+
@reaper&.stop
|
|
374
|
+
# dup workers so that we join them all safely
|
|
375
|
+
@workers.dup
|
|
175
376
|
end
|
|
176
377
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
378
|
+
if timeout == -1
|
|
379
|
+
# Wait for threads to finish without force shutdown.
|
|
380
|
+
threads.each(&:join)
|
|
381
|
+
else
|
|
382
|
+
join = ->(inner_timeout) do
|
|
383
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
384
|
+
threads.reject! do |t|
|
|
385
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
386
|
+
t.join inner_timeout - elapsed
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Wait +timeout+ seconds for threads to finish.
|
|
391
|
+
join.call(timeout)
|
|
392
|
+
|
|
393
|
+
# If threads are still running, raise ForceShutdown and wait to finish.
|
|
394
|
+
@shutdown_mutex.synchronize do
|
|
395
|
+
@force_shutdown = true
|
|
396
|
+
threads.each do |t|
|
|
397
|
+
t.raise ForceShutdown if t[:with_force_shutdown]
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
join.call(@shutdown_grace_time)
|
|
401
|
+
|
|
402
|
+
# If threads are _still_ running, forcefully kill them and wait to finish.
|
|
403
|
+
threads.each(&:kill)
|
|
404
|
+
join.call(1)
|
|
405
|
+
end
|
|
180
406
|
|
|
181
407
|
@spawned = 0
|
|
182
408
|
@workers = []
|