puma 4.1.1 → 5.0.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 +149 -10
- data/LICENSE +23 -20
- data/README.md +30 -46
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/plugins.md +20 -10
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +6 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +15 -2
- 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 +91 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +9 -38
- data/lib/puma.rb +23 -0
- data/lib/puma/app/status.rb +46 -30
- data/lib/puma/binder.rb +112 -124
- data/lib/puma/cli.rb +11 -15
- data/lib/puma/client.rb +250 -209
- data/lib/puma/cluster.rb +203 -85
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +24 -19
- data/lib/puma/control_cli.rb +46 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +162 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +35 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +117 -58
- data/lib/puma/minissl.rb +60 -18
- data/lib/puma/minissl/context_builder.rb +73 -0
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +6 -12
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +16 -9
- data/lib/puma/runner.rb +11 -32
- data/lib/puma/server.rb +173 -193
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +104 -81
- data/lib/rack/handler/puma.rb +1 -5
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +23 -24
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- 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/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/lib/puma/single.rb
CHANGED
@@ -14,11 +14,9 @@ module Puma
|
|
14
14
|
# that this inherits from.
|
15
15
|
class Single < Runner
|
16
16
|
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} }!
|
17
|
+
{
|
18
|
+
started_at: @started_at.utc.iso8601
|
19
|
+
}.merge(@server.stats)
|
22
20
|
end
|
23
21
|
|
24
22
|
def restart
|
@@ -39,64 +37,10 @@ module Puma
|
|
39
37
|
@server.stop(true) if @server
|
40
38
|
end
|
41
39
|
|
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)
|
49
|
-
end
|
50
|
-
|
51
40
|
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
41
|
output_header "single"
|
67
42
|
|
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
|
43
|
+
load_and_bind
|
100
44
|
|
101
45
|
Plugins.fire_background
|
102
46
|
|
@@ -106,10 +50,9 @@ module Puma
|
|
106
50
|
|
107
51
|
@server = server = start_server
|
108
52
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
53
|
+
|
54
|
+
log "Use Ctrl-C to stop"
|
55
|
+
redirect_io
|
113
56
|
|
114
57
|
@launcher.events.fire_on_booted!
|
115
58
|
|
data/lib/puma/state_file.rb
CHANGED
@@ -8,15 +8,18 @@ module Puma
|
|
8
8
|
@options = {}
|
9
9
|
end
|
10
10
|
|
11
|
-
def save(path)
|
12
|
-
File.
|
11
|
+
def save(path, permission = nil)
|
12
|
+
File.open(path, "w") do |file|
|
13
|
+
file.chmod(permission) if permission
|
14
|
+
file.write(YAML.dump(@options))
|
15
|
+
end
|
13
16
|
end
|
14
17
|
|
15
18
|
def load(path)
|
16
19
|
@options = YAML.load File.read(path)
|
17
20
|
end
|
18
21
|
|
19
|
-
FIELDS = %w!control_url control_auth_token pid!
|
22
|
+
FIELDS = %w!control_url control_auth_token pid running_from!
|
20
23
|
|
21
24
|
FIELDS.each do |f|
|
22
25
|
define_method f do
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -47,6 +47,7 @@ module Puma
|
|
47
47
|
@shutdown = false
|
48
48
|
|
49
49
|
@trim_requested = 0
|
50
|
+
@out_of_band_pending = false
|
50
51
|
|
51
52
|
@workers = []
|
52
53
|
|
@@ -54,7 +55,10 @@ module Puma
|
|
54
55
|
@reaper = nil
|
55
56
|
|
56
57
|
@mutex.synchronize do
|
57
|
-
@min.times
|
58
|
+
@min.times do
|
59
|
+
spawn_thread
|
60
|
+
@not_full.wait(@mutex)
|
61
|
+
end
|
58
62
|
end
|
59
63
|
|
60
64
|
@clean_thread_locals = false
|
@@ -62,6 +66,7 @@ module Puma
|
|
62
66
|
|
63
67
|
attr_reader :spawned, :trim_requested, :waiting
|
64
68
|
attr_accessor :clean_thread_locals
|
69
|
+
attr_accessor :out_of_band_hook # @version 5.0.0
|
65
70
|
|
66
71
|
def self.clean_thread_locals
|
67
72
|
Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
|
@@ -72,13 +77,18 @@ module Puma
|
|
72
77
|
# How many objects have yet to be processed by the pool?
|
73
78
|
#
|
74
79
|
def backlog
|
75
|
-
|
80
|
+
with_mutex { @todo.size }
|
76
81
|
end
|
77
82
|
|
78
83
|
def pool_capacity
|
79
84
|
waiting + (@max - spawned)
|
80
85
|
end
|
81
86
|
|
87
|
+
# @version 5.0.0
|
88
|
+
def busy_threads
|
89
|
+
with_mutex { @spawned - @waiting + @todo.size }
|
90
|
+
end
|
91
|
+
|
82
92
|
# :nodoc:
|
83
93
|
#
|
84
94
|
# Must be called with @mutex held!
|
@@ -87,8 +97,7 @@ module Puma
|
|
87
97
|
@spawned += 1
|
88
98
|
|
89
99
|
th = Thread.new(@spawned) do |spawned|
|
90
|
-
|
91
|
-
Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
|
100
|
+
Puma.set_thread_name 'threadpool %03i' % spawned
|
92
101
|
todo = @todo
|
93
102
|
block = @block
|
94
103
|
mutex = @mutex
|
@@ -100,48 +109,40 @@ module Puma
|
|
100
109
|
while true
|
101
110
|
work = nil
|
102
111
|
|
103
|
-
continue = true
|
104
|
-
|
105
112
|
mutex.synchronize do
|
106
113
|
while todo.empty?
|
107
114
|
if @trim_requested > 0
|
108
115
|
@trim_requested -= 1
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
if @shutdown
|
115
|
-
continue = false
|
116
|
-
break
|
116
|
+
@spawned -= 1
|
117
|
+
@workers.delete th
|
118
|
+
Thread.exit
|
117
119
|
end
|
118
120
|
|
119
121
|
@waiting += 1
|
122
|
+
if @out_of_band_pending && trigger_out_of_band_hook
|
123
|
+
@out_of_band_pending = false
|
124
|
+
end
|
120
125
|
not_full.signal
|
121
|
-
|
122
|
-
|
126
|
+
begin
|
127
|
+
not_empty.wait mutex
|
128
|
+
ensure
|
129
|
+
@waiting -= 1
|
130
|
+
end
|
123
131
|
end
|
124
132
|
|
125
|
-
work = todo.shift
|
133
|
+
work = todo.shift
|
126
134
|
end
|
127
135
|
|
128
|
-
break unless continue
|
129
|
-
|
130
136
|
if @clean_thread_locals
|
131
137
|
ThreadPool.clean_thread_locals
|
132
138
|
end
|
133
139
|
|
134
140
|
begin
|
135
|
-
block.call(work, *extra)
|
141
|
+
@out_of_band_pending = true if block.call(work, *extra)
|
136
142
|
rescue Exception => e
|
137
143
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
138
144
|
end
|
139
145
|
end
|
140
|
-
|
141
|
-
mutex.synchronize do
|
142
|
-
@spawned -= 1
|
143
|
-
@workers.delete th
|
144
|
-
end
|
145
146
|
end
|
146
147
|
|
147
148
|
@workers << th
|
@@ -151,9 +152,32 @@ module Puma
|
|
151
152
|
|
152
153
|
private :spawn_thread
|
153
154
|
|
155
|
+
# @version 5.0.0
|
156
|
+
def trigger_out_of_band_hook
|
157
|
+
return false unless out_of_band_hook && out_of_band_hook.any?
|
158
|
+
|
159
|
+
# we execute on idle hook when all threads are free
|
160
|
+
return false unless @spawned == @waiting
|
161
|
+
|
162
|
+
out_of_band_hook.each(&:call)
|
163
|
+
true
|
164
|
+
rescue Exception => e
|
165
|
+
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
private :trigger_out_of_band_hook
|
170
|
+
|
171
|
+
# @version 5.0.0
|
172
|
+
def with_mutex(&block)
|
173
|
+
@mutex.owned? ?
|
174
|
+
yield :
|
175
|
+
@mutex.synchronize(&block)
|
176
|
+
end
|
177
|
+
|
154
178
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
155
179
|
def <<(work)
|
156
|
-
|
180
|
+
with_mutex do
|
157
181
|
if @shutdown
|
158
182
|
raise "Unable to add work while shutting down"
|
159
183
|
end
|
@@ -190,15 +214,12 @@ module Puma
|
|
190
214
|
# request, it might not be added to the `@todo` array right away.
|
191
215
|
# For example if a slow client has only sent a header, but not a body
|
192
216
|
# then the `@todo` array would stay the same size as the reactor works
|
193
|
-
# to try to buffer the request. In
|
217
|
+
# to try to buffer the request. In that scenario the next call to this
|
194
218
|
# method would not block and another request would be added into the reactor
|
195
219
|
# by the server. This would continue until a fully bufferend request
|
196
220
|
# makes it through the reactor and can then be processed by the thread pool.
|
197
|
-
#
|
198
|
-
# Returns the current number of busy threads, or +nil+ if shutting down.
|
199
|
-
#
|
200
221
|
def wait_until_not_full
|
201
|
-
|
222
|
+
with_mutex do
|
202
223
|
while true
|
203
224
|
return if @shutdown
|
204
225
|
|
@@ -206,21 +227,41 @@ module Puma
|
|
206
227
|
# is work queued that cannot be handled by waiting
|
207
228
|
# threads, then accept more work until we would
|
208
229
|
# spin up the max number of threads.
|
209
|
-
|
210
|
-
return busy_threads if @max > busy_threads
|
230
|
+
return if busy_threads < @max
|
211
231
|
|
212
232
|
@not_full.wait @mutex
|
213
233
|
end
|
214
234
|
end
|
215
235
|
end
|
216
236
|
|
217
|
-
#
|
237
|
+
# @version 5.0.0
|
238
|
+
def wait_for_less_busy_worker(delay_s)
|
239
|
+
# Ruby MRI does GVL, this can result
|
240
|
+
# in processing contention when multiple threads
|
241
|
+
# (requests) are running concurrently
|
242
|
+
return unless Puma.mri?
|
243
|
+
return unless delay_s > 0
|
244
|
+
|
245
|
+
with_mutex do
|
246
|
+
return if @shutdown
|
247
|
+
|
248
|
+
# do not delay, if we are not busy
|
249
|
+
return unless busy_threads > 0
|
250
|
+
|
251
|
+
# this will be signaled once a request finishes,
|
252
|
+
# which can happen earlier than delay
|
253
|
+
@not_full.wait @mutex, delay_s
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# If there are any free threads in the pool, tell one to go ahead
|
218
258
|
# and exit. If +force+ is true, then a trim request is requested
|
219
259
|
# even if all threads are being utilized.
|
220
260
|
#
|
221
261
|
def trim(force=false)
|
222
|
-
|
223
|
-
|
262
|
+
with_mutex do
|
263
|
+
free = @waiting - @todo.size
|
264
|
+
if (force or free > 0) and @spawned - @trim_requested > @min
|
224
265
|
@trim_requested += 1
|
225
266
|
@not_empty.signal
|
226
267
|
end
|
@@ -230,7 +271,7 @@ module Puma
|
|
230
271
|
# If there are dead threads in the pool make them go away while decreasing
|
231
272
|
# spawned counter so that new healthy threads could be created again.
|
232
273
|
def reap
|
233
|
-
|
274
|
+
with_mutex do
|
234
275
|
dead_workers = @workers.reject(&:alive?)
|
235
276
|
|
236
277
|
dead_workers.each do |worker|
|
@@ -244,10 +285,12 @@ module Puma
|
|
244
285
|
end
|
245
286
|
end
|
246
287
|
|
247
|
-
class
|
248
|
-
def initialize(pool, timeout)
|
288
|
+
class Automaton
|
289
|
+
def initialize(pool, timeout, thread_name, message)
|
249
290
|
@pool = pool
|
250
291
|
@timeout = timeout
|
292
|
+
@thread_name = thread_name
|
293
|
+
@message = message
|
251
294
|
@running = false
|
252
295
|
end
|
253
296
|
|
@@ -255,8 +298,9 @@ module Puma
|
|
255
298
|
@running = true
|
256
299
|
|
257
300
|
@thread = Thread.new do
|
301
|
+
Puma.set_thread_name @thread_name
|
258
302
|
while @running
|
259
|
-
@pool.
|
303
|
+
@pool.public_send(@message)
|
260
304
|
sleep @timeout
|
261
305
|
end
|
262
306
|
end
|
@@ -269,44 +313,24 @@ module Puma
|
|
269
313
|
end
|
270
314
|
|
271
315
|
def auto_trim!(timeout=30)
|
272
|
-
@auto_trim =
|
316
|
+
@auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
|
273
317
|
@auto_trim.start!
|
274
318
|
end
|
275
319
|
|
276
|
-
class Reaper
|
277
|
-
def initialize(pool, timeout)
|
278
|
-
@pool = pool
|
279
|
-
@timeout = timeout
|
280
|
-
@running = false
|
281
|
-
end
|
282
|
-
|
283
|
-
def start!
|
284
|
-
@running = true
|
285
|
-
|
286
|
-
@thread = Thread.new do
|
287
|
-
while @running
|
288
|
-
@pool.reap
|
289
|
-
sleep @timeout
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
def stop
|
295
|
-
@running = false
|
296
|
-
@thread.wakeup
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
320
|
def auto_reap!(timeout=5)
|
301
|
-
@reaper =
|
321
|
+
@reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
|
302
322
|
@reaper.start!
|
303
323
|
end
|
304
324
|
|
305
325
|
# Tell all threads in the pool to exit and wait for them to finish.
|
326
|
+
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
327
|
+
# Next, wait an extra +grace+ seconds then force-kill remaining threads.
|
328
|
+
# Finally, wait +kill_grace+ seconds for remaining threads to exit.
|
306
329
|
#
|
307
330
|
def shutdown(timeout=-1)
|
308
|
-
threads =
|
331
|
+
threads = with_mutex do
|
309
332
|
@shutdown = true
|
333
|
+
@trim_requested = @spawned
|
310
334
|
@not_empty.broadcast
|
311
335
|
@not_full.broadcast
|
312
336
|
|
@@ -320,27 +344,26 @@ module Puma
|
|
320
344
|
# Wait for threads to finish without force shutdown.
|
321
345
|
threads.each(&:join)
|
322
346
|
else
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
t.join
|
328
|
-
end
|
329
|
-
|
330
|
-
if threads.empty?
|
331
|
-
break
|
332
|
-
else
|
333
|
-
sleep 1
|
347
|
+
join = ->(inner_timeout) do
|
348
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
349
|
+
threads.reject! do |t|
|
350
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
351
|
+
t.join inner_timeout - elapsed
|
334
352
|
end
|
335
353
|
end
|
336
354
|
|
355
|
+
# Wait +timeout+ seconds for threads to finish.
|
356
|
+
join.call(timeout)
|
357
|
+
|
358
|
+
# If threads are still running, raise ForceShutdown and wait to finish.
|
337
359
|
threads.each do |t|
|
338
360
|
t.raise ForceShutdown
|
339
361
|
end
|
362
|
+
join.call(SHUTDOWN_GRACE_TIME)
|
340
363
|
|
341
|
-
threads
|
342
|
-
|
343
|
-
|
364
|
+
# If threads are _still_ running, forcefully kill them and wait to finish.
|
365
|
+
threads.each(&:kill)
|
366
|
+
join.call(1)
|
344
367
|
end
|
345
368
|
|
346
369
|
@spawned = 0
|