puma 4.3.12 → 6.0.2
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 +1618 -521
- data/LICENSE +23 -20
- data/README.md +130 -42
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -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/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +85 -128
- 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 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +250 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +52 -38
- data/lib/puma/binder.rb +232 -119
- data/lib/puma/cli.rb +33 -33
- data/lib/puma/client.rb +129 -88
- data/lib/puma/cluster/worker.rb +175 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +224 -231
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +112 -87
- data/lib/puma/const.rb +86 -91
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +31 -2
- data/lib/puma/dsl.rb +426 -110
- data/lib/puma/error_logger.rb +112 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -2
- 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 +170 -148
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +35 -19
- data/lib/puma/minissl.rb +213 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +5 -9
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +644 -0
- data/lib/puma/runner.rb +86 -76
- data/lib/puma/server.rb +306 -793
- data/lib/puma/single.rb +18 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/systemd.rb +47 -0
- data/lib/puma/thread_pool.rb +136 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +11 -12
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- metadata +31 -23
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- 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/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!{ "started_at": "#{@started_at.utc.iso8601}", "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,15 @@ module Puma
|
|
105
50
|
start_control
|
106
51
|
|
107
52
|
@server = server = start_server
|
53
|
+
server_thread = server.run
|
108
54
|
|
109
|
-
|
110
|
-
|
111
|
-
redirect_io
|
112
|
-
end
|
55
|
+
log "Use Ctrl-C to stop"
|
56
|
+
redirect_io
|
113
57
|
|
114
|
-
@
|
58
|
+
@events.fire_on_booted!
|
115
59
|
|
116
60
|
begin
|
117
|
-
|
61
|
+
server_thread.join
|
118
62
|
rescue Interrupt
|
119
63
|
# Swallow it
|
120
64
|
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/systemd.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sd_notify'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
class Systemd
|
7
|
+
def initialize(log_writer, events)
|
8
|
+
@log_writer = log_writer
|
9
|
+
@events = events
|
10
|
+
end
|
11
|
+
|
12
|
+
def hook_events
|
13
|
+
@events.on_booted { SdNotify.ready }
|
14
|
+
@events.on_stopped { SdNotify.stopping }
|
15
|
+
@events.on_restart { SdNotify.reloading }
|
16
|
+
end
|
17
|
+
|
18
|
+
def start_watchdog
|
19
|
+
return unless SdNotify.watchdog?
|
20
|
+
|
21
|
+
ping_f = watchdog_sleep_time
|
22
|
+
|
23
|
+
log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
|
24
|
+
Thread.new do
|
25
|
+
loop do
|
26
|
+
sleep ping_f
|
27
|
+
SdNotify.watchdog
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def watchdog_sleep_time
|
35
|
+
usec = Integer(ENV["WATCHDOG_USEC"])
|
36
|
+
|
37
|
+
sec_f = usec / 1_000_000.0
|
38
|
+
# "It is recommended that a daemon sends a keep-alive notification message
|
39
|
+
# to the service manager every half of the time returned here."
|
40
|
+
sec_f / 2
|
41
|
+
end
|
42
|
+
|
43
|
+
def log(str)
|
44
|
+
@log_writer.log(str)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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,19 @@ 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])
|
44
47
|
@block = block
|
45
|
-
@
|
48
|
+
@out_of_band = options[:out_of_band]
|
49
|
+
@clean_thread_locals = options[:clean_thread_locals]
|
50
|
+
@reaping_time = options[:reaping_time]
|
51
|
+
@auto_trim_time = options[:auto_trim_time]
|
46
52
|
|
47
53
|
@shutdown = false
|
48
54
|
|
49
55
|
@trim_requested = 0
|
56
|
+
@out_of_band_pending = false
|
50
57
|
|
51
58
|
@workers = []
|
52
59
|
|
@@ -54,17 +61,20 @@ module Puma
|
|
54
61
|
@reaper = nil
|
55
62
|
|
56
63
|
@mutex.synchronize do
|
57
|
-
@min.times
|
64
|
+
@min.times do
|
65
|
+
spawn_thread
|
66
|
+
@not_full.wait(@mutex)
|
67
|
+
end
|
58
68
|
end
|
59
69
|
|
60
|
-
@
|
70
|
+
@force_shutdown = false
|
71
|
+
@shutdown_mutex = Mutex.new
|
61
72
|
end
|
62
73
|
|
63
74
|
attr_reader :spawned, :trim_requested, :waiting
|
64
|
-
attr_accessor :clean_thread_locals
|
65
75
|
|
66
76
|
def self.clean_thread_locals
|
67
|
-
Thread.current.keys.each do |key| # rubocop: disable
|
77
|
+
Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
|
68
78
|
Thread.current[key] = nil unless key == :__recursive_key__
|
69
79
|
end
|
70
80
|
end
|
@@ -72,13 +82,20 @@ module Puma
|
|
72
82
|
# How many objects have yet to be processed by the pool?
|
73
83
|
#
|
74
84
|
def backlog
|
75
|
-
|
85
|
+
with_mutex { @todo.size }
|
76
86
|
end
|
77
87
|
|
88
|
+
# @!attribute [r] pool_capacity
|
78
89
|
def pool_capacity
|
79
90
|
waiting + (@max - spawned)
|
80
91
|
end
|
81
92
|
|
93
|
+
# @!attribute [r] busy_threads
|
94
|
+
# @version 5.0.0
|
95
|
+
def busy_threads
|
96
|
+
with_mutex { @spawned - @waiting + @todo.size }
|
97
|
+
end
|
98
|
+
|
82
99
|
# :nodoc:
|
83
100
|
#
|
84
101
|
# Must be called with @mutex held!
|
@@ -87,60 +104,51 @@ module Puma
|
|
87
104
|
@spawned += 1
|
88
105
|
|
89
106
|
th = Thread.new(@spawned) do |spawned|
|
90
|
-
Puma.set_thread_name '
|
107
|
+
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
91
108
|
todo = @todo
|
92
109
|
block = @block
|
93
110
|
mutex = @mutex
|
94
111
|
not_empty = @not_empty
|
95
112
|
not_full = @not_full
|
96
113
|
|
97
|
-
extra = @extra.map { |i| i.new }
|
98
|
-
|
99
114
|
while true
|
100
115
|
work = nil
|
101
116
|
|
102
|
-
continue = true
|
103
|
-
|
104
117
|
mutex.synchronize do
|
105
118
|
while todo.empty?
|
106
119
|
if @trim_requested > 0
|
107
120
|
@trim_requested -= 1
|
108
|
-
|
121
|
+
@spawned -= 1
|
122
|
+
@workers.delete th
|
109
123
|
not_full.signal
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
if @shutdown
|
114
|
-
continue = false
|
115
|
-
break
|
124
|
+
Thread.exit
|
116
125
|
end
|
117
126
|
|
118
127
|
@waiting += 1
|
128
|
+
if @out_of_band_pending && trigger_out_of_band_hook
|
129
|
+
@out_of_band_pending = false
|
130
|
+
end
|
119
131
|
not_full.signal
|
120
|
-
|
121
|
-
|
132
|
+
begin
|
133
|
+
not_empty.wait mutex
|
134
|
+
ensure
|
135
|
+
@waiting -= 1
|
136
|
+
end
|
122
137
|
end
|
123
138
|
|
124
|
-
work = todo.shift
|
139
|
+
work = todo.shift
|
125
140
|
end
|
126
141
|
|
127
|
-
break unless continue
|
128
|
-
|
129
142
|
if @clean_thread_locals
|
130
143
|
ThreadPool.clean_thread_locals
|
131
144
|
end
|
132
145
|
|
133
146
|
begin
|
134
|
-
block.call(work
|
147
|
+
@out_of_band_pending = true if block.call(work)
|
135
148
|
rescue Exception => e
|
136
149
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
137
150
|
end
|
138
151
|
end
|
139
|
-
|
140
|
-
mutex.synchronize do
|
141
|
-
@spawned -= 1
|
142
|
-
@workers.delete th
|
143
|
-
end
|
144
152
|
end
|
145
153
|
|
146
154
|
@workers << th
|
@@ -150,9 +158,32 @@ module Puma
|
|
150
158
|
|
151
159
|
private :spawn_thread
|
152
160
|
|
161
|
+
# @version 5.0.0
|
162
|
+
def trigger_out_of_band_hook
|
163
|
+
return false unless @out_of_band&.any?
|
164
|
+
|
165
|
+
# we execute on idle hook when all threads are free
|
166
|
+
return false unless @spawned == @waiting
|
167
|
+
|
168
|
+
@out_of_band.each(&:call)
|
169
|
+
true
|
170
|
+
rescue Exception => e
|
171
|
+
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
private :trigger_out_of_band_hook
|
176
|
+
|
177
|
+
# @version 5.0.0
|
178
|
+
def with_mutex(&block)
|
179
|
+
@mutex.owned? ?
|
180
|
+
yield :
|
181
|
+
@mutex.synchronize(&block)
|
182
|
+
end
|
183
|
+
|
153
184
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
154
185
|
def <<(work)
|
155
|
-
|
186
|
+
with_mutex do
|
156
187
|
if @shutdown
|
157
188
|
raise "Unable to add work while shutting down"
|
158
189
|
end
|
@@ -191,13 +222,10 @@ module Puma
|
|
191
222
|
# then the `@todo` array would stay the same size as the reactor works
|
192
223
|
# to try to buffer the request. In that scenario the next call to this
|
193
224
|
# method would not block and another request would be added into the reactor
|
194
|
-
# by the server. This would continue until a fully
|
225
|
+
# by the server. This would continue until a fully buffered request
|
195
226
|
# makes it through the reactor and can then be processed by the thread pool.
|
196
|
-
#
|
197
|
-
# Returns the current number of busy threads, or +nil+ if shutting down.
|
198
|
-
#
|
199
227
|
def wait_until_not_full
|
200
|
-
|
228
|
+
with_mutex do
|
201
229
|
while true
|
202
230
|
return if @shutdown
|
203
231
|
|
@@ -205,21 +233,42 @@ module Puma
|
|
205
233
|
# is work queued that cannot be handled by waiting
|
206
234
|
# threads, then accept more work until we would
|
207
235
|
# spin up the max number of threads.
|
208
|
-
|
209
|
-
return busy_threads if @max > busy_threads
|
236
|
+
return if busy_threads < @max
|
210
237
|
|
211
238
|
@not_full.wait @mutex
|
212
239
|
end
|
213
240
|
end
|
214
241
|
end
|
215
242
|
|
216
|
-
#
|
243
|
+
# @version 5.0.0
|
244
|
+
def wait_for_less_busy_worker(delay_s)
|
245
|
+
return unless delay_s && delay_s > 0
|
246
|
+
|
247
|
+
# Ruby MRI does GVL, this can result
|
248
|
+
# in processing contention when multiple threads
|
249
|
+
# (requests) are running concurrently
|
250
|
+
return unless Puma.mri?
|
251
|
+
|
252
|
+
with_mutex do
|
253
|
+
return if @shutdown
|
254
|
+
|
255
|
+
# do not delay, if we are not busy
|
256
|
+
return unless busy_threads > 0
|
257
|
+
|
258
|
+
# this will be signaled once a request finishes,
|
259
|
+
# which can happen earlier than delay
|
260
|
+
@not_full.wait @mutex, delay_s
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# If there are any free threads in the pool, tell one to go ahead
|
217
265
|
# and exit. If +force+ is true, then a trim request is requested
|
218
266
|
# even if all threads are being utilized.
|
219
267
|
#
|
220
268
|
def trim(force=false)
|
221
|
-
|
222
|
-
|
269
|
+
with_mutex do
|
270
|
+
free = @waiting - @todo.size
|
271
|
+
if (force or free > 0) and @spawned - @trim_requested > @min
|
223
272
|
@trim_requested += 1
|
224
273
|
@not_empty.signal
|
225
274
|
end
|
@@ -229,7 +278,7 @@ module Puma
|
|
229
278
|
# If there are dead threads in the pool make them go away while decreasing
|
230
279
|
# spawned counter so that new healthy threads could be created again.
|
231
280
|
def reap
|
232
|
-
|
281
|
+
with_mutex do
|
233
282
|
dead_workers = @workers.reject(&:alive?)
|
234
283
|
|
235
284
|
dead_workers.each do |worker|
|
@@ -270,26 +319,43 @@ module Puma
|
|
270
319
|
end
|
271
320
|
end
|
272
321
|
|
273
|
-
def auto_trim!(timeout
|
274
|
-
@auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
|
322
|
+
def auto_trim!(timeout=@auto_trim_time)
|
323
|
+
@auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
|
275
324
|
@auto_trim.start!
|
276
325
|
end
|
277
326
|
|
278
|
-
def auto_reap!(timeout
|
279
|
-
@reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
|
327
|
+
def auto_reap!(timeout=@reaping_time)
|
328
|
+
@reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
|
280
329
|
@reaper.start!
|
281
330
|
end
|
282
331
|
|
332
|
+
# Allows ThreadPool::ForceShutdown to be raised within the
|
333
|
+
# provided block if the thread is forced to shutdown during execution.
|
334
|
+
def with_force_shutdown
|
335
|
+
t = Thread.current
|
336
|
+
@shutdown_mutex.synchronize do
|
337
|
+
raise ForceShutdown if @force_shutdown
|
338
|
+
t[:with_force_shutdown] = true
|
339
|
+
end
|
340
|
+
yield
|
341
|
+
ensure
|
342
|
+
t[:with_force_shutdown] = false
|
343
|
+
end
|
344
|
+
|
283
345
|
# Tell all threads in the pool to exit and wait for them to finish.
|
346
|
+
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
347
|
+
# Next, wait an extra +grace+ seconds then force-kill remaining threads.
|
348
|
+
# Finally, wait +kill_grace+ seconds for remaining threads to exit.
|
284
349
|
#
|
285
350
|
def shutdown(timeout=-1)
|
286
|
-
threads =
|
351
|
+
threads = with_mutex do
|
287
352
|
@shutdown = true
|
353
|
+
@trim_requested = @spawned
|
288
354
|
@not_empty.broadcast
|
289
355
|
@not_full.broadcast
|
290
356
|
|
291
|
-
@auto_trim
|
292
|
-
@reaper
|
357
|
+
@auto_trim&.stop
|
358
|
+
@reaper&.stop
|
293
359
|
# dup workers so that we join them all safely
|
294
360
|
@workers.dup
|
295
361
|
end
|
@@ -298,27 +364,29 @@ module Puma
|
|
298
364
|
# Wait for threads to finish without force shutdown.
|
299
365
|
threads.each(&:join)
|
300
366
|
else
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
t.join
|
306
|
-
end
|
307
|
-
|
308
|
-
if threads.empty?
|
309
|
-
break
|
310
|
-
else
|
311
|
-
sleep 1
|
367
|
+
join = ->(inner_timeout) do
|
368
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
369
|
+
threads.reject! do |t|
|
370
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
371
|
+
t.join inner_timeout - elapsed
|
312
372
|
end
|
313
373
|
end
|
314
374
|
|
315
|
-
threads
|
316
|
-
|
317
|
-
end
|
375
|
+
# Wait +timeout+ seconds for threads to finish.
|
376
|
+
join.call(timeout)
|
318
377
|
|
319
|
-
threads
|
320
|
-
|
378
|
+
# If threads are still running, raise ForceShutdown and wait to finish.
|
379
|
+
@shutdown_mutex.synchronize do
|
380
|
+
@force_shutdown = true
|
381
|
+
threads.each do |t|
|
382
|
+
t.raise ForceShutdown if t[:with_force_shutdown]
|
383
|
+
end
|
321
384
|
end
|
385
|
+
join.call(SHUTDOWN_GRACE_TIME)
|
386
|
+
|
387
|
+
# If threads are _still_ running, forcefully kill them and wait to finish.
|
388
|
+
threads.each(&:kill)
|
389
|
+
join.call(1)
|
322
390
|
end
|
323
391
|
|
324
392
|
@spawned = 0
|
data/lib/puma/util.rb
CHANGED
@@ -10,18 +10,34 @@ module Puma
|
|
10
10
|
IO.pipe
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
14
|
-
#
|
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
|
15
23
|
if defined?(::Encoding)
|
24
|
+
def escape(s, encoding = Encoding::UTF_8)
|
25
|
+
URI.encode_www_form_component(s, encoding)
|
26
|
+
end
|
27
|
+
|
16
28
|
def unescape(s, encoding = Encoding::UTF_8)
|
17
29
|
URI.decode_www_form_component(s, encoding)
|
18
30
|
end
|
19
31
|
else
|
32
|
+
def escape(s, encoding = nil)
|
33
|
+
URI.encode_www_form_component(s, encoding)
|
34
|
+
end
|
35
|
+
|
20
36
|
def unescape(s, encoding = nil)
|
21
37
|
URI.decode_www_form_component(s, encoding)
|
22
38
|
end
|
23
39
|
end
|
24
|
-
module_function :unescape
|
40
|
+
module_function :unescape, :escape
|
25
41
|
|
26
42
|
DEFAULT_SEP = /[&;] */n
|
27
43
|
|
@@ -50,7 +66,7 @@ module Puma
|
|
50
66
|
end
|
51
67
|
end
|
52
68
|
|
53
|
-
|
69
|
+
params
|
54
70
|
end
|
55
71
|
|
56
72
|
# A case-insensitive Hash that preserves the original case of a
|
@@ -72,6 +88,7 @@ module Puma
|
|
72
88
|
end
|
73
89
|
end
|
74
90
|
|
91
|
+
# @!attribute [r] to_hash
|
75
92
|
def to_hash
|
76
93
|
hash = {}
|
77
94
|
each { |k,v| hash[k] = v }
|