puma 6.4.3 → 8.0.2
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 +4 -4
- data/History.md +448 -8
- data/README.md +110 -51
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/deployment.md +58 -23
- data/docs/fork_worker.md +11 -1
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +6 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +11 -5
- data/docs/systemd.md +14 -5
- data/ext/puma_http11/extconf.rb +20 -32
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +29 -9
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +194 -101
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/puma_http11.c +125 -118
- data/lib/puma/app/status.rb +11 -3
- data/lib/puma/binder.rb +22 -12
- data/lib/puma/cli.rb +11 -9
- data/lib/puma/client.rb +233 -136
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +24 -21
- data/lib/puma/cluster/worker_handle.rb +38 -8
- data/lib/puma/cluster.rb +74 -48
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +197 -64
- data/lib/puma/const.rb +23 -12
- data/lib/puma/control_cli.rb +11 -7
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +483 -127
- data/lib/puma/error_logger.rb +7 -5
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher/bundle_pruner.rb +3 -5
- data/lib/puma/launcher.rb +76 -59
- data/lib/puma/log_writer.rb +17 -11
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -1
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -13
- data/lib/puma/{request.rb → response.rb} +57 -209
- data/lib/puma/runner.rb +15 -17
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +200 -104
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +7 -4
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +179 -96
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +11 -8
- data/tools/Dockerfile +15 -5
- metadata +26 -16
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/thread_pool.rb
CHANGED
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
require 'thread'
|
|
4
4
|
|
|
5
5
|
require_relative 'io_buffer'
|
|
6
|
+
require_relative 'server_plugin_control'
|
|
6
7
|
|
|
7
8
|
module Puma
|
|
9
|
+
|
|
10
|
+
# Add `Thread#puma_server` and `Thread#puma_server=`
|
|
11
|
+
Thread.attr_accessor(:puma_server)
|
|
12
|
+
|
|
8
13
|
# Internal Docs for A simple thread pool management object.
|
|
9
14
|
#
|
|
10
15
|
# Each Puma "worker" has a thread pool to process requests.
|
|
@@ -20,37 +25,90 @@ module Puma
|
|
|
20
25
|
class ForceShutdown < RuntimeError
|
|
21
26
|
end
|
|
22
27
|
|
|
28
|
+
class ProcessorThread
|
|
29
|
+
attr_accessor :thread
|
|
30
|
+
attr_writer :marked_as_io_thread
|
|
31
|
+
|
|
32
|
+
def initialize(pool)
|
|
33
|
+
@pool = pool
|
|
34
|
+
@thread = nil
|
|
35
|
+
@marked_as_io_thread = false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def mark_as_io_thread!
|
|
39
|
+
unless @marked_as_io_thread
|
|
40
|
+
@marked_as_io_thread = true
|
|
41
|
+
|
|
42
|
+
# Immediately signal the pool that it can spawn a new thread
|
|
43
|
+
# if there's some work in the queue.
|
|
44
|
+
@pool.spawn_thread_if_needed
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def marked_as_io_thread?
|
|
49
|
+
@marked_as_io_thread
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def alive?
|
|
53
|
+
@thread&.alive?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def join(...)
|
|
57
|
+
@thread.join(...)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def kill(...)
|
|
61
|
+
@thread.kill(...)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def [](key)
|
|
65
|
+
@thread[key]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def raise(...)
|
|
69
|
+
@thread.raise(...)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
23
73
|
# How long, after raising the ForceShutdown of a thread during
|
|
24
74
|
# forced shutdown mode, to wait for the thread to try and finish
|
|
25
75
|
# up its work before leaving the thread to die on the vine.
|
|
26
76
|
SHUTDOWN_GRACE_TIME = 5 # seconds
|
|
27
77
|
|
|
78
|
+
attr_reader :out_of_band_running
|
|
79
|
+
|
|
28
80
|
# Maintain a minimum of +min+ and maximum of +max+ threads
|
|
29
81
|
# in the pool.
|
|
30
82
|
#
|
|
31
83
|
# The block passed is the work that will be performed in each
|
|
32
84
|
# thread.
|
|
33
85
|
#
|
|
34
|
-
def initialize(name, options = {}, &block)
|
|
86
|
+
def initialize(name, options = {}, server: nil, &block)
|
|
87
|
+
@server = server
|
|
88
|
+
|
|
35
89
|
@not_empty = ConditionVariable.new
|
|
36
90
|
@not_full = ConditionVariable.new
|
|
37
91
|
@mutex = Mutex.new
|
|
92
|
+
@todo = Queue.new
|
|
38
93
|
|
|
39
|
-
@
|
|
40
|
-
|
|
94
|
+
@backlog_max = 0
|
|
41
95
|
@spawned = 0
|
|
42
96
|
@waiting = 0
|
|
43
97
|
|
|
44
98
|
@name = name
|
|
45
99
|
@min = Integer(options[:min_threads])
|
|
46
100
|
@max = Integer(options[:max_threads])
|
|
101
|
+
@max_io_threads = Integer(options[:max_io_threads] || 0)
|
|
102
|
+
|
|
47
103
|
# Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
|
|
48
104
|
# to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
|
|
49
105
|
# makes stubbing constants difficult.
|
|
50
106
|
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
|
107
|
+
@shutdown_debug = options[:shutdown_debug]
|
|
51
108
|
@block = block
|
|
52
109
|
@out_of_band = options[:out_of_band]
|
|
53
|
-
@
|
|
110
|
+
@out_of_band_running = false
|
|
111
|
+
@out_of_band_condvar = ConditionVariable.new
|
|
54
112
|
@before_thread_start = options[:before_thread_start]
|
|
55
113
|
@before_thread_exit = options[:before_thread_exit]
|
|
56
114
|
@reaping_time = options[:reaping_time]
|
|
@@ -61,7 +119,7 @@ module Puma
|
|
|
61
119
|
@trim_requested = 0
|
|
62
120
|
@out_of_band_pending = false
|
|
63
121
|
|
|
64
|
-
@
|
|
122
|
+
@processors = []
|
|
65
123
|
|
|
66
124
|
@auto_trim = nil
|
|
67
125
|
@reaper = nil
|
|
@@ -78,22 +136,43 @@ module Puma
|
|
|
78
136
|
end
|
|
79
137
|
|
|
80
138
|
attr_reader :spawned, :trim_requested, :waiting
|
|
139
|
+
attr_accessor :min, :max
|
|
81
140
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
141
|
+
# generate stats hash so as not to perform multiple locks
|
|
142
|
+
# @return [Hash] hash containing stat info from ThreadPool
|
|
143
|
+
def stats
|
|
144
|
+
with_mutex do
|
|
145
|
+
temp = @backlog_max
|
|
146
|
+
@backlog_max = 0
|
|
147
|
+
{ backlog: @todo.size,
|
|
148
|
+
running: @spawned,
|
|
149
|
+
pool_capacity: pool_capacity,
|
|
150
|
+
busy_threads: @spawned - @waiting + @todo.size,
|
|
151
|
+
io_threads: @processors.count(&:marked_as_io_thread?),
|
|
152
|
+
backlog_max: temp
|
|
153
|
+
}
|
|
85
154
|
end
|
|
86
155
|
end
|
|
87
156
|
|
|
157
|
+
def reset_max
|
|
158
|
+
with_mutex { @backlog_max = 0 }
|
|
159
|
+
end
|
|
160
|
+
|
|
88
161
|
# How many objects have yet to be processed by the pool?
|
|
89
162
|
#
|
|
90
163
|
def backlog
|
|
91
164
|
with_mutex { @todo.size }
|
|
92
165
|
end
|
|
93
166
|
|
|
167
|
+
# The maximum size of the backlog
|
|
168
|
+
#
|
|
169
|
+
def backlog_max
|
|
170
|
+
with_mutex { @backlog_max }
|
|
171
|
+
end
|
|
172
|
+
|
|
94
173
|
# @!attribute [r] pool_capacity
|
|
95
174
|
def pool_capacity
|
|
96
|
-
waiting + (@max - spawned)
|
|
175
|
+
(waiting + (@max - spawned)).clamp(0, Float::INFINITY)
|
|
97
176
|
end
|
|
98
177
|
|
|
99
178
|
# @!attribute [r] busy_threads
|
|
@@ -110,8 +189,12 @@ module Puma
|
|
|
110
189
|
@spawned += 1
|
|
111
190
|
|
|
112
191
|
trigger_before_thread_start_hooks
|
|
113
|
-
|
|
192
|
+
processor = ProcessorThread.new(self)
|
|
193
|
+
processor.thread = Thread.new(processor, @spawned) do |processor, spawned|
|
|
114
194
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
|
195
|
+
# Advertise server into the thread
|
|
196
|
+
Thread.current.puma_server = @server
|
|
197
|
+
|
|
115
198
|
todo = @todo
|
|
116
199
|
block = @block
|
|
117
200
|
mutex = @mutex
|
|
@@ -122,11 +205,23 @@ module Puma
|
|
|
122
205
|
work = nil
|
|
123
206
|
|
|
124
207
|
mutex.synchronize do
|
|
208
|
+
if processor.marked_as_io_thread?
|
|
209
|
+
if @processors.count { |t| !t.marked_as_io_thread? } < @max
|
|
210
|
+
# We're not at max processor threads, so the io thread can rejoin the normal population.
|
|
211
|
+
processor.marked_as_io_thread = false
|
|
212
|
+
else
|
|
213
|
+
# We're already at max threads, so we exit the extra io thread.
|
|
214
|
+
@processors.delete(processor)
|
|
215
|
+
trigger_before_thread_exit_hooks
|
|
216
|
+
Thread.exit
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
125
220
|
while todo.empty?
|
|
126
221
|
if @trim_requested > 0
|
|
127
222
|
@trim_requested -= 1
|
|
128
223
|
@spawned -= 1
|
|
129
|
-
@
|
|
224
|
+
@processors.delete(processor)
|
|
130
225
|
not_full.signal
|
|
131
226
|
trigger_before_thread_exit_hooks
|
|
132
227
|
Thread.exit
|
|
@@ -147,21 +242,17 @@ module Puma
|
|
|
147
242
|
work = todo.shift
|
|
148
243
|
end
|
|
149
244
|
|
|
150
|
-
if @clean_thread_locals
|
|
151
|
-
ThreadPool.clean_thread_locals
|
|
152
|
-
end
|
|
153
|
-
|
|
154
245
|
begin
|
|
155
|
-
@out_of_band_pending = true if block.call(work)
|
|
246
|
+
@out_of_band_pending = true if block.call(processor, work)
|
|
156
247
|
rescue Exception => e
|
|
157
248
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
|
158
249
|
end
|
|
159
250
|
end
|
|
160
251
|
end
|
|
161
252
|
|
|
162
|
-
@
|
|
253
|
+
@processors << processor
|
|
163
254
|
|
|
164
|
-
|
|
255
|
+
processor
|
|
165
256
|
end
|
|
166
257
|
|
|
167
258
|
private :spawn_thread
|
|
@@ -171,7 +262,7 @@ module Puma
|
|
|
171
262
|
|
|
172
263
|
@before_thread_start.each do |b|
|
|
173
264
|
begin
|
|
174
|
-
b.call
|
|
265
|
+
b[:block].call(ServerPluginControl.new(@server))
|
|
175
266
|
rescue Exception => e
|
|
176
267
|
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
|
177
268
|
end
|
|
@@ -186,7 +277,7 @@ module Puma
|
|
|
186
277
|
|
|
187
278
|
@before_thread_exit.each do |b|
|
|
188
279
|
begin
|
|
189
|
-
b.call
|
|
280
|
+
b[:block].call
|
|
190
281
|
rescue Exception => e
|
|
191
282
|
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
|
192
283
|
end
|
|
@@ -202,16 +293,27 @@ module Puma
|
|
|
202
293
|
|
|
203
294
|
# we execute on idle hook when all threads are free
|
|
204
295
|
return false unless @spawned == @waiting
|
|
205
|
-
|
|
206
|
-
@out_of_band.each
|
|
296
|
+
@out_of_band_running = true
|
|
297
|
+
@out_of_band.each { |b| b[:block].call }
|
|
207
298
|
true
|
|
208
299
|
rescue Exception => e
|
|
209
300
|
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
|
210
301
|
true
|
|
302
|
+
ensure
|
|
303
|
+
@out_of_band_running = false
|
|
304
|
+
@out_of_band_condvar.broadcast
|
|
211
305
|
end
|
|
212
306
|
|
|
213
307
|
private :trigger_out_of_band_hook
|
|
214
308
|
|
|
309
|
+
def wait_while_out_of_band_running
|
|
310
|
+
return unless @out_of_band_running
|
|
311
|
+
|
|
312
|
+
with_mutex do
|
|
313
|
+
@out_of_band_condvar.wait(@mutex) while @out_of_band_running
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
215
317
|
# @version 5.0.0
|
|
216
318
|
def with_mutex(&block)
|
|
217
319
|
@mutex.owned? ?
|
|
@@ -219,6 +321,16 @@ module Puma
|
|
|
219
321
|
@mutex.synchronize(&block)
|
|
220
322
|
end
|
|
221
323
|
|
|
324
|
+
# :nodoc:
|
|
325
|
+
#
|
|
326
|
+
# Must be called with @mutex held!
|
|
327
|
+
#
|
|
328
|
+
def can_spawn_processor?
|
|
329
|
+
io_processors_count = @processors.count(&:marked_as_io_thread?)
|
|
330
|
+
extra_io_processors_count = io_processors_count > @max_io_threads ? io_processors_count - @max_io_threads : 0
|
|
331
|
+
(@spawned - io_processors_count) < (@max - extra_io_processors_count)
|
|
332
|
+
end
|
|
333
|
+
|
|
222
334
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
|
223
335
|
def <<(work)
|
|
224
336
|
with_mutex do
|
|
@@ -227,78 +339,26 @@ module Puma
|
|
|
227
339
|
end
|
|
228
340
|
|
|
229
341
|
@todo << work
|
|
342
|
+
t = @todo.size
|
|
343
|
+
@backlog_max = t if t > @backlog_max
|
|
230
344
|
|
|
231
|
-
if @waiting < @todo.size and
|
|
345
|
+
if @waiting < @todo.size and can_spawn_processor?
|
|
232
346
|
spawn_thread
|
|
233
347
|
end
|
|
234
348
|
|
|
235
349
|
@not_empty.signal
|
|
236
350
|
end
|
|
351
|
+
self
|
|
237
352
|
end
|
|
238
353
|
|
|
239
|
-
|
|
240
|
-
# the thread pool can pull more requests from the socket and
|
|
241
|
-
# pass to the reactor.
|
|
242
|
-
#
|
|
243
|
-
# The general idea is that the thread pool can only work on a fixed
|
|
244
|
-
# number of requests at the same time. If it is already processing that
|
|
245
|
-
# number of requests then it is at capacity. If another Puma process has
|
|
246
|
-
# spare capacity, then the request can be left on the socket so the other
|
|
247
|
-
# worker can pick it up and process it.
|
|
248
|
-
#
|
|
249
|
-
# For example: if there are 5 threads, but only 4 working on
|
|
250
|
-
# requests, this method will not wait and the `Puma::Server`
|
|
251
|
-
# can pull a request right away.
|
|
252
|
-
#
|
|
253
|
-
# If there are 5 threads and all 5 of them are busy, then it will
|
|
254
|
-
# pause here, and wait until the `not_full` condition variable is
|
|
255
|
-
# signaled, usually this indicates that a request has been processed.
|
|
256
|
-
#
|
|
257
|
-
# It's important to note that even though the server might accept another
|
|
258
|
-
# request, it might not be added to the `@todo` array right away.
|
|
259
|
-
# For example if a slow client has only sent a header, but not a body
|
|
260
|
-
# then the `@todo` array would stay the same size as the reactor works
|
|
261
|
-
# to try to buffer the request. In that scenario the next call to this
|
|
262
|
-
# method would not block and another request would be added into the reactor
|
|
263
|
-
# by the server. This would continue until a fully buffered request
|
|
264
|
-
# makes it through the reactor and can then be processed by the thread pool.
|
|
265
|
-
def wait_until_not_full
|
|
354
|
+
def spawn_thread_if_needed # :nodoc:
|
|
266
355
|
with_mutex do
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# If we can still spin up new threads and there
|
|
271
|
-
# is work queued that cannot be handled by waiting
|
|
272
|
-
# threads, then accept more work until we would
|
|
273
|
-
# spin up the max number of threads.
|
|
274
|
-
return if busy_threads < @max
|
|
275
|
-
|
|
276
|
-
@not_full.wait @mutex
|
|
356
|
+
if @waiting < @todo.size and can_spawn_processor?
|
|
357
|
+
spawn_thread
|
|
277
358
|
end
|
|
278
359
|
end
|
|
279
360
|
end
|
|
280
361
|
|
|
281
|
-
# @version 5.0.0
|
|
282
|
-
def wait_for_less_busy_worker(delay_s)
|
|
283
|
-
return unless delay_s && delay_s > 0
|
|
284
|
-
|
|
285
|
-
# Ruby MRI does GVL, this can result
|
|
286
|
-
# in processing contention when multiple threads
|
|
287
|
-
# (requests) are running concurrently
|
|
288
|
-
return unless Puma.mri?
|
|
289
|
-
|
|
290
|
-
with_mutex do
|
|
291
|
-
return if @shutdown
|
|
292
|
-
|
|
293
|
-
# do not delay, if we are not busy
|
|
294
|
-
return unless busy_threads > 0
|
|
295
|
-
|
|
296
|
-
# this will be signaled once a request finishes,
|
|
297
|
-
# which can happen earlier than delay
|
|
298
|
-
@not_full.wait @mutex, delay_s
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
362
|
# If there are any free threads in the pool, tell one to go ahead
|
|
303
363
|
# and exit. If +force+ is true, then a trim request is requested
|
|
304
364
|
# even if all threads are being utilized.
|
|
@@ -317,16 +377,12 @@ module Puma
|
|
|
317
377
|
# spawned counter so that new healthy threads could be created again.
|
|
318
378
|
def reap
|
|
319
379
|
with_mutex do
|
|
320
|
-
|
|
380
|
+
@processors, dead_processors = @processors.partition(&:alive?)
|
|
321
381
|
|
|
322
|
-
|
|
323
|
-
|
|
382
|
+
dead_processors.each do |processor|
|
|
383
|
+
processor.kill
|
|
324
384
|
@spawned -= 1
|
|
325
385
|
end
|
|
326
|
-
|
|
327
|
-
@workers.delete_if do |w|
|
|
328
|
-
dead_workers.include?(w)
|
|
329
|
-
end
|
|
330
386
|
end
|
|
331
387
|
end
|
|
332
388
|
|
|
@@ -358,12 +414,12 @@ module Puma
|
|
|
358
414
|
end
|
|
359
415
|
|
|
360
416
|
def auto_trim!(timeout=@auto_trim_time)
|
|
361
|
-
@auto_trim = Automaton.new(self, timeout, "#{@name}
|
|
417
|
+
@auto_trim = Automaton.new(self, timeout, "#{@name} tp trim", :trim)
|
|
362
418
|
@auto_trim.start!
|
|
363
419
|
end
|
|
364
420
|
|
|
365
421
|
def auto_reap!(timeout=@reaping_time)
|
|
366
|
-
@reaper = Automaton.new(self, timeout, "#{@name}
|
|
422
|
+
@reaper = Automaton.new(self, timeout, "#{@name} tp reap", :reap)
|
|
367
423
|
@reaper.start!
|
|
368
424
|
end
|
|
369
425
|
|
|
@@ -385,7 +441,7 @@ module Puma
|
|
|
385
441
|
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
|
386
442
|
# threads. Finally, wait 1 second for remaining threads to exit.
|
|
387
443
|
#
|
|
388
|
-
def shutdown(timeout
|
|
444
|
+
def shutdown(timeout)
|
|
389
445
|
threads = with_mutex do
|
|
390
446
|
@shutdown = true
|
|
391
447
|
@trim_requested = @spawned
|
|
@@ -394,8 +450,12 @@ module Puma
|
|
|
394
450
|
|
|
395
451
|
@auto_trim&.stop
|
|
396
452
|
@reaper&.stop
|
|
397
|
-
# dup
|
|
398
|
-
@
|
|
453
|
+
# dup processors so that we join them all safely
|
|
454
|
+
@processors.dup
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
if @shutdown_debug == true
|
|
458
|
+
shutdown_debug("Shutdown initiated")
|
|
399
459
|
end
|
|
400
460
|
|
|
401
461
|
if timeout == -1
|
|
@@ -405,13 +465,16 @@ module Puma
|
|
|
405
465
|
join = ->(inner_timeout) do
|
|
406
466
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
407
467
|
threads.reject! do |t|
|
|
408
|
-
|
|
409
|
-
t.join
|
|
468
|
+
remaining = inner_timeout - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
|
469
|
+
remaining > 0 && t.join(remaining)
|
|
410
470
|
end
|
|
411
471
|
end
|
|
412
472
|
|
|
413
473
|
# Wait +timeout+ seconds for threads to finish.
|
|
414
474
|
join.call(timeout)
|
|
475
|
+
if @shutdown_debug == :on_force && !threads.empty?
|
|
476
|
+
shutdown_debug("Shutdown timeout exceeded")
|
|
477
|
+
end
|
|
415
478
|
|
|
416
479
|
# If threads are still running, raise ForceShutdown and wait to finish.
|
|
417
480
|
@shutdown_mutex.synchronize do
|
|
@@ -421,6 +484,9 @@ module Puma
|
|
|
421
484
|
end
|
|
422
485
|
end
|
|
423
486
|
join.call(@shutdown_grace_time)
|
|
487
|
+
if @shutdown_debug == :on_force && !threads.empty?
|
|
488
|
+
shutdown_debug("Shutdown grace timeout exceeded")
|
|
489
|
+
end
|
|
424
490
|
|
|
425
491
|
# If threads are _still_ running, forcefully kill them and wait to finish.
|
|
426
492
|
threads.each(&:kill)
|
|
@@ -428,7 +494,24 @@ module Puma
|
|
|
428
494
|
end
|
|
429
495
|
|
|
430
496
|
@spawned = 0
|
|
431
|
-
@
|
|
497
|
+
@processors = []
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
private
|
|
501
|
+
|
|
502
|
+
def shutdown_debug(message)
|
|
503
|
+
pid = Process.pid
|
|
504
|
+
threads = Thread.list
|
|
505
|
+
|
|
506
|
+
$stdout.syswrite "#{pid}: #{message}\n"
|
|
507
|
+
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
|
508
|
+
|
|
509
|
+
threads.each_with_index do |thread, index|
|
|
510
|
+
$stdout.syswrite "#{pid}: Thread #{index + 1}/#{threads.size}: #{thread.inspect}\n"
|
|
511
|
+
$stdout.syswrite "#{pid}: #{(thread.backtrace || []).join("\n#{pid}: ")}\n\n"
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
|
432
515
|
end
|
|
433
516
|
end
|
|
434
517
|
end
|
data/lib/puma/util.rb
CHANGED
|
@@ -10,13 +10,6 @@ module Puma
|
|
|
10
10
|
IO.pipe
|
|
11
11
|
end
|
|
12
12
|
|
|
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
13
|
# Escapes and unescapes a URI escaped string with
|
|
21
14
|
# +encoding+. +encoding+ will be the target encoding of the string
|
|
22
15
|
# returned, and it defaults to UTF-8
|
data/lib/puma.rb
CHANGED
|
@@ -75,4 +75,14 @@ module Puma
|
|
|
75
75
|
def self.set_thread_name(name)
|
|
76
76
|
Thread.current.name = "puma #{name}"
|
|
77
77
|
end
|
|
78
|
+
|
|
79
|
+
# Shows deprecated warning for renamed methods.
|
|
80
|
+
# @example
|
|
81
|
+
# Puma.deprecate_method_change :on_booted, __callee__, __method__
|
|
82
|
+
#
|
|
83
|
+
def self.deprecate_method_change(method_old, method_caller, method_new)
|
|
84
|
+
if method_old == method_caller
|
|
85
|
+
warn "Use '#{method_new}', '#{method_caller}' is deprecated and will be removed in v8"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
78
88
|
end
|
data/lib/rack/handler/puma.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# This module is used as an 'include' file in code at bottom of file
|
|
4
3
|
module Puma
|
|
4
|
+
|
|
5
|
+
# This module is used as an 'include' file in code at bottom of file. It loads
|
|
6
|
+
# into either `Rackup::Handler::Puma` or `Rack::Handler::Puma`.
|
|
7
|
+
|
|
5
8
|
module RackHandler
|
|
6
9
|
DEFAULT_OPTIONS = {
|
|
7
10
|
:Verbose => false,
|
|
@@ -29,7 +32,7 @@ module Puma
|
|
|
29
32
|
|
|
30
33
|
@events = options[:events] || ::Puma::Events.new
|
|
31
34
|
|
|
32
|
-
conf = ::Puma::Configuration.new(options, default_options.merge({events: @events})) do |user_config, file_config, default_config|
|
|
35
|
+
conf = ::Puma::Configuration.new(options, default_options.merge({ events: @events })) do |user_config, file_config, default_config|
|
|
33
36
|
if options.delete(:Verbose)
|
|
34
37
|
begin
|
|
35
38
|
require 'rack/commonlogger' # Rack 1.x
|
|
@@ -69,7 +72,7 @@ module Puma
|
|
|
69
72
|
|
|
70
73
|
log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
|
|
71
74
|
|
|
72
|
-
launcher = ::Puma::Launcher.new(conf, :
|
|
75
|
+
launcher = ::Puma::Launcher.new(conf, log_writer: log_writer, events: @events)
|
|
73
76
|
|
|
74
77
|
yield launcher if block_given?
|
|
75
78
|
begin
|
|
@@ -93,9 +96,9 @@ module Puma
|
|
|
93
96
|
def set_host_port_to_config(host, port, config)
|
|
94
97
|
config.clear_binds! if host || port
|
|
95
98
|
|
|
96
|
-
if host
|
|
99
|
+
if host&.start_with? '.', '/', '@'
|
|
97
100
|
config.bind "unix://#{host}"
|
|
98
|
-
elsif host
|
|
101
|
+
elsif host&.start_with? 'ssl://'
|
|
99
102
|
uri = URI.parse(host)
|
|
100
103
|
uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
|
|
101
104
|
config.bind uri.to_s
|
|
@@ -106,7 +109,7 @@ module Puma
|
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
if port
|
|
109
|
-
host ||= ::Puma::Configuration
|
|
112
|
+
host ||= ::Puma::Configuration.default_tcp_host
|
|
110
113
|
config.port port, host
|
|
111
114
|
end
|
|
112
115
|
end
|
|
@@ -115,7 +118,7 @@ module Puma
|
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
# rackup was removed in Rack 3, it is now a separate gem
|
|
118
|
-
if Object.const_defined? :
|
|
121
|
+
if Object.const_defined?(:Rackup) && ::Rackup.const_defined?(:Handler)
|
|
119
122
|
module Rackup
|
|
120
123
|
module Handler
|
|
121
124
|
module Puma
|
|
@@ -127,7 +130,7 @@ if Object.const_defined? :Rackup
|
|
|
127
130
|
end
|
|
128
131
|
end
|
|
129
132
|
else
|
|
130
|
-
do_register = Object.const_defined?(:Rack) && Rack.release < '3'
|
|
133
|
+
do_register = Object.const_defined?(:Rack) && ::Rack.release < '3'
|
|
131
134
|
module Rack
|
|
132
135
|
module Handler
|
|
133
136
|
module Puma
|
data/tools/Dockerfile
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
# Use this Dockerfile to create minimal reproductions of issues
|
|
2
|
+
# Build (MRI): docker build -f tools/Dockerfile .
|
|
3
|
+
# Build (JRuby): docker build -f tools/Dockerfile --build-arg RUBY_IMAGE=jruby:9.4 .
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
ARG RUBY_IMAGE=ruby:latest
|
|
6
|
+
FROM ${RUBY_IMAGE}
|
|
4
7
|
|
|
5
|
-
#
|
|
6
|
-
|
|
8
|
+
# Set BUNDLE_FROZEN=false if you need to update Gemfile.lock during a build.
|
|
9
|
+
ARG BUNDLE_FROZEN=true
|
|
10
|
+
|
|
11
|
+
RUN apt-get update \
|
|
12
|
+
&& apt-get install -y --no-install-recommends ragel procps git \
|
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
# Only freeze Bundler and compile native extensions when using MRI.
|
|
16
|
+
RUN if [ "$(ruby -e 'print RUBY_ENGINE')" = "ruby" ] && [ "${BUNDLE_FROZEN}" = "true" ]; then bundle config --global frozen 1; fi
|
|
7
17
|
|
|
8
18
|
WORKDIR /usr/src/app
|
|
9
19
|
|
|
10
20
|
COPY . .
|
|
11
21
|
|
|
12
22
|
RUN bundle install
|
|
13
|
-
RUN bundle exec rake compile
|
|
23
|
+
RUN if [ "$(ruby -e 'print RUBY_ENGINE')" = "ruby" ]; then bundle exec rake compile; fi
|
|
14
24
|
|
|
15
25
|
EXPOSE 9292
|
|
16
|
-
CMD bundle exec bin/puma test/rackup/hello.ru
|
|
26
|
+
CMD ["bundle", "exec", "bin/puma", "test/rackup/hello.ru"]
|