puma 3.12.0 → 5.3.1
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 +1413 -439
- data/LICENSE +23 -20
- data/README.md +131 -60
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +24 -19
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +38 -13
- data/docs/fork_worker.md +33 -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 +1 -1
- data/docs/plugins.md +20 -10
- data/docs/rails_dev_mode.md +29 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +7 -6
- data/docs/stats.md +142 -0
- data/docs/systemd.md +48 -70
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +27 -0
- data/ext/puma_http11/http11_parser.c +84 -109
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +262 -87
- 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 +89 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
- data/ext/puma_http11/puma_http11.c +34 -50
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +197 -144
- data/lib/puma/cli.rb +17 -15
- data/lib/puma/client.rb +257 -226
- data/lib/puma/cluster/worker.rb +176 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/cluster.rb +223 -212
- data/lib/puma/commonlogger.rb +4 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +41 -19
- data/lib/puma/control_cli.rb +117 -73
- data/lib/puma/detect.rb +26 -3
- data/lib/puma/dsl.rb +531 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +57 -31
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +182 -70
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/minissl.rb +149 -48
- data/lib/puma/null_io.rb +15 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +8 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +4 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +87 -316
- data/lib/puma/request.rb +456 -0
- data/lib/puma/runner.rb +33 -52
- data/lib/puma/server.rb +288 -679
- data/lib/puma/single.rb +13 -67
- data/lib/puma/state_file.rb +10 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +131 -81
- data/lib/puma/util.rb +14 -6
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +8 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -29
- 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 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -39
- 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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
2
4
|
require 'puma/detect'
|
3
5
|
require 'puma/plugin'
|
@@ -11,12 +13,11 @@ module Puma
|
|
11
13
|
# gets created via the `start_server` method from the `Puma::Runner` class
|
12
14
|
# that this inherits from.
|
13
15
|
class Single < Runner
|
16
|
+
# @!attribute [r] stats
|
14
17
|
def stats
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
m = @server.max_threads || 0
|
19
|
-
%Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
|
18
|
+
{
|
19
|
+
started_at: @started_at.utc.iso8601
|
20
|
+
}.merge(@server.stats)
|
20
21
|
end
|
21
22
|
|
22
23
|
def restart
|
@@ -24,7 +25,7 @@ module Puma
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def stop
|
27
|
-
@server.stop
|
28
|
+
@server.stop(false) if @server
|
28
29
|
end
|
29
30
|
|
30
31
|
def halt
|
@@ -34,67 +35,13 @@ module Puma
|
|
34
35
|
def stop_blocked
|
35
36
|
log "- Gracefully stopping, waiting for requests to finish"
|
36
37
|
@control.stop(true) if @control
|
37
|
-
@server.stop(true)
|
38
|
-
end
|
39
|
-
|
40
|
-
def jruby_daemon?
|
41
|
-
daemon? and Puma.jruby?
|
42
|
-
end
|
43
|
-
|
44
|
-
def jruby_daemon_start
|
45
|
-
require 'puma/jruby_restart'
|
46
|
-
JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
|
38
|
+
@server.stop(true) if @server
|
47
39
|
end
|
48
40
|
|
49
41
|
def run
|
50
|
-
already_daemon = false
|
51
|
-
|
52
|
-
if jruby_daemon?
|
53
|
-
require 'puma/jruby_restart'
|
54
|
-
|
55
|
-
if JRubyRestart.daemon?
|
56
|
-
# load and bind before redirecting IO so errors show up on stdout/stderr
|
57
|
-
load_and_bind
|
58
|
-
redirect_io
|
59
|
-
end
|
60
|
-
|
61
|
-
already_daemon = JRubyRestart.daemon_init
|
62
|
-
end
|
63
|
-
|
64
42
|
output_header "single"
|
65
43
|
|
66
|
-
|
67
|
-
if already_daemon
|
68
|
-
JRubyRestart.perm_daemonize
|
69
|
-
else
|
70
|
-
pid = nil
|
71
|
-
|
72
|
-
Signal.trap "SIGUSR2" do
|
73
|
-
log "* Started new process #{pid} as daemon..."
|
74
|
-
|
75
|
-
# Must use exit! so we don't unwind and run the ensures
|
76
|
-
# that will be run by the new child (such as deleting the
|
77
|
-
# pidfile)
|
78
|
-
exit!(true)
|
79
|
-
end
|
80
|
-
|
81
|
-
Signal.trap "SIGCHLD" do
|
82
|
-
log "! Error starting new process as daemon, exiting"
|
83
|
-
exit 1
|
84
|
-
end
|
85
|
-
|
86
|
-
jruby_daemon_start
|
87
|
-
sleep
|
88
|
-
end
|
89
|
-
else
|
90
|
-
if daemon?
|
91
|
-
log "* Daemonizing..."
|
92
|
-
Process.daemon(true)
|
93
|
-
redirect_io
|
94
|
-
end
|
95
|
-
|
96
|
-
load_and_bind
|
97
|
-
end
|
44
|
+
load_and_bind
|
98
45
|
|
99
46
|
Plugins.fire_background
|
100
47
|
|
@@ -103,16 +50,15 @@ module Puma
|
|
103
50
|
start_control
|
104
51
|
|
105
52
|
@server = server = start_server
|
53
|
+
server_thread = server.run
|
106
54
|
|
107
|
-
|
108
|
-
|
109
|
-
redirect_io
|
110
|
-
end
|
55
|
+
log "Use Ctrl-C to stop"
|
56
|
+
redirect_io
|
111
57
|
|
112
58
|
@launcher.events.fire_on_booted!
|
113
59
|
|
114
60
|
begin
|
115
|
-
|
61
|
+
server_thread.join
|
116
62
|
rescue Interrupt
|
117
63
|
# Swallow it
|
118
64
|
end
|
data/lib/puma/state_file.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
4
|
|
3
5
|
module Puma
|
@@ -6,15 +8,20 @@ module Puma
|
|
6
8
|
@options = {}
|
7
9
|
end
|
8
10
|
|
9
|
-
def save(path)
|
10
|
-
|
11
|
+
def save(path, permission = nil)
|
12
|
+
contents =YAML.dump @options
|
13
|
+
if permission
|
14
|
+
File.write path, contents, mode: 'wb:UTF-8'
|
15
|
+
else
|
16
|
+
File.write path, contents, mode: 'wb:UTF-8', perm: permission
|
17
|
+
end
|
11
18
|
end
|
12
19
|
|
13
20
|
def load(path)
|
14
21
|
@options = YAML.load File.read(path)
|
15
22
|
end
|
16
23
|
|
17
|
-
FIELDS = %w!control_url control_auth_token pid!
|
24
|
+
FIELDS = %w!control_url control_auth_token pid running_from!
|
18
25
|
|
19
26
|
FIELDS.each do |f|
|
20
27
|
define_method f do
|
data/lib/puma/systemd.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sd_notify'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
class Systemd
|
7
|
+
def initialize(events)
|
8
|
+
@events = events
|
9
|
+
end
|
10
|
+
|
11
|
+
def hook_events
|
12
|
+
@events.on_booted { SdNotify.ready }
|
13
|
+
@events.on_stopped { SdNotify.stopping }
|
14
|
+
@events.on_restart { SdNotify.reloading }
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_watchdog
|
18
|
+
return unless SdNotify.watchdog?
|
19
|
+
|
20
|
+
ping_f = watchdog_sleep_time
|
21
|
+
|
22
|
+
log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
|
23
|
+
Thread.new do
|
24
|
+
loop do
|
25
|
+
sleep ping_f
|
26
|
+
SdNotify.watchdog
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def watchdog_sleep_time
|
34
|
+
usec = Integer(ENV["WATCHDOG_USEC"])
|
35
|
+
|
36
|
+
sec_f = usec / 1_000_000.0
|
37
|
+
# "It is recommended that a daemon sends a keep-alive notification message
|
38
|
+
# to the service manager every half of the time returned here."
|
39
|
+
sec_f / 2
|
40
|
+
end
|
41
|
+
|
42
|
+
def log(str)
|
43
|
+
@events.log str
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
|
3
5
|
module Puma
|
@@ -11,7 +13,7 @@ module Puma
|
|
11
13
|
# a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
|
12
14
|
#
|
13
15
|
# Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
|
14
|
-
# and
|
16
|
+
# and processes it.
|
15
17
|
class ThreadPool
|
16
18
|
class ForceShutdown < RuntimeError
|
17
19
|
end
|
@@ -45,6 +47,7 @@ module Puma
|
|
45
47
|
@shutdown = false
|
46
48
|
|
47
49
|
@trim_requested = 0
|
50
|
+
@out_of_band_pending = false
|
48
51
|
|
49
52
|
@workers = []
|
50
53
|
|
@@ -52,14 +55,20 @@ module Puma
|
|
52
55
|
@reaper = nil
|
53
56
|
|
54
57
|
@mutex.synchronize do
|
55
|
-
@min.times
|
58
|
+
@min.times do
|
59
|
+
spawn_thread
|
60
|
+
@not_full.wait(@mutex)
|
61
|
+
end
|
56
62
|
end
|
57
63
|
|
58
64
|
@clean_thread_locals = false
|
65
|
+
@force_shutdown = false
|
66
|
+
@shutdown_mutex = Mutex.new
|
59
67
|
end
|
60
68
|
|
61
69
|
attr_reader :spawned, :trim_requested, :waiting
|
62
70
|
attr_accessor :clean_thread_locals
|
71
|
+
attr_accessor :out_of_band_hook # @version 5.0.0
|
63
72
|
|
64
73
|
def self.clean_thread_locals
|
65
74
|
Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
|
@@ -70,13 +79,20 @@ module Puma
|
|
70
79
|
# How many objects have yet to be processed by the pool?
|
71
80
|
#
|
72
81
|
def backlog
|
73
|
-
|
82
|
+
with_mutex { @todo.size }
|
74
83
|
end
|
75
84
|
|
85
|
+
# @!attribute [r] pool_capacity
|
76
86
|
def pool_capacity
|
77
87
|
waiting + (@max - spawned)
|
78
88
|
end
|
79
89
|
|
90
|
+
# @!attribute [r] busy_threads
|
91
|
+
# @version 5.0.0
|
92
|
+
def busy_threads
|
93
|
+
with_mutex { @spawned - @waiting + @todo.size }
|
94
|
+
end
|
95
|
+
|
80
96
|
# :nodoc:
|
81
97
|
#
|
82
98
|
# Must be called with @mutex held!
|
@@ -85,8 +101,7 @@ module Puma
|
|
85
101
|
@spawned += 1
|
86
102
|
|
87
103
|
th = Thread.new(@spawned) do |spawned|
|
88
|
-
|
89
|
-
Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
|
104
|
+
Puma.set_thread_name 'threadpool %03i' % spawned
|
90
105
|
todo = @todo
|
91
106
|
block = @block
|
92
107
|
mutex = @mutex
|
@@ -98,48 +113,40 @@ module Puma
|
|
98
113
|
while true
|
99
114
|
work = nil
|
100
115
|
|
101
|
-
continue = true
|
102
|
-
|
103
116
|
mutex.synchronize do
|
104
117
|
while todo.empty?
|
105
118
|
if @trim_requested > 0
|
106
119
|
@trim_requested -= 1
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
if @shutdown
|
113
|
-
continue = false
|
114
|
-
break
|
120
|
+
@spawned -= 1
|
121
|
+
@workers.delete th
|
122
|
+
Thread.exit
|
115
123
|
end
|
116
124
|
|
117
125
|
@waiting += 1
|
126
|
+
if @out_of_band_pending && trigger_out_of_band_hook
|
127
|
+
@out_of_band_pending = false
|
128
|
+
end
|
118
129
|
not_full.signal
|
119
|
-
|
120
|
-
|
130
|
+
begin
|
131
|
+
not_empty.wait mutex
|
132
|
+
ensure
|
133
|
+
@waiting -= 1
|
134
|
+
end
|
121
135
|
end
|
122
136
|
|
123
|
-
work = todo.shift
|
137
|
+
work = todo.shift
|
124
138
|
end
|
125
139
|
|
126
|
-
break unless continue
|
127
|
-
|
128
140
|
if @clean_thread_locals
|
129
141
|
ThreadPool.clean_thread_locals
|
130
142
|
end
|
131
143
|
|
132
144
|
begin
|
133
|
-
block.call(work, *extra)
|
145
|
+
@out_of_band_pending = true if block.call(work, *extra)
|
134
146
|
rescue Exception => e
|
135
147
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
136
148
|
end
|
137
149
|
end
|
138
|
-
|
139
|
-
mutex.synchronize do
|
140
|
-
@spawned -= 1
|
141
|
-
@workers.delete th
|
142
|
-
end
|
143
150
|
end
|
144
151
|
|
145
152
|
@workers << th
|
@@ -149,9 +156,32 @@ module Puma
|
|
149
156
|
|
150
157
|
private :spawn_thread
|
151
158
|
|
159
|
+
# @version 5.0.0
|
160
|
+
def trigger_out_of_band_hook
|
161
|
+
return false unless out_of_band_hook && out_of_band_hook.any?
|
162
|
+
|
163
|
+
# we execute on idle hook when all threads are free
|
164
|
+
return false unless @spawned == @waiting
|
165
|
+
|
166
|
+
out_of_band_hook.each(&:call)
|
167
|
+
true
|
168
|
+
rescue Exception => e
|
169
|
+
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
170
|
+
true
|
171
|
+
end
|
172
|
+
|
173
|
+
private :trigger_out_of_band_hook
|
174
|
+
|
175
|
+
# @version 5.0.0
|
176
|
+
def with_mutex(&block)
|
177
|
+
@mutex.owned? ?
|
178
|
+
yield :
|
179
|
+
@mutex.synchronize(&block)
|
180
|
+
end
|
181
|
+
|
152
182
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
153
183
|
def <<(work)
|
154
|
-
|
184
|
+
with_mutex do
|
155
185
|
if @shutdown
|
156
186
|
raise "Unable to add work while shutting down"
|
157
187
|
end
|
@@ -188,12 +218,12 @@ module Puma
|
|
188
218
|
# request, it might not be added to the `@todo` array right away.
|
189
219
|
# For example if a slow client has only sent a header, but not a body
|
190
220
|
# then the `@todo` array would stay the same size as the reactor works
|
191
|
-
# to try to buffer the request. In
|
221
|
+
# to try to buffer the request. In that scenario the next call to this
|
192
222
|
# method would not block and another request would be added into the reactor
|
193
|
-
# by the server. This would continue until a fully
|
223
|
+
# by the server. This would continue until a fully buffered request
|
194
224
|
# makes it through the reactor and can then be processed by the thread pool.
|
195
225
|
def wait_until_not_full
|
196
|
-
|
226
|
+
with_mutex do
|
197
227
|
while true
|
198
228
|
return if @shutdown
|
199
229
|
|
@@ -201,20 +231,42 @@ module Puma
|
|
201
231
|
# is work queued that cannot be handled by waiting
|
202
232
|
# threads, then accept more work until we would
|
203
233
|
# spin up the max number of threads.
|
204
|
-
return if
|
234
|
+
return if busy_threads < @max
|
205
235
|
|
206
236
|
@not_full.wait @mutex
|
207
237
|
end
|
208
238
|
end
|
209
239
|
end
|
210
240
|
|
211
|
-
#
|
241
|
+
# @version 5.0.0
|
242
|
+
def wait_for_less_busy_worker(delay_s)
|
243
|
+
return unless delay_s && delay_s > 0
|
244
|
+
|
245
|
+
# Ruby MRI does GVL, this can result
|
246
|
+
# in processing contention when multiple threads
|
247
|
+
# (requests) are running concurrently
|
248
|
+
return unless Puma.mri?
|
249
|
+
|
250
|
+
with_mutex do
|
251
|
+
return if @shutdown
|
252
|
+
|
253
|
+
# do not delay, if we are not busy
|
254
|
+
return unless busy_threads > 0
|
255
|
+
|
256
|
+
# this will be signaled once a request finishes,
|
257
|
+
# which can happen earlier than delay
|
258
|
+
@not_full.wait @mutex, delay_s
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# If there are any free threads in the pool, tell one to go ahead
|
212
263
|
# and exit. If +force+ is true, then a trim request is requested
|
213
264
|
# even if all threads are being utilized.
|
214
265
|
#
|
215
266
|
def trim(force=false)
|
216
|
-
|
217
|
-
|
267
|
+
with_mutex do
|
268
|
+
free = @waiting - @todo.size
|
269
|
+
if (force or free > 0) and @spawned - @trim_requested > @min
|
218
270
|
@trim_requested += 1
|
219
271
|
@not_empty.signal
|
220
272
|
end
|
@@ -224,7 +276,7 @@ module Puma
|
|
224
276
|
# If there are dead threads in the pool make them go away while decreasing
|
225
277
|
# spawned counter so that new healthy threads could be created again.
|
226
278
|
def reap
|
227
|
-
|
279
|
+
with_mutex do
|
228
280
|
dead_workers = @workers.reject(&:alive?)
|
229
281
|
|
230
282
|
dead_workers.each do |worker|
|
@@ -238,10 +290,12 @@ module Puma
|
|
238
290
|
end
|
239
291
|
end
|
240
292
|
|
241
|
-
class
|
242
|
-
def initialize(pool, timeout)
|
293
|
+
class Automaton
|
294
|
+
def initialize(pool, timeout, thread_name, message)
|
243
295
|
@pool = pool
|
244
296
|
@timeout = timeout
|
297
|
+
@thread_name = thread_name
|
298
|
+
@message = message
|
245
299
|
@running = false
|
246
300
|
end
|
247
301
|
|
@@ -249,8 +303,9 @@ module Puma
|
|
249
303
|
@running = true
|
250
304
|
|
251
305
|
@thread = Thread.new do
|
306
|
+
Puma.set_thread_name @thread_name
|
252
307
|
while @running
|
253
|
-
@pool.
|
308
|
+
@pool.public_send(@message)
|
254
309
|
sleep @timeout
|
255
310
|
end
|
256
311
|
end
|
@@ -263,44 +318,37 @@ module Puma
|
|
263
318
|
end
|
264
319
|
|
265
320
|
def auto_trim!(timeout=30)
|
266
|
-
@auto_trim =
|
321
|
+
@auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
|
267
322
|
@auto_trim.start!
|
268
323
|
end
|
269
324
|
|
270
|
-
class Reaper
|
271
|
-
def initialize(pool, timeout)
|
272
|
-
@pool = pool
|
273
|
-
@timeout = timeout
|
274
|
-
@running = false
|
275
|
-
end
|
276
|
-
|
277
|
-
def start!
|
278
|
-
@running = true
|
279
|
-
|
280
|
-
@thread = Thread.new do
|
281
|
-
while @running
|
282
|
-
@pool.reap
|
283
|
-
sleep @timeout
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
def stop
|
289
|
-
@running = false
|
290
|
-
@thread.wakeup
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
325
|
def auto_reap!(timeout=5)
|
295
|
-
@reaper =
|
326
|
+
@reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
|
296
327
|
@reaper.start!
|
297
328
|
end
|
298
329
|
|
330
|
+
# Allows ThreadPool::ForceShutdown to be raised within the
|
331
|
+
# provided block if the thread is forced to shutdown during execution.
|
332
|
+
def with_force_shutdown
|
333
|
+
t = Thread.current
|
334
|
+
@shutdown_mutex.synchronize do
|
335
|
+
raise ForceShutdown if @force_shutdown
|
336
|
+
t[:with_force_shutdown] = true
|
337
|
+
end
|
338
|
+
yield
|
339
|
+
ensure
|
340
|
+
t[:with_force_shutdown] = false
|
341
|
+
end
|
342
|
+
|
299
343
|
# Tell all threads in the pool to exit and wait for them to finish.
|
344
|
+
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
345
|
+
# Next, wait an extra +grace+ seconds then force-kill remaining threads.
|
346
|
+
# Finally, wait +kill_grace+ seconds for remaining threads to exit.
|
300
347
|
#
|
301
348
|
def shutdown(timeout=-1)
|
302
|
-
threads =
|
349
|
+
threads = with_mutex do
|
303
350
|
@shutdown = true
|
351
|
+
@trim_requested = @spawned
|
304
352
|
@not_empty.broadcast
|
305
353
|
@not_full.broadcast
|
306
354
|
|
@@ -314,27 +362,29 @@ module Puma
|
|
314
362
|
# Wait for threads to finish without force shutdown.
|
315
363
|
threads.each(&:join)
|
316
364
|
else
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
t.join
|
322
|
-
end
|
323
|
-
|
324
|
-
if threads.empty?
|
325
|
-
break
|
326
|
-
else
|
327
|
-
sleep 1
|
365
|
+
join = ->(inner_timeout) do
|
366
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
367
|
+
threads.reject! do |t|
|
368
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
369
|
+
t.join inner_timeout - elapsed
|
328
370
|
end
|
329
371
|
end
|
330
372
|
|
331
|
-
threads
|
332
|
-
|
333
|
-
end
|
373
|
+
# Wait +timeout+ seconds for threads to finish.
|
374
|
+
join.call(timeout)
|
334
375
|
|
335
|
-
threads
|
336
|
-
|
376
|
+
# If threads are still running, raise ForceShutdown and wait to finish.
|
377
|
+
@shutdown_mutex.synchronize do
|
378
|
+
@force_shutdown = true
|
379
|
+
threads.each do |t|
|
380
|
+
t.raise ForceShutdown if t[:with_force_shutdown]
|
381
|
+
end
|
337
382
|
end
|
383
|
+
join.call(SHUTDOWN_GRACE_TIME)
|
384
|
+
|
385
|
+
# If threads are _still_ running, forcefully kill them and wait to finish.
|
386
|
+
threads.each(&:kill)
|
387
|
+
join.call(1)
|
338
388
|
end
|
339
389
|
|
340
390
|
@spawned = 0
|
data/lib/puma/util.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'puma/rack/backports/uri/common_193'
|
5
|
-
else
|
6
|
-
require 'uri/common'
|
7
|
-
end
|
3
|
+
require 'uri/common'
|
8
4
|
|
9
5
|
module Puma
|
10
6
|
module Util
|
@@ -27,6 +23,17 @@ module Puma
|
|
27
23
|
end
|
28
24
|
module_function :unescape
|
29
25
|
|
26
|
+
# @version 5.0.0
|
27
|
+
def nakayoshi_gc(events)
|
28
|
+
events.log "! Promoting existing objects to old generation..."
|
29
|
+
4.times { GC.start(full_mark: false) }
|
30
|
+
if GC.respond_to?(:compact)
|
31
|
+
events.log "! Compacting..."
|
32
|
+
GC.compact
|
33
|
+
end
|
34
|
+
events.log "! Friendly fork preparation complete."
|
35
|
+
end
|
36
|
+
|
30
37
|
DEFAULT_SEP = /[&;] */n
|
31
38
|
|
32
39
|
# Stolen from Mongrel, with some small modifications:
|
@@ -76,6 +83,7 @@ module Puma
|
|
76
83
|
end
|
77
84
|
end
|
78
85
|
|
86
|
+
# @!attribute [r] to_hash
|
79
87
|
def to_hash
|
80
88
|
hash = {}
|
81
89
|
each { |k,v| hash[k] = v }
|
data/lib/puma.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Standard libraries
|
2
4
|
require 'socket'
|
3
5
|
require 'tempfile'
|
@@ -8,16 +10,68 @@ require 'stringio'
|
|
8
10
|
|
9
11
|
require 'thread'
|
10
12
|
|
13
|
+
require 'puma/puma_http11'
|
14
|
+
require 'puma/detect'
|
15
|
+
require 'puma/json'
|
16
|
+
|
11
17
|
module Puma
|
12
18
|
autoload :Const, 'puma/const'
|
13
19
|
autoload :Server, 'puma/server'
|
14
20
|
autoload :Launcher, 'puma/launcher'
|
15
21
|
|
22
|
+
# at present, MiniSSL::Engine is only defined in extension code (puma_http11),
|
23
|
+
# not in minissl.rb
|
24
|
+
HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
|
25
|
+
|
26
|
+
HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
|
27
|
+
|
28
|
+
if HAS_SSL
|
29
|
+
require 'puma/minissl'
|
30
|
+
else
|
31
|
+
module MiniSSL
|
32
|
+
# this class is defined so that it exists when Puma is compiled
|
33
|
+
# without ssl support, as Server and Reactor use it in rescue statements.
|
34
|
+
class SSLError < StandardError ; end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.ssl?
|
39
|
+
HAS_SSL
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.abstract_unix_socket?
|
43
|
+
@abstract_unix ||=
|
44
|
+
if HAS_UNIX_SOCKET
|
45
|
+
begin
|
46
|
+
::UNIXServer.new("\0puma.temp.unix").close
|
47
|
+
true
|
48
|
+
rescue ArgumentError # darwin
|
49
|
+
false
|
50
|
+
end
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!attribute [rw] stats_object=
|
16
57
|
def self.stats_object=(val)
|
17
58
|
@get_stats = val
|
18
59
|
end
|
19
60
|
|
61
|
+
# @!attribute [rw] stats_object
|
20
62
|
def self.stats
|
63
|
+
Puma::JSON.generate @get_stats.stats
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!attribute [r] stats_hash
|
67
|
+
# @version 5.0.0
|
68
|
+
def self.stats_hash
|
21
69
|
@get_stats.stats
|
22
70
|
end
|
71
|
+
|
72
|
+
# Thread name is new in Ruby 2.3
|
73
|
+
def self.set_thread_name(name)
|
74
|
+
return unless Thread.current.respond_to?(:name=)
|
75
|
+
Thread.current.name = "puma #{name}"
|
76
|
+
end
|
23
77
|
end
|