puma 3.12.6 → 6.3.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 +1806 -451
- data/LICENSE +23 -20
- data/README.md +217 -65
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +69 -58
- data/docs/fork_worker.md +31 -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/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +94 -120
- 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/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +61 -3
- data/ext/puma_http11/http11_parser.c +103 -117
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +6 -6
- data/ext/puma_http11/mini_ssl.c +389 -99
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +248 -92
- data/ext/puma_http11/puma_http11.c +49 -57
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +244 -150
- data/lib/puma/cli.rb +38 -34
- data/lib/puma/client.rb +388 -244
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +261 -243
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +116 -88
- data/lib/puma/const.rb +154 -104
- data/lib/puma/control_cli.rb +115 -70
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +764 -134
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -112
- data/lib/puma/io_buffer.rb +42 -5
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +184 -133
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +93 -0
- data/lib/puma/minissl.rb +263 -70
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -1
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/rack/builder.rb +9 -11
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +93 -315
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +94 -69
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +327 -772
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +146 -92
- data/lib/puma/util.rb +22 -10
- data/lib/puma.rb +60 -5
- data/lib/rack/handler/puma.rb +116 -90
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +54 -32
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
- /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
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
|
@@ -13,90 +13,35 @@ module Puma
|
|
13
13
|
# gets created via the `start_server` method from the `Puma::Runner` class
|
14
14
|
# that this inherits from.
|
15
15
|
class Single < Runner
|
16
|
+
# @!attribute [r] stats
|
16
17
|
def stats
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
m = @server.max_threads || 0
|
21
|
-
%Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
|
18
|
+
{
|
19
|
+
started_at: utc_iso8601(@started_at)
|
20
|
+
}.merge(@server.stats).merge(super)
|
22
21
|
end
|
23
22
|
|
24
23
|
def restart
|
25
|
-
@server
|
24
|
+
@server&.begin_restart
|
26
25
|
end
|
27
26
|
|
28
27
|
def stop
|
29
|
-
@server
|
28
|
+
@server&.stop false
|
30
29
|
end
|
31
30
|
|
32
31
|
def halt
|
33
|
-
@server
|
32
|
+
@server&.halt
|
34
33
|
end
|
35
34
|
|
36
35
|
def stop_blocked
|
37
36
|
log "- Gracefully stopping, waiting for requests to finish"
|
38
|
-
@control
|
39
|
-
@server
|
40
|
-
end
|
41
|
-
|
42
|
-
def jruby_daemon?
|
43
|
-
daemon? and Puma.jruby?
|
44
|
-
end
|
45
|
-
|
46
|
-
def jruby_daemon_start
|
47
|
-
require 'puma/jruby_restart'
|
48
|
-
JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
|
37
|
+
@control&.stop true
|
38
|
+
@server&.stop true
|
49
39
|
end
|
50
40
|
|
51
41
|
def run
|
52
|
-
already_daemon = false
|
53
|
-
|
54
|
-
if jruby_daemon?
|
55
|
-
require 'puma/jruby_restart'
|
56
|
-
|
57
|
-
if JRubyRestart.daemon?
|
58
|
-
# load and bind before redirecting IO so errors show up on stdout/stderr
|
59
|
-
load_and_bind
|
60
|
-
redirect_io
|
61
|
-
end
|
62
|
-
|
63
|
-
already_daemon = JRubyRestart.daemon_init
|
64
|
-
end
|
65
|
-
|
66
42
|
output_header "single"
|
67
43
|
|
68
|
-
|
69
|
-
if already_daemon
|
70
|
-
JRubyRestart.perm_daemonize
|
71
|
-
else
|
72
|
-
pid = nil
|
73
|
-
|
74
|
-
Signal.trap "SIGUSR2" do
|
75
|
-
log "* Started new process #{pid} as daemon..."
|
76
|
-
|
77
|
-
# Must use exit! so we don't unwind and run the ensures
|
78
|
-
# that will be run by the new child (such as deleting the
|
79
|
-
# pidfile)
|
80
|
-
exit!(true)
|
81
|
-
end
|
82
|
-
|
83
|
-
Signal.trap "SIGCHLD" do
|
84
|
-
log "! Error starting new process as daemon, exiting"
|
85
|
-
exit 1
|
86
|
-
end
|
87
|
-
|
88
|
-
jruby_daemon_start
|
89
|
-
sleep
|
90
|
-
end
|
91
|
-
else
|
92
|
-
if daemon?
|
93
|
-
log "* Daemonizing..."
|
94
|
-
Process.daemon(true)
|
95
|
-
redirect_io
|
96
|
-
end
|
97
|
-
|
98
|
-
load_and_bind
|
99
|
-
end
|
44
|
+
load_and_bind
|
100
45
|
|
101
46
|
Plugins.fire_background
|
102
47
|
|
@@ -105,16 +50,17 @@ module Puma
|
|
105
50
|
start_control
|
106
51
|
|
107
52
|
@server = server = start_server
|
53
|
+
server_thread = server.run
|
108
54
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
55
|
+
log "Use Ctrl-C to stop"
|
56
|
+
redirect_io
|
57
|
+
|
58
|
+
@events.fire_on_booted!
|
113
59
|
|
114
|
-
@
|
60
|
+
debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
|
115
61
|
|
116
62
|
begin
|
117
|
-
|
63
|
+
server_thread.join
|
118
64
|
rescue Interrupt
|
119
65
|
# Swallow it
|
120
66
|
end
|
data/lib/puma/state_file.rb
CHANGED
@@ -1,24 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'yaml'
|
4
|
-
|
5
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
|
+
#
|
6
14
|
class StateFile
|
15
|
+
|
16
|
+
ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
|
17
|
+
|
7
18
|
def initialize
|
8
19
|
@options = {}
|
9
20
|
end
|
10
21
|
|
11
|
-
def save(path)
|
12
|
-
|
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
|
+
if permission
|
36
|
+
File.write path, contents, mode: 'wb:UTF-8'
|
37
|
+
else
|
38
|
+
File.write path, contents, mode: 'wb:UTF-8', perm: permission
|
39
|
+
end
|
13
40
|
end
|
14
41
|
|
15
42
|
def load(path)
|
16
|
-
|
43
|
+
File.read(path).lines.each do |line|
|
44
|
+
next if line.start_with? '#'
|
45
|
+
k,v = line.split ':', 2
|
46
|
+
next unless v && ALLOWED_FIELDS.include?(k)
|
47
|
+
v = v.strip
|
48
|
+
@options[k] =
|
49
|
+
case v
|
50
|
+
when '' then nil
|
51
|
+
when /\A\d+\z/ then v.to_i
|
52
|
+
when /\A\d+\.\d+\z/ then v.to_f
|
53
|
+
else v.gsub(/\A"|"\z/, '')
|
54
|
+
end
|
55
|
+
end
|
17
56
|
end
|
18
57
|
|
19
|
-
|
20
|
-
|
21
|
-
FIELDS.each do |f|
|
58
|
+
ALLOWED_FIELDS.each do |f|
|
22
59
|
define_method f do
|
23
60
|
@options[f]
|
24
61
|
end
|
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
|
#
|
@@ -13,7 +15,7 @@ module Puma
|
|
13
15
|
# a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
|
14
16
|
#
|
15
17
|
# Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
|
16
|
-
# and
|
18
|
+
# and processes it.
|
17
19
|
class ThreadPool
|
18
20
|
class ForceShutdown < RuntimeError
|
19
21
|
end
|
@@ -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(
|
34
|
+
def initialize(name, options = {}, &block)
|
33
35
|
@not_empty = ConditionVariable.new
|
34
36
|
@not_full = ConditionVariable.new
|
35
37
|
@mutex = Mutex.new
|
@@ -39,14 +41,23 @@ module Puma
|
|
39
41
|
@spawned = 0
|
40
42
|
@waiting = 0
|
41
43
|
|
42
|
-
@
|
43
|
-
@
|
44
|
+
@name = name
|
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)
|
44
51
|
@block = block
|
45
|
-
@
|
52
|
+
@out_of_band = options[:out_of_band]
|
53
|
+
@clean_thread_locals = options[:clean_thread_locals]
|
54
|
+
@reaping_time = options[:reaping_time]
|
55
|
+
@auto_trim_time = options[:auto_trim_time]
|
46
56
|
|
47
57
|
@shutdown = false
|
48
58
|
|
49
59
|
@trim_requested = 0
|
60
|
+
@out_of_band_pending = false
|
50
61
|
|
51
62
|
@workers = []
|
52
63
|
|
@@ -54,17 +65,20 @@ module Puma
|
|
54
65
|
@reaper = nil
|
55
66
|
|
56
67
|
@mutex.synchronize do
|
57
|
-
@min.times
|
68
|
+
@min.times do
|
69
|
+
spawn_thread
|
70
|
+
@not_full.wait(@mutex)
|
71
|
+
end
|
58
72
|
end
|
59
73
|
|
60
|
-
@
|
74
|
+
@force_shutdown = false
|
75
|
+
@shutdown_mutex = Mutex.new
|
61
76
|
end
|
62
77
|
|
63
78
|
attr_reader :spawned, :trim_requested, :waiting
|
64
|
-
attr_accessor :clean_thread_locals
|
65
79
|
|
66
80
|
def self.clean_thread_locals
|
67
|
-
Thread.current.keys.each do |key| # rubocop: disable
|
81
|
+
Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
|
68
82
|
Thread.current[key] = nil unless key == :__recursive_key__
|
69
83
|
end
|
70
84
|
end
|
@@ -72,13 +86,20 @@ module Puma
|
|
72
86
|
# How many objects have yet to be processed by the pool?
|
73
87
|
#
|
74
88
|
def backlog
|
75
|
-
|
89
|
+
with_mutex { @todo.size }
|
76
90
|
end
|
77
91
|
|
92
|
+
# @!attribute [r] pool_capacity
|
78
93
|
def pool_capacity
|
79
94
|
waiting + (@max - spawned)
|
80
95
|
end
|
81
96
|
|
97
|
+
# @!attribute [r] busy_threads
|
98
|
+
# @version 5.0.0
|
99
|
+
def busy_threads
|
100
|
+
with_mutex { @spawned - @waiting + @todo.size }
|
101
|
+
end
|
102
|
+
|
82
103
|
# :nodoc:
|
83
104
|
#
|
84
105
|
# Must be called with @mutex held!
|
@@ -87,61 +108,51 @@ module Puma
|
|
87
108
|
@spawned += 1
|
88
109
|
|
89
110
|
th = Thread.new(@spawned) do |spawned|
|
90
|
-
|
91
|
-
Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
|
111
|
+
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
92
112
|
todo = @todo
|
93
113
|
block = @block
|
94
114
|
mutex = @mutex
|
95
115
|
not_empty = @not_empty
|
96
116
|
not_full = @not_full
|
97
117
|
|
98
|
-
extra = @extra.map { |i| i.new }
|
99
|
-
|
100
118
|
while true
|
101
119
|
work = nil
|
102
120
|
|
103
|
-
continue = true
|
104
|
-
|
105
121
|
mutex.synchronize do
|
106
122
|
while todo.empty?
|
107
123
|
if @trim_requested > 0
|
108
124
|
@trim_requested -= 1
|
109
|
-
|
125
|
+
@spawned -= 1
|
126
|
+
@workers.delete th
|
110
127
|
not_full.signal
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
if @shutdown
|
115
|
-
continue = false
|
116
|
-
break
|
128
|
+
Thread.exit
|
117
129
|
end
|
118
130
|
|
119
131
|
@waiting += 1
|
132
|
+
if @out_of_band_pending && trigger_out_of_band_hook
|
133
|
+
@out_of_band_pending = false
|
134
|
+
end
|
120
135
|
not_full.signal
|
121
|
-
|
122
|
-
|
136
|
+
begin
|
137
|
+
not_empty.wait mutex
|
138
|
+
ensure
|
139
|
+
@waiting -= 1
|
140
|
+
end
|
123
141
|
end
|
124
142
|
|
125
|
-
work = todo.shift
|
143
|
+
work = todo.shift
|
126
144
|
end
|
127
145
|
|
128
|
-
break unless continue
|
129
|
-
|
130
146
|
if @clean_thread_locals
|
131
147
|
ThreadPool.clean_thread_locals
|
132
148
|
end
|
133
149
|
|
134
150
|
begin
|
135
|
-
block.call(work
|
151
|
+
@out_of_band_pending = true if block.call(work)
|
136
152
|
rescue Exception => e
|
137
153
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
138
154
|
end
|
139
155
|
end
|
140
|
-
|
141
|
-
mutex.synchronize do
|
142
|
-
@spawned -= 1
|
143
|
-
@workers.delete th
|
144
|
-
end
|
145
156
|
end
|
146
157
|
|
147
158
|
@workers << th
|
@@ -151,9 +162,32 @@ module Puma
|
|
151
162
|
|
152
163
|
private :spawn_thread
|
153
164
|
|
165
|
+
# @version 5.0.0
|
166
|
+
def trigger_out_of_band_hook
|
167
|
+
return false unless @out_of_band&.any?
|
168
|
+
|
169
|
+
# we execute on idle hook when all threads are free
|
170
|
+
return false unless @spawned == @waiting
|
171
|
+
|
172
|
+
@out_of_band.each(&:call)
|
173
|
+
true
|
174
|
+
rescue Exception => e
|
175
|
+
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
private :trigger_out_of_band_hook
|
180
|
+
|
181
|
+
# @version 5.0.0
|
182
|
+
def with_mutex(&block)
|
183
|
+
@mutex.owned? ?
|
184
|
+
yield :
|
185
|
+
@mutex.synchronize(&block)
|
186
|
+
end
|
187
|
+
|
154
188
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
155
189
|
def <<(work)
|
156
|
-
|
190
|
+
with_mutex do
|
157
191
|
if @shutdown
|
158
192
|
raise "Unable to add work while shutting down"
|
159
193
|
end
|
@@ -190,12 +224,12 @@ module Puma
|
|
190
224
|
# request, it might not be added to the `@todo` array right away.
|
191
225
|
# For example if a slow client has only sent a header, but not a body
|
192
226
|
# then the `@todo` array would stay the same size as the reactor works
|
193
|
-
# to try to buffer the request. In
|
227
|
+
# to try to buffer the request. In that scenario the next call to this
|
194
228
|
# method would not block and another request would be added into the reactor
|
195
|
-
# by the server. This would continue until a fully
|
229
|
+
# by the server. This would continue until a fully buffered request
|
196
230
|
# makes it through the reactor and can then be processed by the thread pool.
|
197
231
|
def wait_until_not_full
|
198
|
-
|
232
|
+
with_mutex do
|
199
233
|
while true
|
200
234
|
return if @shutdown
|
201
235
|
|
@@ -203,20 +237,42 @@ module Puma
|
|
203
237
|
# is work queued that cannot be handled by waiting
|
204
238
|
# threads, then accept more work until we would
|
205
239
|
# spin up the max number of threads.
|
206
|
-
return if
|
240
|
+
return if busy_threads < @max
|
207
241
|
|
208
242
|
@not_full.wait @mutex
|
209
243
|
end
|
210
244
|
end
|
211
245
|
end
|
212
246
|
|
213
|
-
#
|
247
|
+
# @version 5.0.0
|
248
|
+
def wait_for_less_busy_worker(delay_s)
|
249
|
+
return unless delay_s && delay_s > 0
|
250
|
+
|
251
|
+
# Ruby MRI does GVL, this can result
|
252
|
+
# in processing contention when multiple threads
|
253
|
+
# (requests) are running concurrently
|
254
|
+
return unless Puma.mri?
|
255
|
+
|
256
|
+
with_mutex do
|
257
|
+
return if @shutdown
|
258
|
+
|
259
|
+
# do not delay, if we are not busy
|
260
|
+
return unless busy_threads > 0
|
261
|
+
|
262
|
+
# this will be signaled once a request finishes,
|
263
|
+
# which can happen earlier than delay
|
264
|
+
@not_full.wait @mutex, delay_s
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# If there are any free threads in the pool, tell one to go ahead
|
214
269
|
# and exit. If +force+ is true, then a trim request is requested
|
215
270
|
# even if all threads are being utilized.
|
216
271
|
#
|
217
272
|
def trim(force=false)
|
218
|
-
|
219
|
-
|
273
|
+
with_mutex do
|
274
|
+
free = @waiting - @todo.size
|
275
|
+
if (force or free > 0) and @spawned - @trim_requested > @min
|
220
276
|
@trim_requested += 1
|
221
277
|
@not_empty.signal
|
222
278
|
end
|
@@ -226,7 +282,7 @@ module Puma
|
|
226
282
|
# If there are dead threads in the pool make them go away while decreasing
|
227
283
|
# spawned counter so that new healthy threads could be created again.
|
228
284
|
def reap
|
229
|
-
|
285
|
+
with_mutex do
|
230
286
|
dead_workers = @workers.reject(&:alive?)
|
231
287
|
|
232
288
|
dead_workers.each do |worker|
|
@@ -240,10 +296,12 @@ module Puma
|
|
240
296
|
end
|
241
297
|
end
|
242
298
|
|
243
|
-
class
|
244
|
-
def initialize(pool, timeout)
|
299
|
+
class Automaton
|
300
|
+
def initialize(pool, timeout, thread_name, message)
|
245
301
|
@pool = pool
|
246
302
|
@timeout = timeout
|
303
|
+
@thread_name = thread_name
|
304
|
+
@message = message
|
247
305
|
@running = false
|
248
306
|
end
|
249
307
|
|
@@ -251,8 +309,9 @@ module Puma
|
|
251
309
|
@running = true
|
252
310
|
|
253
311
|
@thread = Thread.new do
|
312
|
+
Puma.set_thread_name @thread_name
|
254
313
|
while @running
|
255
|
-
@pool.
|
314
|
+
@pool.public_send(@message)
|
256
315
|
sleep @timeout
|
257
316
|
end
|
258
317
|
end
|
@@ -264,50 +323,43 @@ module Puma
|
|
264
323
|
end
|
265
324
|
end
|
266
325
|
|
267
|
-
def auto_trim!(timeout
|
268
|
-
@auto_trim =
|
326
|
+
def auto_trim!(timeout=@auto_trim_time)
|
327
|
+
@auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
|
269
328
|
@auto_trim.start!
|
270
329
|
end
|
271
330
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
@timeout = timeout
|
276
|
-
@running = false
|
277
|
-
end
|
278
|
-
|
279
|
-
def start!
|
280
|
-
@running = true
|
281
|
-
|
282
|
-
@thread = Thread.new do
|
283
|
-
while @running
|
284
|
-
@pool.reap
|
285
|
-
sleep @timeout
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
def stop
|
291
|
-
@running = false
|
292
|
-
@thread.wakeup
|
293
|
-
end
|
331
|
+
def auto_reap!(timeout=@reaping_time)
|
332
|
+
@reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
|
333
|
+
@reaper.start!
|
294
334
|
end
|
295
335
|
|
296
|
-
|
297
|
-
|
298
|
-
|
336
|
+
# Allows ThreadPool::ForceShutdown to be raised within the
|
337
|
+
# provided block if the thread is forced to shutdown during execution.
|
338
|
+
def with_force_shutdown
|
339
|
+
t = Thread.current
|
340
|
+
@shutdown_mutex.synchronize do
|
341
|
+
raise ForceShutdown if @force_shutdown
|
342
|
+
t[:with_force_shutdown] = true
|
343
|
+
end
|
344
|
+
yield
|
345
|
+
ensure
|
346
|
+
t[:with_force_shutdown] = false
|
299
347
|
end
|
300
348
|
|
301
349
|
# Tell all threads in the pool to exit and wait for them to finish.
|
350
|
+
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
351
|
+
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
352
|
+
# threads. Finally, wait 1 second for remaining threads to exit.
|
302
353
|
#
|
303
354
|
def shutdown(timeout=-1)
|
304
|
-
threads =
|
355
|
+
threads = with_mutex do
|
305
356
|
@shutdown = true
|
357
|
+
@trim_requested = @spawned
|
306
358
|
@not_empty.broadcast
|
307
359
|
@not_full.broadcast
|
308
360
|
|
309
|
-
@auto_trim
|
310
|
-
@reaper
|
361
|
+
@auto_trim&.stop
|
362
|
+
@reaper&.stop
|
311
363
|
# dup workers so that we join them all safely
|
312
364
|
@workers.dup
|
313
365
|
end
|
@@ -316,27 +368,29 @@ module Puma
|
|
316
368
|
# Wait for threads to finish without force shutdown.
|
317
369
|
threads.each(&:join)
|
318
370
|
else
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
t.join
|
324
|
-
end
|
325
|
-
|
326
|
-
if threads.empty?
|
327
|
-
break
|
328
|
-
else
|
329
|
-
sleep 1
|
371
|
+
join = ->(inner_timeout) do
|
372
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
373
|
+
threads.reject! do |t|
|
374
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
375
|
+
t.join inner_timeout - elapsed
|
330
376
|
end
|
331
377
|
end
|
332
378
|
|
333
|
-
threads
|
334
|
-
|
335
|
-
end
|
379
|
+
# Wait +timeout+ seconds for threads to finish.
|
380
|
+
join.call(timeout)
|
336
381
|
|
337
|
-
threads
|
338
|
-
|
382
|
+
# If threads are still running, raise ForceShutdown and wait to finish.
|
383
|
+
@shutdown_mutex.synchronize do
|
384
|
+
@force_shutdown = true
|
385
|
+
threads.each do |t|
|
386
|
+
t.raise ForceShutdown if t[:with_force_shutdown]
|
387
|
+
end
|
339
388
|
end
|
389
|
+
join.call(@shutdown_grace_time)
|
390
|
+
|
391
|
+
# If threads are _still_ running, forcefully kill them and wait to finish.
|
392
|
+
threads.each(&:kill)
|
393
|
+
join.call(1)
|
340
394
|
end
|
341
395
|
|
342
396
|
@spawned = 0
|
data/lib/puma/util.rb
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
|
3
2
|
|
4
|
-
|
5
|
-
require 'puma/rack/backports/uri/common_193'
|
6
|
-
else
|
7
|
-
require 'uri/common'
|
8
|
-
end
|
3
|
+
require 'uri/common'
|
9
4
|
|
10
5
|
module Puma
|
11
6
|
module Util
|
@@ -15,18 +10,34 @@ module Puma
|
|
15
10
|
IO.pipe
|
16
11
|
end
|
17
12
|
|
18
|
-
#
|
19
|
-
#
|
13
|
+
# An instance method on Thread has been provided to address https://bugs.ruby-lang.org/issues/13632,
|
14
|
+
# which currently effects 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
|
+
# Escapes and unescapes a URI escaped string with
|
21
|
+
# +encoding+. +encoding+ will be the target encoding of the string
|
22
|
+
# returned, and it defaults to UTF-8
|
20
23
|
if defined?(::Encoding)
|
24
|
+
def escape(s, encoding = Encoding::UTF_8)
|
25
|
+
URI.encode_www_form_component(s, encoding)
|
26
|
+
end
|
27
|
+
|
21
28
|
def unescape(s, encoding = Encoding::UTF_8)
|
22
29
|
URI.decode_www_form_component(s, encoding)
|
23
30
|
end
|
24
31
|
else
|
32
|
+
def escape(s, encoding = nil)
|
33
|
+
URI.encode_www_form_component(s, encoding)
|
34
|
+
end
|
35
|
+
|
25
36
|
def unescape(s, encoding = nil)
|
26
37
|
URI.decode_www_form_component(s, encoding)
|
27
38
|
end
|
28
39
|
end
|
29
|
-
module_function :unescape
|
40
|
+
module_function :unescape, :escape
|
30
41
|
|
31
42
|
DEFAULT_SEP = /[&;] */n
|
32
43
|
|
@@ -55,7 +66,7 @@ module Puma
|
|
55
66
|
end
|
56
67
|
end
|
57
68
|
|
58
|
-
|
69
|
+
params
|
59
70
|
end
|
60
71
|
|
61
72
|
# A case-insensitive Hash that preserves the original case of a
|
@@ -77,6 +88,7 @@ module Puma
|
|
77
88
|
end
|
78
89
|
end
|
79
90
|
|
91
|
+
# @!attribute [r] to_hash
|
80
92
|
def to_hash
|
81
93
|
hash = {}
|
82
94
|
each { |k,v| hash[k] = v }
|