puma 3.12.1 → 4.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 +26 -5
- data/README.md +29 -9
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/mini_ssl.c +20 -4
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +11 -4
- data/lib/puma/app/status.rb +3 -2
- data/lib/puma/binder.rb +9 -1
- data/lib/puma/client.rb +43 -25
- data/lib/puma/cluster.rb +38 -14
- data/lib/puma/configuration.rb +2 -1
- data/lib/puma/const.rb +6 -2
- data/lib/puma/control_cli.rb +10 -0
- data/lib/puma/dsl.rb +45 -3
- data/lib/puma/io_buffer.rb +1 -6
- data/lib/puma/launcher.rb +10 -11
- data/lib/puma/minissl.rb +13 -1
- data/lib/puma/reactor.rb +104 -53
- data/lib/puma/runner.rb +1 -1
- data/lib/puma/server.rb +25 -24
- data/lib/puma/single.rb +2 -2
- data/lib/puma/thread_pool.rb +5 -1
- data/lib/puma/util.rb +1 -6
- data/tools/jungle/init.d/puma +5 -5
- metadata +22 -11
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/app/status.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
module App
|
3
5
|
class Status
|
@@ -60,8 +62,7 @@ module Puma
|
|
60
62
|
return rack_response(200, OK_STATUS)
|
61
63
|
|
62
64
|
when /\/gc-stats$/
|
63
|
-
|
64
|
-
return rack_response(200, json)
|
65
|
+
return rack_response(200, GC.stat.to_json)
|
65
66
|
|
66
67
|
when /\/stats$/
|
67
68
|
return rack_response(200, @cli.stats)
|
data/lib/puma/binder.rb
CHANGED
@@ -50,7 +50,13 @@ module Puma
|
|
50
50
|
|
51
51
|
def close
|
52
52
|
@ios.each { |i| i.close }
|
53
|
-
@unix_paths.each
|
53
|
+
@unix_paths.each do |i|
|
54
|
+
# Errno::ENOENT is intermittently raised
|
55
|
+
begin
|
56
|
+
File.unlink i
|
57
|
+
rescue Errno::ENOENT
|
58
|
+
end
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
62
|
def import_from_env
|
@@ -187,6 +193,8 @@ module Puma
|
|
187
193
|
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
188
194
|
end
|
189
195
|
|
196
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
197
|
+
|
190
198
|
if params['verify_mode']
|
191
199
|
ctx.verify_mode = case params['verify_mode']
|
192
200
|
when "peer"
|
data/lib/puma/client.rb
CHANGED
@@ -27,9 +27,10 @@ module Puma
|
|
27
27
|
# For example a web request from a browser or from CURL. This
|
28
28
|
#
|
29
29
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
30
|
+
# by the reactor, that's because the latter is expected to call `#to_io`
|
31
|
+
# on any non-IO objects it polls. For example nio4r internally calls
|
32
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
|
+
# registered.
|
33
34
|
#
|
34
35
|
# Instances of this class are responsible for knowing if
|
35
36
|
# the header and body are fully buffered via the `try_to_finish` method.
|
@@ -54,6 +55,7 @@ module Puma
|
|
54
55
|
@ready = false
|
55
56
|
|
56
57
|
@body = nil
|
58
|
+
@body_read_start = nil
|
57
59
|
@buffer = nil
|
58
60
|
@tempfile = nil
|
59
61
|
|
@@ -64,6 +66,8 @@ module Puma
|
|
64
66
|
|
65
67
|
@peerip = nil
|
66
68
|
@remote_addr_header = nil
|
69
|
+
|
70
|
+
@body_remain = 0
|
67
71
|
end
|
68
72
|
|
69
73
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -102,6 +106,8 @@ module Puma
|
|
102
106
|
@tempfile = nil
|
103
107
|
@parsed_bytes = 0
|
104
108
|
@ready = false
|
109
|
+
@body_remain = 0
|
110
|
+
@peerip = nil
|
105
111
|
|
106
112
|
if @buffer
|
107
113
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -114,9 +120,16 @@ module Puma
|
|
114
120
|
end
|
115
121
|
|
116
122
|
return false
|
117
|
-
|
118
|
-
|
119
|
-
|
123
|
+
else
|
124
|
+
begin
|
125
|
+
if fast_check &&
|
126
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
127
|
+
return try_to_finish
|
128
|
+
end
|
129
|
+
rescue IOError
|
130
|
+
# swallow it
|
131
|
+
end
|
132
|
+
|
120
133
|
end
|
121
134
|
end
|
122
135
|
|
@@ -147,8 +160,11 @@ module Puma
|
|
147
160
|
def decode_chunk(chunk)
|
148
161
|
if @partial_part_left > 0
|
149
162
|
if @partial_part_left <= chunk.size
|
150
|
-
|
163
|
+
if @partial_part_left > 2
|
164
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
165
|
+
end
|
151
166
|
chunk = chunk[@partial_part_left..-1]
|
167
|
+
@partial_part_left = 0
|
152
168
|
else
|
153
169
|
@body << chunk
|
154
170
|
@partial_part_left -= chunk.size
|
@@ -172,8 +188,7 @@ module Puma
|
|
172
188
|
rest = io.read
|
173
189
|
rest = rest[2..-1] if rest.start_with?("\r\n")
|
174
190
|
@buffer = rest.empty? ? nil : rest
|
175
|
-
|
176
|
-
@ready = true
|
191
|
+
set_ready
|
177
192
|
return true
|
178
193
|
end
|
179
194
|
|
@@ -211,7 +226,7 @@ module Puma
|
|
211
226
|
while true
|
212
227
|
begin
|
213
228
|
chunk = @io.read_nonblock(4096)
|
214
|
-
rescue
|
229
|
+
rescue IO::WaitReadable
|
215
230
|
return false
|
216
231
|
rescue SystemCallError, IOError
|
217
232
|
raise ConnectionError, "Connection error detected during read"
|
@@ -221,8 +236,7 @@ module Puma
|
|
221
236
|
unless chunk
|
222
237
|
@body.close
|
223
238
|
@buffer = nil
|
224
|
-
|
225
|
-
@ready = true
|
239
|
+
set_ready
|
226
240
|
raise EOFError
|
227
241
|
end
|
228
242
|
|
@@ -231,6 +245,8 @@ module Puma
|
|
231
245
|
end
|
232
246
|
|
233
247
|
def setup_body
|
248
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
249
|
+
|
234
250
|
if @env[HTTP_EXPECT] == CONTINUE
|
235
251
|
# TODO allow a hook here to check the headers before
|
236
252
|
# going forward
|
@@ -255,8 +271,7 @@ module Puma
|
|
255
271
|
unless cl
|
256
272
|
@buffer = body.empty? ? nil : body
|
257
273
|
@body = EmptyBody
|
258
|
-
|
259
|
-
@ready = true
|
274
|
+
set_ready
|
260
275
|
return true
|
261
276
|
end
|
262
277
|
|
@@ -265,8 +280,7 @@ module Puma
|
|
265
280
|
if remain <= 0
|
266
281
|
@body = StringIO.new(body)
|
267
282
|
@buffer = nil
|
268
|
-
|
269
|
-
@ready = true
|
283
|
+
set_ready
|
270
284
|
return true
|
271
285
|
end
|
272
286
|
|
@@ -294,15 +308,14 @@ module Puma
|
|
294
308
|
data = @io.read_nonblock(CHUNK_SIZE)
|
295
309
|
rescue Errno::EAGAIN
|
296
310
|
return false
|
297
|
-
rescue SystemCallError, IOError
|
311
|
+
rescue SystemCallError, IOError, EOFError
|
298
312
|
raise ConnectionError, "Connection error detected during read"
|
299
313
|
end
|
300
314
|
|
301
315
|
# No data means a closed socket
|
302
316
|
unless data
|
303
317
|
@buffer = nil
|
304
|
-
|
305
|
-
@ready = true
|
318
|
+
set_ready
|
306
319
|
raise EOFError
|
307
320
|
end
|
308
321
|
|
@@ -338,8 +351,7 @@ module Puma
|
|
338
351
|
# No data means a closed socket
|
339
352
|
unless data
|
340
353
|
@buffer = nil
|
341
|
-
|
342
|
-
@ready = true
|
354
|
+
set_ready
|
343
355
|
raise EOFError
|
344
356
|
end
|
345
357
|
|
@@ -416,8 +428,7 @@ module Puma
|
|
416
428
|
unless chunk
|
417
429
|
@body.close
|
418
430
|
@buffer = nil
|
419
|
-
|
420
|
-
@ready = true
|
431
|
+
set_ready
|
421
432
|
raise EOFError
|
422
433
|
end
|
423
434
|
|
@@ -426,8 +437,7 @@ module Puma
|
|
426
437
|
if remain <= 0
|
427
438
|
@body.rewind
|
428
439
|
@buffer = nil
|
429
|
-
|
430
|
-
@ready = true
|
440
|
+
set_ready
|
431
441
|
return true
|
432
442
|
end
|
433
443
|
|
@@ -436,6 +446,14 @@ module Puma
|
|
436
446
|
false
|
437
447
|
end
|
438
448
|
|
449
|
+
def set_ready
|
450
|
+
if @body_read_start
|
451
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
452
|
+
end
|
453
|
+
@requests_served += 1
|
454
|
+
@ready = true
|
455
|
+
end
|
456
|
+
|
439
457
|
def write_400
|
440
458
|
begin
|
441
459
|
@io << ERROR_400_RESPONSE
|
data/lib/puma/cluster.rb
CHANGED
@@ -19,8 +19,6 @@ module Puma
|
|
19
19
|
# via the `spawn_workers` method call. Each worker will have it's own
|
20
20
|
# instance of a `Puma::Server`.
|
21
21
|
class Cluster < Runner
|
22
|
-
WORKER_CHECK_INTERVAL = 5
|
23
|
-
|
24
22
|
def initialize(cli, events)
|
25
23
|
super cli, events
|
26
24
|
|
@@ -37,7 +35,35 @@ module Puma
|
|
37
35
|
@workers.each { |x| x.term }
|
38
36
|
|
39
37
|
begin
|
40
|
-
|
38
|
+
if RUBY_VERSION < '2.6'
|
39
|
+
@workers.each do |w|
|
40
|
+
begin
|
41
|
+
Process.waitpid(w.pid)
|
42
|
+
rescue Errno::ECHILD
|
43
|
+
# child is already terminated
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
# below code is for a bug in Ruby 2.6+, above waitpid call hangs
|
48
|
+
t_st = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
+
pids = @workers.map(&:pid)
|
50
|
+
loop do
|
51
|
+
pids.reject! do |w_pid|
|
52
|
+
begin
|
53
|
+
if Process.waitpid(w_pid, Process::WNOHANG)
|
54
|
+
log " worker status: #{$?}"
|
55
|
+
true
|
56
|
+
end
|
57
|
+
rescue Errno::ECHILD
|
58
|
+
true # child is already terminated
|
59
|
+
end
|
60
|
+
end
|
61
|
+
break if pids.empty?
|
62
|
+
sleep 0.5
|
63
|
+
end
|
64
|
+
t_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
65
|
+
log format(" worker shutdown time: %6.2f", t_end - t_st)
|
66
|
+
end
|
41
67
|
rescue Interrupt
|
42
68
|
log "! Cancelled waiting for workers"
|
43
69
|
end
|
@@ -183,7 +209,7 @@ module Puma
|
|
183
209
|
def check_workers(force=false)
|
184
210
|
return if !force && @next_check && @next_check >= Time.now
|
185
211
|
|
186
|
-
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
212
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
187
213
|
|
188
214
|
any = false
|
189
215
|
|
@@ -200,14 +226,9 @@ module Puma
|
|
200
226
|
# during this loop by giving the kernel time to kill them.
|
201
227
|
sleep 1 if any
|
202
228
|
|
203
|
-
|
204
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
205
|
-
break unless pid
|
229
|
+
@workers.reject! { |w| Process.waitpid(w.pid, Process::WNOHANG) }
|
206
230
|
|
207
|
-
|
208
|
-
end
|
209
|
-
|
210
|
-
@workers.delete_if(&:dead?)
|
231
|
+
@workers.reject!(&:dead?)
|
211
232
|
|
212
233
|
cull_workers
|
213
234
|
spawn_workers
|
@@ -290,7 +311,7 @@ module Puma
|
|
290
311
|
base_payload = "p#{Process.pid}"
|
291
312
|
|
292
313
|
while true
|
293
|
-
sleep WORKER_CHECK_INTERVAL
|
314
|
+
sleep Const::WORKER_CHECK_INTERVAL
|
294
315
|
begin
|
295
316
|
b = server.backlog || 0
|
296
317
|
r = server.running || 0
|
@@ -390,10 +411,13 @@ module Puma
|
|
390
411
|
log "Early termination of worker"
|
391
412
|
exit! 0
|
392
413
|
else
|
414
|
+
@launcher.close_binder_listeners
|
415
|
+
|
393
416
|
stop_workers
|
394
417
|
stop
|
395
418
|
|
396
|
-
raise
|
419
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
420
|
+
exit 0 # Clean exit, workers were stopped
|
397
421
|
end
|
398
422
|
end
|
399
423
|
end
|
@@ -487,7 +511,7 @@ module Puma
|
|
487
511
|
|
488
512
|
force_check = false
|
489
513
|
|
490
|
-
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
514
|
+
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
491
515
|
|
492
516
|
if res
|
493
517
|
req = read.read_nonblock(1)
|
data/lib/puma/configuration.rb
CHANGED
@@ -186,7 +186,8 @@ module Puma
|
|
186
186
|
:rackup => DefaultRackup,
|
187
187
|
:logger => STDOUT,
|
188
188
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
189
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
189
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
190
|
+
:raise_exception_on_sigterm => true
|
190
191
|
}
|
191
192
|
end
|
192
193
|
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "4.0.0".freeze
|
104
|
+
CODE_NAME = "4 Fast 4 Furious".freeze
|
105
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
106
|
|
107
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -227,5 +227,9 @@ module Puma
|
|
227
227
|
HIJACK_IO = "rack.hijack_io".freeze
|
228
228
|
|
229
229
|
EARLY_HINTS = "rack.early_hints".freeze
|
230
|
+
|
231
|
+
# Mininum interval to checks worker health
|
232
|
+
WORKER_CHECK_INTERVAL = 5
|
233
|
+
|
230
234
|
end
|
231
235
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -206,6 +206,16 @@ module Puma
|
|
206
206
|
when "phased-restart"
|
207
207
|
Process.kill "SIGUSR1", @pid
|
208
208
|
|
209
|
+
when "status"
|
210
|
+
begin
|
211
|
+
Process.kill 0, @pid
|
212
|
+
puts "Puma is started"
|
213
|
+
rescue Errno::ESRCH
|
214
|
+
raise "Puma is not running"
|
215
|
+
end
|
216
|
+
|
217
|
+
return
|
218
|
+
|
209
219
|
else
|
210
220
|
return
|
211
221
|
end
|
data/lib/puma/dsl.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'puma/const'
|
4
|
+
|
3
5
|
module Puma
|
4
6
|
# The methods that are available for use inside the config file.
|
5
7
|
# These same methods are used in Puma cli and the rack handler
|
@@ -103,7 +105,12 @@ module Puma
|
|
103
105
|
end
|
104
106
|
|
105
107
|
if opts[:no_token]
|
106
|
-
|
108
|
+
# We need to use 'none' rather than :none because this value will be
|
109
|
+
# passed on to an instance of OptionParser, which doesn't support
|
110
|
+
# symbols as option values.
|
111
|
+
#
|
112
|
+
# See: https://github.com/puma/puma/issues/1193#issuecomment-305995488
|
113
|
+
auth_token = 'none'
|
107
114
|
else
|
108
115
|
auth_token = opts[:auth_token]
|
109
116
|
auth_token ||= Configuration.random_token
|
@@ -295,12 +302,15 @@ module Puma
|
|
295
302
|
|
296
303
|
def ssl_bind(host, port, opts)
|
297
304
|
verify = opts.fetch(:verify_mode, 'none')
|
305
|
+
no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
|
306
|
+
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
298
307
|
|
299
308
|
if defined?(JRUBY_VERSION)
|
300
309
|
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
301
|
-
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}"
|
310
|
+
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
|
302
311
|
else
|
303
|
-
|
312
|
+
ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
|
313
|
+
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
|
304
314
|
end
|
305
315
|
end
|
306
316
|
|
@@ -375,6 +385,21 @@ module Puma
|
|
375
385
|
|
376
386
|
alias_method :after_worker_boot, :after_worker_fork
|
377
387
|
|
388
|
+
# Code to run out-of-band when the worker is idle.
|
389
|
+
# These hooks run immediately after a request has finished
|
390
|
+
# processing and there are no busy threads on the worker.
|
391
|
+
# The worker doesn't accept new requests until this code finishes.
|
392
|
+
#
|
393
|
+
# This hook is useful for running out-of-band garbage collection
|
394
|
+
# or scheduling asynchronous tasks to execute after a response.
|
395
|
+
#
|
396
|
+
# This can be called multiple times to add hooks.
|
397
|
+
#
|
398
|
+
def out_of_band(&block)
|
399
|
+
@options[:out_of_band] ||= []
|
400
|
+
@options[:out_of_band] << block
|
401
|
+
end
|
402
|
+
|
378
403
|
# The directory to operate out of.
|
379
404
|
def directory(dir)
|
380
405
|
@options[:directory] = dir.to_s
|
@@ -424,6 +449,16 @@ module Puma
|
|
424
449
|
@options[:prune_bundler] = answer
|
425
450
|
end
|
426
451
|
|
452
|
+
# In environments where SIGTERM is something expected, instructing
|
453
|
+
# puma to shutdown gracefully ( for example in Kubernetes, where
|
454
|
+
# rolling restart is guaranteed usually on infrastructure level )
|
455
|
+
# SignalException should not be raised for SIGTERM
|
456
|
+
#
|
457
|
+
# When set to false, if puma process receives SIGTERM, it won't raise SignalException
|
458
|
+
def raise_exception_on_sigterm(answer=true)
|
459
|
+
@options[:raise_exception_on_sigterm] = answer
|
460
|
+
end
|
461
|
+
|
427
462
|
# Additional text to display in process listing
|
428
463
|
def tag(string)
|
429
464
|
@options[:tag] = string.to_s
|
@@ -434,6 +469,13 @@ module Puma
|
|
434
469
|
# that have not checked in within the given +timeout+.
|
435
470
|
# This mitigates hung processes. Default value is 60 seconds.
|
436
471
|
def worker_timeout(timeout)
|
472
|
+
timeout = Integer(timeout)
|
473
|
+
min = Const::WORKER_CHECK_INTERVAL
|
474
|
+
|
475
|
+
if timeout <= min
|
476
|
+
raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
|
477
|
+
end
|
478
|
+
|
437
479
|
@options[:worker_timeout] = Integer(timeout)
|
438
480
|
end
|
439
481
|
|