puma 3.11.3 → 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 +5 -5
- data/History.md +61 -0
- data/README.md +41 -11
- data/docs/architecture.md +2 -1
- data/docs/deployment.md +24 -4
- data/docs/restart.md +5 -3
- data/docs/systemd.md +37 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/mini_ssl.c +42 -5
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +17 -4
- data/lib/puma.rb +8 -0
- data/lib/puma/app/status.rb +3 -2
- data/lib/puma/binder.rb +22 -10
- data/lib/puma/cli.rb +18 -7
- data/lib/puma/client.rb +54 -22
- data/lib/puma/cluster.rb +54 -15
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +8 -2
- data/lib/puma/control_cli.rb +23 -11
- data/lib/puma/convenient.rb +2 -0
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +63 -11
- data/lib/puma/events.rb +2 -0
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +15 -13
- data/lib/puma/minissl.rb +20 -4
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/rack/builder.rb +2 -1
- data/lib/puma/reactor.rb +215 -30
- data/lib/puma/runner.rb +11 -2
- data/lib/puma/server.rb +63 -26
- data/lib/puma/single.rb +14 -3
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +50 -5
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +4 -0
- data/tools/jungle/README.md +10 -4
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +7 -7
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- metadata +23 -9
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/cluster.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
2
4
|
require 'puma/util'
|
3
5
|
require 'puma/plugin'
|
@@ -5,9 +7,18 @@ require 'puma/plugin'
|
|
5
7
|
require 'time'
|
6
8
|
|
7
9
|
module Puma
|
10
|
+
# This class is instantiated by the `Puma::Launcher` and used
|
11
|
+
# to boot and serve a Ruby application when puma "workers" are needed
|
12
|
+
# i.e. when using multi-processes. For example `$ puma -w 5`
|
13
|
+
#
|
14
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
15
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
16
|
+
# that this inherits from.
|
17
|
+
#
|
18
|
+
# An instance of this class will spawn the number of processes passed in
|
19
|
+
# via the `spawn_workers` method call. Each worker will have it's own
|
20
|
+
# instance of a `Puma::Server`.
|
8
21
|
class Cluster < Runner
|
9
|
-
WORKER_CHECK_INTERVAL = 5
|
10
|
-
|
11
22
|
def initialize(cli, events)
|
12
23
|
super cli, events
|
13
24
|
|
@@ -24,7 +35,35 @@ module Puma
|
|
24
35
|
@workers.each { |x| x.term }
|
25
36
|
|
26
37
|
begin
|
27
|
-
|
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
|
28
67
|
rescue Interrupt
|
29
68
|
log "! Cancelled waiting for workers"
|
30
69
|
end
|
@@ -170,7 +209,7 @@ module Puma
|
|
170
209
|
def check_workers(force=false)
|
171
210
|
return if !force && @next_check && @next_check >= Time.now
|
172
211
|
|
173
|
-
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
212
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
174
213
|
|
175
214
|
any = false
|
176
215
|
|
@@ -187,14 +226,9 @@ module Puma
|
|
187
226
|
# during this loop by giving the kernel time to kill them.
|
188
227
|
sleep 1 if any
|
189
228
|
|
190
|
-
|
191
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
192
|
-
break unless pid
|
229
|
+
@workers.reject! { |w| Process.waitpid(w.pid, Process::WNOHANG) }
|
193
230
|
|
194
|
-
|
195
|
-
end
|
196
|
-
|
197
|
-
@workers.delete_if(&:dead?)
|
231
|
+
@workers.reject!(&:dead?)
|
198
232
|
|
199
233
|
cull_workers
|
200
234
|
spawn_workers
|
@@ -277,11 +311,13 @@ module Puma
|
|
277
311
|
base_payload = "p#{Process.pid}"
|
278
312
|
|
279
313
|
while true
|
280
|
-
sleep WORKER_CHECK_INTERVAL
|
314
|
+
sleep Const::WORKER_CHECK_INTERVAL
|
281
315
|
begin
|
282
316
|
b = server.backlog || 0
|
283
317
|
r = server.running || 0
|
284
|
-
|
318
|
+
t = server.pool_capacity || 0
|
319
|
+
m = server.max_threads || 0
|
320
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
285
321
|
io << payload
|
286
322
|
rescue IOError
|
287
323
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
@@ -375,10 +411,13 @@ module Puma
|
|
375
411
|
log "Early termination of worker"
|
376
412
|
exit! 0
|
377
413
|
else
|
414
|
+
@launcher.close_binder_listeners
|
415
|
+
|
378
416
|
stop_workers
|
379
417
|
stop
|
380
418
|
|
381
|
-
raise
|
419
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
420
|
+
exit 0 # Clean exit, workers were stopped
|
382
421
|
end
|
383
422
|
end
|
384
423
|
end
|
@@ -472,7 +511,7 @@ module Puma
|
|
472
511
|
|
473
512
|
force_check = false
|
474
513
|
|
475
|
-
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
514
|
+
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
476
515
|
|
477
516
|
if res
|
478
517
|
req = read.read_nonblock(1)
|
data/lib/puma/commonlogger.rb
CHANGED
data/lib/puma/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/rack/builder'
|
2
4
|
require 'puma/plugin'
|
3
5
|
require 'puma/const'
|
@@ -184,7 +186,8 @@ module Puma
|
|
184
186
|
:rackup => DefaultRackup,
|
185
187
|
:logger => STDOUT,
|
186
188
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
187
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
189
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
190
|
+
:raise_exception_on_sigterm => true
|
188
191
|
}
|
189
192
|
end
|
190
193
|
|
data/lib/puma/const.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
module Puma
|
3
5
|
class UnsupportedOption < RuntimeError
|
4
6
|
end
|
@@ -98,8 +100,8 @@ module Puma
|
|
98
100
|
# too taxing on performance.
|
99
101
|
module Const
|
100
102
|
|
101
|
-
PUMA_VERSION = VERSION = "
|
102
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "4.0.0".freeze
|
104
|
+
CODE_NAME = "4 Fast 4 Furious".freeze
|
103
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
104
106
|
|
105
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -225,5 +227,9 @@ module Puma
|
|
225
227
|
HIJACK_IO = "rack.hijack_io".freeze
|
226
228
|
|
227
229
|
EARLY_HINTS = "rack.early_hints".freeze
|
230
|
+
|
231
|
+
# Mininum interval to checks worker health
|
232
|
+
WORKER_CHECK_INTERVAL = 5
|
233
|
+
|
228
234
|
end
|
229
235
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
require_relative 'state_file'
|
5
|
+
require_relative 'const'
|
6
|
+
require_relative 'detect'
|
7
|
+
require_relative 'configuration'
|
6
8
|
require 'uri'
|
7
9
|
require 'socket'
|
8
10
|
|
@@ -69,6 +71,7 @@ module Puma
|
|
69
71
|
end
|
70
72
|
|
71
73
|
opts.order!(argv) { |a| opts.terminate a }
|
74
|
+
opts.parse!
|
72
75
|
|
73
76
|
@command = argv.shift
|
74
77
|
|
@@ -128,7 +131,7 @@ module Puma
|
|
128
131
|
uri = URI.parse @control_url
|
129
132
|
|
130
133
|
# create server object by scheme
|
131
|
-
|
134
|
+
server = case uri.scheme
|
132
135
|
when "tcp"
|
133
136
|
TCPSocket.new uri.host, uri.port
|
134
137
|
when "unix"
|
@@ -146,9 +149,9 @@ module Puma
|
|
146
149
|
url = url + "?token=#{@control_auth_token}"
|
147
150
|
end
|
148
151
|
|
149
|
-
|
152
|
+
server << "GET #{url} HTTP/1.0\r\n\r\n"
|
150
153
|
|
151
|
-
unless data =
|
154
|
+
unless data = server.read
|
152
155
|
raise "Server closed connection before responding"
|
153
156
|
end
|
154
157
|
|
@@ -171,8 +174,8 @@ module Puma
|
|
171
174
|
message "Command #{@command} sent success"
|
172
175
|
message response.last if @command == "stats" || @command == "gc-stats"
|
173
176
|
end
|
174
|
-
|
175
|
-
|
177
|
+
ensure
|
178
|
+
server.close if server && !server.closed?
|
176
179
|
end
|
177
180
|
|
178
181
|
def send_signal
|
@@ -203,8 +206,17 @@ module Puma
|
|
203
206
|
when "phased-restart"
|
204
207
|
Process.kill "SIGUSR1", @pid
|
205
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
|
+
|
206
219
|
else
|
207
|
-
message "Puma is started"
|
208
220
|
return
|
209
221
|
end
|
210
222
|
|
@@ -220,7 +232,7 @@ module Puma
|
|
220
232
|
end
|
221
233
|
|
222
234
|
def run
|
223
|
-
start if @command == "start"
|
235
|
+
return start if @command == "start"
|
224
236
|
|
225
237
|
prepare_configuration
|
226
238
|
|
data/lib/puma/convenient.rb
CHANGED
data/lib/puma/daemon_ext.rb
CHANGED
data/lib/puma/delegation.rb
CHANGED
data/lib/puma/detect.rb
CHANGED
data/lib/puma/dsl.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
|
1
5
|
module Puma
|
2
6
|
# The methods that are available for use inside the config file.
|
3
7
|
# These same methods are used in Puma cli and the rack handler
|
@@ -55,6 +59,14 @@ module Puma
|
|
55
59
|
@plugins.clear
|
56
60
|
end
|
57
61
|
|
62
|
+
def set_default_host(host)
|
63
|
+
@options[:default_host] = host
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_host
|
67
|
+
@options[:default_host] || Configuration::DefaultTCPHost
|
68
|
+
end
|
69
|
+
|
58
70
|
def inject(&blk)
|
59
71
|
instance_eval(&blk)
|
60
72
|
end
|
@@ -93,7 +105,12 @@ module Puma
|
|
93
105
|
end
|
94
106
|
|
95
107
|
if opts[:no_token]
|
96
|
-
|
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'
|
97
114
|
else
|
98
115
|
auth_token = opts[:auth_token]
|
99
116
|
auth_token ||= Configuration.random_token
|
@@ -138,7 +155,7 @@ module Puma
|
|
138
155
|
# Define the TCP port to bind to. Use +bind+ for more advanced options.
|
139
156
|
#
|
140
157
|
def port(port, host=nil)
|
141
|
-
host ||=
|
158
|
+
host ||= default_host
|
142
159
|
bind "tcp://#{host}:#{port}"
|
143
160
|
end
|
144
161
|
|
@@ -146,13 +163,13 @@ module Puma
|
|
146
163
|
# them
|
147
164
|
#
|
148
165
|
def persistent_timeout(seconds)
|
149
|
-
@options[:persistent_timeout] = seconds
|
166
|
+
@options[:persistent_timeout] = Integer(seconds)
|
150
167
|
end
|
151
168
|
|
152
169
|
# Define how long the tcp socket stays open, if no data has been received
|
153
170
|
#
|
154
171
|
def first_data_timeout(seconds)
|
155
|
-
@options[:first_data_timeout] = seconds
|
172
|
+
@options[:first_data_timeout] = Integer(seconds)
|
156
173
|
end
|
157
174
|
|
158
175
|
# Work around leaky apps that leave garbage in Thread locals
|
@@ -169,7 +186,7 @@ module Puma
|
|
169
186
|
end
|
170
187
|
|
171
188
|
# When shutting down, drain the accept socket of pending
|
172
|
-
# connections and
|
189
|
+
# connections and process them. This loops over the accept
|
173
190
|
# socket until there are no more read events and then stops
|
174
191
|
# looking and waits for the requests to finish.
|
175
192
|
def drain_on_shutdown(which=true)
|
@@ -285,12 +302,15 @@ module Puma
|
|
285
302
|
|
286
303
|
def ssl_bind(host, port, opts)
|
287
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)
|
288
307
|
|
289
308
|
if defined?(JRUBY_VERSION)
|
290
309
|
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
291
|
-
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}"
|
292
311
|
else
|
293
|
-
|
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}"
|
294
314
|
end
|
295
315
|
end
|
296
316
|
|
@@ -365,6 +385,21 @@ module Puma
|
|
365
385
|
|
366
386
|
alias_method :after_worker_boot, :after_worker_fork
|
367
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
|
+
|
368
403
|
# The directory to operate out of.
|
369
404
|
def directory(dir)
|
370
405
|
@options[:directory] = dir.to_s
|
@@ -414,6 +449,16 @@ module Puma
|
|
414
449
|
@options[:prune_bundler] = answer
|
415
450
|
end
|
416
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
|
+
|
417
462
|
# Additional text to display in process listing
|
418
463
|
def tag(string)
|
419
464
|
@options[:tag] = string.to_s
|
@@ -424,17 +469,24 @@ module Puma
|
|
424
469
|
# that have not checked in within the given +timeout+.
|
425
470
|
# This mitigates hung processes. Default value is 60 seconds.
|
426
471
|
def worker_timeout(timeout)
|
427
|
-
|
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
|
+
|
479
|
+
@options[:worker_timeout] = Integer(timeout)
|
428
480
|
end
|
429
481
|
|
430
482
|
# *Cluster mode only* Set the timeout for workers to boot
|
431
483
|
def worker_boot_timeout(timeout)
|
432
|
-
@options[:worker_boot_timeout] = timeout
|
484
|
+
@options[:worker_boot_timeout] = Integer(timeout)
|
433
485
|
end
|
434
486
|
|
435
487
|
# *Cluster mode only* Set the timeout for worker shutdown
|
436
488
|
def worker_shutdown_timeout(timeout)
|
437
|
-
@options[:worker_shutdown_timeout] = timeout
|
489
|
+
@options[:worker_shutdown_timeout] = Integer(timeout)
|
438
490
|
end
|
439
491
|
|
440
492
|
# When set to true (the default), workers accept all requests
|
@@ -493,7 +545,7 @@ module Puma
|
|
493
545
|
when Hash
|
494
546
|
if hdr = val[:header]
|
495
547
|
@options[:remote_address] = :header
|
496
|
-
@options[:remote_address_header] = "HTTP_" + hdr.upcase.
|
548
|
+
@options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
|
497
549
|
else
|
498
550
|
raise "Invalid value for set_remote_address - #{val.inspect}"
|
499
551
|
end
|