puma 6.0.0 → 6.6.0
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 +392 -13
- data/LICENSE +0 -0
- data/README.md +135 -29
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +0 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +11 -1
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +4 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +13 -7
- data/docs/testing_benchmarks_local_files.md +0 -0
- data/docs/testing_test_rackup_ci_files.md +0 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +21 -14
- data/ext/puma_http11/http11_parser.c +0 -0
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +0 -0
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +107 -10
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +30 -7
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
- data/ext/puma_http11/puma_http11.c +4 -1
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +26 -15
- data/lib/puma/cli.rb +13 -5
- data/lib/puma/client.rb +113 -26
- data/lib/puma/cluster/worker.rb +14 -6
- data/lib/puma/cluster/worker_handle.rb +4 -5
- data/lib/puma/cluster.rb +93 -22
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +42 -22
- data/lib/puma/const.rb +149 -89
- data/lib/puma/control_cli.rb +16 -9
- data/lib/puma/detect.rb +5 -4
- data/lib/puma/dsl.rb +432 -40
- data/lib/puma/error_logger.rb +6 -5
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +0 -0
- data/lib/puma/launcher.rb +29 -29
- data/lib/puma/log_writer.rb +23 -13
- data/lib/puma/minissl/context_builder.rb +4 -0
- data/lib/puma/minissl.rb +23 -0
- data/lib/puma/null_io.rb +42 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +2 -2
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +17 -8
- data/lib/puma/request.rb +207 -126
- data/lib/puma/runner.rb +26 -4
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +121 -49
- data/lib/puma/single.rb +3 -1
- data/lib/puma/state_file.rb +2 -2
- data/lib/puma/thread_pool.rb +56 -9
- data/lib/puma/util.rb +1 -1
- data/lib/puma.rb +1 -3
- data/lib/rack/handler/puma.rb +116 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +12 -13
- data/lib/puma/systemd.rb +0 -47
data/lib/puma/error_logger.rb
CHANGED
@@ -15,14 +15,14 @@ module Puma
|
|
15
15
|
|
16
16
|
LOG_QUEUE = Queue.new
|
17
17
|
|
18
|
-
def initialize(ioerr)
|
18
|
+
def initialize(ioerr, env: ENV)
|
19
19
|
@ioerr = ioerr
|
20
20
|
|
21
|
-
@debug =
|
21
|
+
@debug = env.key?('PUMA_DEBUG')
|
22
22
|
end
|
23
23
|
|
24
|
-
def self.stdio
|
25
|
-
new
|
24
|
+
def self.stdio(env: ENV)
|
25
|
+
new($stderr, env: env)
|
26
26
|
end
|
27
27
|
|
28
28
|
# Print occurred error details.
|
@@ -102,7 +102,8 @@ module Puma
|
|
102
102
|
@ioerr.is_a?(IO) and @ioerr.wait_writable(1)
|
103
103
|
@ioerr.write "#{w_str}\n"
|
104
104
|
@ioerr.flush unless @ioerr.sync
|
105
|
-
rescue Errno::EPIPE, Errno::EBADF, IOError
|
105
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
106
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
106
107
|
end
|
107
108
|
end
|
108
109
|
rescue ThreadError
|
data/lib/puma/events.rb
CHANGED
File without changes
|
data/lib/puma/io_buffer.rb
CHANGED
@@ -22,6 +22,16 @@ module Puma
|
|
22
22
|
read
|
23
23
|
end
|
24
24
|
|
25
|
+
# Read & Reset - returns contents and resets
|
26
|
+
# @return [String] StringIO contents
|
27
|
+
def read_and_reset
|
28
|
+
rewind
|
29
|
+
str = read
|
30
|
+
truncate 0
|
31
|
+
rewind
|
32
|
+
str
|
33
|
+
end
|
34
|
+
|
25
35
|
alias_method :clear, :reset
|
26
36
|
|
27
37
|
# before Ruby 2.5, `write` would only take one argument
|
data/lib/puma/jruby_restart.rb
CHANGED
@@ -6,22 +6,6 @@ module Puma
|
|
6
6
|
module JRubyRestart
|
7
7
|
extend FFI::Library
|
8
8
|
ffi_lib 'c'
|
9
|
-
|
10
|
-
attach_function :execlp, [:string, :varargs], :int
|
11
9
|
attach_function :chdir, [:string], :int
|
12
|
-
attach_function :fork, [], :int
|
13
|
-
attach_function :exit, [:int], :void
|
14
|
-
attach_function :setsid, [], :int
|
15
|
-
|
16
|
-
def self.chdir_exec(dir, argv)
|
17
|
-
chdir(dir)
|
18
|
-
cmd = argv.first
|
19
|
-
argv = ([:string] * argv.size).zip(argv)
|
20
|
-
argv.flatten!
|
21
|
-
argv << :string
|
22
|
-
argv << nil
|
23
|
-
execlp(cmd, *argv)
|
24
|
-
raise SystemCallError.new(FFI.errno)
|
25
|
-
end
|
26
10
|
end
|
27
11
|
end
|
File without changes
|
File without changes
|
data/lib/puma/launcher.rb
CHANGED
@@ -46,6 +46,8 @@ module Puma
|
|
46
46
|
@original_argv = @argv.dup
|
47
47
|
@config = conf
|
48
48
|
|
49
|
+
env = launcher_args.delete(:env) || ENV
|
50
|
+
|
49
51
|
@config.options[:log_writer] = @log_writer
|
50
52
|
|
51
53
|
# Advertise the Configuration
|
@@ -59,6 +61,13 @@ module Puma
|
|
59
61
|
|
60
62
|
@environment = conf.environment
|
61
63
|
|
64
|
+
# Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
|
65
|
+
# Skip this on JRuby though, because it is incompatible with the systemd
|
66
|
+
# integration due to https://github.com/jruby/jruby/issues/6504
|
67
|
+
if ENV["NOTIFY_SOCKET"] && !Puma.jruby? && !ENV["PUMA_SKIP_SYSTEMD"]
|
68
|
+
@config.plugins.create('systemd')
|
69
|
+
end
|
70
|
+
|
62
71
|
if @config.options[:bind_to_activated_sockets]
|
63
72
|
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
64
73
|
@config.options[:binds],
|
@@ -72,6 +81,8 @@ module Puma
|
|
72
81
|
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
73
82
|
@log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
|
74
83
|
|
84
|
+
@log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
|
85
|
+
|
75
86
|
generate_restart_data
|
76
87
|
|
77
88
|
if clustered? && !Puma.forkable?
|
@@ -96,7 +107,7 @@ module Puma
|
|
96
107
|
|
97
108
|
@status = :run
|
98
109
|
|
99
|
-
log_config if
|
110
|
+
log_config if env['PUMA_LOG_CONFIG']
|
100
111
|
end
|
101
112
|
|
102
113
|
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
@@ -156,6 +167,13 @@ module Puma
|
|
156
167
|
log "* phased-restart called but not available, restarting normally."
|
157
168
|
return restart
|
158
169
|
end
|
170
|
+
|
171
|
+
if @options.file_options[:tag].nil?
|
172
|
+
dir = File.realdirpath(@restart_dir)
|
173
|
+
@options[:tag] = File.basename(dir)
|
174
|
+
set_process_title
|
175
|
+
end
|
176
|
+
|
159
177
|
true
|
160
178
|
end
|
161
179
|
|
@@ -180,7 +198,6 @@ module Puma
|
|
180
198
|
|
181
199
|
setup_signals
|
182
200
|
set_process_title
|
183
|
-
integrate_with_systemd
|
184
201
|
|
185
202
|
# This blocks until the server is stopped
|
186
203
|
@runner.run
|
@@ -279,7 +296,9 @@ module Puma
|
|
279
296
|
close_binder_listeners
|
280
297
|
|
281
298
|
require_relative 'jruby_restart'
|
282
|
-
|
299
|
+
argv = restart_args
|
300
|
+
JRubyRestart.chdir(@restart_dir)
|
301
|
+
Kernel.exec(*argv)
|
283
302
|
elsif Puma.windows?
|
284
303
|
close_binder_listeners
|
285
304
|
|
@@ -311,27 +330,6 @@ module Puma
|
|
311
330
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
312
331
|
end
|
313
332
|
|
314
|
-
# Puma's systemd integration allows Puma to inform systemd:
|
315
|
-
# 1. when it has successfully started
|
316
|
-
# 2. when it is starting shutdown
|
317
|
-
# 3. periodically for a liveness check with a watchdog thread
|
318
|
-
def integrate_with_systemd
|
319
|
-
return unless ENV["NOTIFY_SOCKET"]
|
320
|
-
|
321
|
-
begin
|
322
|
-
require_relative 'systemd'
|
323
|
-
rescue LoadError
|
324
|
-
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
325
|
-
return
|
326
|
-
end
|
327
|
-
|
328
|
-
log "* Enabling systemd notification integration"
|
329
|
-
|
330
|
-
systemd = Systemd.new(@log_writer, @events)
|
331
|
-
systemd.hook_events
|
332
|
-
systemd.start_watchdog
|
333
|
-
end
|
334
|
-
|
335
333
|
def log(str)
|
336
334
|
@log_writer.log(str)
|
337
335
|
end
|
@@ -421,12 +419,14 @@ module Puma
|
|
421
419
|
end
|
422
420
|
|
423
421
|
def setup_signals
|
424
|
-
|
425
|
-
|
426
|
-
|
422
|
+
unless ENV["PUMA_SKIP_SIGUSR2"]
|
423
|
+
begin
|
424
|
+
Signal.trap "SIGUSR2" do
|
425
|
+
restart
|
426
|
+
end
|
427
|
+
rescue Exception
|
428
|
+
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
|
427
429
|
end
|
428
|
-
rescue Exception
|
429
|
-
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
|
430
430
|
end
|
431
431
|
|
432
432
|
unless Puma.jruby?
|
data/lib/puma/log_writer.rb
CHANGED
@@ -28,38 +28,43 @@ module Puma
|
|
28
28
|
attr_reader :stdout,
|
29
29
|
:stderr
|
30
30
|
|
31
|
-
attr_accessor :formatter
|
31
|
+
attr_accessor :formatter, :custom_logger
|
32
32
|
|
33
33
|
# Create a LogWriter that prints to +stdout+ and +stderr+.
|
34
|
-
def initialize(stdout, stderr)
|
34
|
+
def initialize(stdout, stderr, env: ENV)
|
35
35
|
@formatter = DefaultFormatter.new
|
36
|
+
@custom_logger = nil
|
36
37
|
@stdout = stdout
|
37
38
|
@stderr = stderr
|
38
39
|
|
39
|
-
@debug =
|
40
|
-
@error_logger = ErrorLogger.new(@stderr)
|
40
|
+
@debug = env.key?('PUMA_DEBUG')
|
41
|
+
@error_logger = ErrorLogger.new(@stderr, env: env)
|
41
42
|
end
|
42
43
|
|
43
44
|
DEFAULT = new(STDOUT, STDERR)
|
44
45
|
|
45
46
|
# Returns an LogWriter object which writes its status to
|
46
47
|
# two StringIO objects.
|
47
|
-
def self.strings
|
48
|
-
LogWriter.new(StringIO.new, StringIO.new)
|
48
|
+
def self.strings(env: ENV)
|
49
|
+
LogWriter.new(StringIO.new, StringIO.new, env: env)
|
49
50
|
end
|
50
51
|
|
51
|
-
def self.stdio
|
52
|
-
LogWriter.new($stdout, $stderr)
|
52
|
+
def self.stdio(env: ENV)
|
53
|
+
LogWriter.new($stdout, $stderr, env: env)
|
53
54
|
end
|
54
55
|
|
55
|
-
def self.null
|
56
|
+
def self.null(env: ENV)
|
56
57
|
n = NullIO.new
|
57
|
-
LogWriter.new(n, n)
|
58
|
+
LogWriter.new(n, n, env: env)
|
58
59
|
end
|
59
60
|
|
60
61
|
# Write +str+ to +@stdout+
|
61
62
|
def log(str)
|
62
|
-
|
63
|
+
if @custom_logger&.respond_to?(:write)
|
64
|
+
@custom_logger.write(format(str))
|
65
|
+
else
|
66
|
+
internal_write "#{@formatter.call str}\n"
|
67
|
+
end
|
63
68
|
end
|
64
69
|
|
65
70
|
def write(str)
|
@@ -73,13 +78,18 @@ module Puma
|
|
73
78
|
@stdout.is_a?(IO) and @stdout.wait_writable(1)
|
74
79
|
@stdout.write w_str
|
75
80
|
@stdout.flush unless @stdout.sync
|
76
|
-
rescue Errno::EPIPE, Errno::EBADF, IOError
|
81
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
82
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
77
83
|
end
|
78
84
|
end
|
79
85
|
rescue ThreadError
|
80
86
|
end
|
81
87
|
private :internal_write
|
82
88
|
|
89
|
+
def debug?
|
90
|
+
@debug
|
91
|
+
end
|
92
|
+
|
83
93
|
def debug(str)
|
84
94
|
log("% #{str}") if @debug
|
85
95
|
end
|
@@ -115,7 +125,7 @@ module Puma
|
|
115
125
|
def ssl_error(error, ssl_socket)
|
116
126
|
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
117
127
|
peercert = ssl_socket.peercert
|
118
|
-
subject = peercert
|
128
|
+
subject = peercert&.subject
|
119
129
|
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
120
130
|
end
|
121
131
|
|
@@ -38,6 +38,7 @@ module Puma
|
|
38
38
|
|
39
39
|
ctx.key = params['key'] if params['key']
|
40
40
|
ctx.key_pem = params['key_pem'] if params['key_pem']
|
41
|
+
ctx.key_password_command = params['key_password_command'] if params['key_password_command']
|
41
42
|
|
42
43
|
if params['cert'].nil? && params['cert_pem'].nil?
|
43
44
|
log_writer.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
|
@@ -50,10 +51,13 @@ module Puma
|
|
50
51
|
unless params['ca']
|
51
52
|
log_writer.error "Please specify the SSL ca via 'ca='"
|
52
53
|
end
|
54
|
+
# needed for Puma::MiniSSL::Socket#peercert, env['puma.peercert']
|
55
|
+
require 'openssl'
|
53
56
|
end
|
54
57
|
|
55
58
|
ctx.ca = params['ca'] if params['ca']
|
56
59
|
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
60
|
+
ctx.ssl_ciphersuites = params['ssl_ciphersuites'] if params['ssl_ciphersuites'] && HAS_TLS1_3
|
57
61
|
|
58
62
|
ctx.reuse = params['reuse'] if params['reuse']
|
59
63
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -5,6 +5,7 @@ begin
|
|
5
5
|
rescue LoadError
|
6
6
|
end
|
7
7
|
|
8
|
+
require 'open3'
|
8
9
|
# need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
|
9
10
|
# use require, see https://github.com/puma/puma/pull/2381
|
10
11
|
require 'puma/puma_http11'
|
@@ -183,6 +184,11 @@ module Puma
|
|
183
184
|
@socket.peeraddr
|
184
185
|
end
|
185
186
|
|
187
|
+
# OpenSSL is loaded in `MiniSSL::ContextBuilder` when
|
188
|
+
# `MiniSSL::Context#verify_mode` is not `VERIFY_NONE`.
|
189
|
+
# When `VERIFY_NONE`, `MiniSSL::Engine#peercert` is nil, regardless of
|
190
|
+
# whether the client sends a cert.
|
191
|
+
# @return [OpenSSL::X509::Certificate, nil]
|
186
192
|
# @!attribute [r] peercert
|
187
193
|
def peercert
|
188
194
|
return @peercert if @peercert
|
@@ -277,11 +283,13 @@ module Puma
|
|
277
283
|
else
|
278
284
|
# non-jruby Context properties
|
279
285
|
attr_reader :key
|
286
|
+
attr_reader :key_password_command
|
280
287
|
attr_reader :cert
|
281
288
|
attr_reader :ca
|
282
289
|
attr_reader :cert_pem
|
283
290
|
attr_reader :key_pem
|
284
291
|
attr_accessor :ssl_cipher_filter
|
292
|
+
attr_accessor :ssl_ciphersuites
|
285
293
|
attr_accessor :verification_flags
|
286
294
|
|
287
295
|
attr_reader :reuse, :reuse_cache_size, :reuse_timeout
|
@@ -291,6 +299,10 @@ module Puma
|
|
291
299
|
@key = key
|
292
300
|
end
|
293
301
|
|
302
|
+
def key_password_command=(key_password_command)
|
303
|
+
@key_password_command = key_password_command
|
304
|
+
end
|
305
|
+
|
294
306
|
def cert=(cert)
|
295
307
|
check_file cert, 'Cert'
|
296
308
|
@cert = cert
|
@@ -316,6 +328,17 @@ module Puma
|
|
316
328
|
raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
|
317
329
|
end
|
318
330
|
|
331
|
+
# Executes the command to return the password needed to decrypt the key.
|
332
|
+
def key_password
|
333
|
+
raise "Key password command not configured" if @key_password_command.nil?
|
334
|
+
|
335
|
+
stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
|
336
|
+
|
337
|
+
return stdout_str.chomp if status.success?
|
338
|
+
|
339
|
+
raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
|
340
|
+
end
|
341
|
+
|
319
342
|
# Controls session reuse. Allowed values are as follows:
|
320
343
|
# * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
|
321
344
|
# in case reuse 'on' is made the default in future Puma versions.
|
data/lib/puma/null_io.rb
CHANGED
@@ -16,15 +16,38 @@ module Puma
|
|
16
16
|
def each
|
17
17
|
end
|
18
18
|
|
19
|
+
def pos
|
20
|
+
0
|
21
|
+
end
|
22
|
+
|
19
23
|
# Mimics IO#read with no data.
|
20
24
|
#
|
21
|
-
def read(
|
22
|
-
|
25
|
+
def read(length = nil, buffer = nil)
|
26
|
+
if length.to_i < 0
|
27
|
+
raise ArgumentError, "(negative length #{length} given)"
|
28
|
+
end
|
29
|
+
|
30
|
+
buffer = if buffer.nil?
|
31
|
+
"".b
|
32
|
+
else
|
33
|
+
String.try_convert(buffer) or raise TypeError, "no implicit conversion of #{buffer.class} into String"
|
34
|
+
end
|
35
|
+
buffer.clear
|
36
|
+
if length.to_i > 0
|
37
|
+
nil
|
38
|
+
else
|
39
|
+
buffer
|
40
|
+
end
|
23
41
|
end
|
24
42
|
|
25
43
|
def rewind
|
26
44
|
end
|
27
45
|
|
46
|
+
def seek(pos, whence = 0)
|
47
|
+
raise ArgumentError, "negative length #{pos} given" if pos.negative?
|
48
|
+
0
|
49
|
+
end
|
50
|
+
|
28
51
|
def close
|
29
52
|
end
|
30
53
|
|
@@ -57,5 +80,22 @@ module Puma
|
|
57
80
|
def closed?
|
58
81
|
false
|
59
82
|
end
|
83
|
+
|
84
|
+
def set_encoding(enc)
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
# per rack spec
|
89
|
+
def external_encoding
|
90
|
+
Encoding::ASCII_8BIT
|
91
|
+
end
|
92
|
+
|
93
|
+
def binmode
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def binmode?
|
98
|
+
true
|
99
|
+
end
|
60
100
|
end
|
61
101
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../plugin'
|
4
|
+
|
5
|
+
# Puma's systemd integration allows Puma to inform systemd:
|
6
|
+
# 1. when it has successfully started
|
7
|
+
# 2. when it is starting shutdown
|
8
|
+
# 3. periodically for a liveness check with a watchdog thread
|
9
|
+
# 4. periodically set the status
|
10
|
+
Puma::Plugin.create do
|
11
|
+
def start(launcher)
|
12
|
+
require_relative '../sd_notify'
|
13
|
+
|
14
|
+
launcher.log_writer.log "* Enabling systemd notification integration"
|
15
|
+
|
16
|
+
# hook_events
|
17
|
+
launcher.events.on_booted { Puma::SdNotify.ready }
|
18
|
+
launcher.events.on_stopped { Puma::SdNotify.stopping }
|
19
|
+
launcher.events.on_restart { Puma::SdNotify.reloading }
|
20
|
+
|
21
|
+
# start watchdog
|
22
|
+
if Puma::SdNotify.watchdog?
|
23
|
+
ping_f = watchdog_sleep_time
|
24
|
+
|
25
|
+
in_background do
|
26
|
+
launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
|
27
|
+
loop do
|
28
|
+
sleep ping_f
|
29
|
+
Puma::SdNotify.watchdog
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# start status loop
|
35
|
+
instance = self
|
36
|
+
sleep_time = 1.0
|
37
|
+
in_background do
|
38
|
+
launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
|
39
|
+
|
40
|
+
loop do
|
41
|
+
sleep sleep_time
|
42
|
+
# TODO: error handling?
|
43
|
+
Puma::SdNotify.status(instance.status)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def status
|
49
|
+
if clustered?
|
50
|
+
messages = stats[:worker_status].map do |worker|
|
51
|
+
common_message(worker[:last_status])
|
52
|
+
end.join(',')
|
53
|
+
|
54
|
+
"Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
|
55
|
+
else
|
56
|
+
"Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def watchdog_sleep_time
|
63
|
+
usec = Integer(ENV["WATCHDOG_USEC"])
|
64
|
+
|
65
|
+
sec_f = usec / 1_000_000.0
|
66
|
+
# "It is recommended that a daemon sends a keep-alive notification message
|
67
|
+
# to the service manager every half of the time returned here."
|
68
|
+
sec_f / 2
|
69
|
+
end
|
70
|
+
|
71
|
+
def stats
|
72
|
+
Puma.stats_hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def clustered?
|
76
|
+
stats.has_key?(:workers)
|
77
|
+
end
|
78
|
+
|
79
|
+
def workers
|
80
|
+
stats.fetch(:workers, 1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def booted_workers
|
84
|
+
stats.fetch(:booted_workers, 1)
|
85
|
+
end
|
86
|
+
|
87
|
+
def common_message(stats)
|
88
|
+
"{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
|
89
|
+
end
|
90
|
+
end
|
File without changes
|
data/lib/puma/plugin.rb
CHANGED
File without changes
|
data/lib/puma/rack/builder.rb
CHANGED
@@ -173,7 +173,7 @@ module Puma::Rack
|
|
173
173
|
TOPLEVEL_BINDING, file, 0
|
174
174
|
end
|
175
175
|
|
176
|
-
def initialize(default_app = nil
|
176
|
+
def initialize(default_app = nil, &block)
|
177
177
|
@use, @map, @run, @warmup = [], nil, default_app, nil
|
178
178
|
|
179
179
|
# Conditionally load rack now, so that any rack middlewares,
|
@@ -183,7 +183,7 @@ module Puma::Rack
|
|
183
183
|
rescue LoadError
|
184
184
|
end
|
185
185
|
|
186
|
-
instance_eval(&block) if
|
186
|
+
instance_eval(&block) if block
|
187
187
|
end
|
188
188
|
|
189
189
|
def self.app(default_app = nil, &block)
|
data/lib/puma/rack/urlmap.rb
CHANGED
@@ -34,7 +34,7 @@ module Puma::Rack
|
|
34
34
|
end
|
35
35
|
|
36
36
|
location = location.chomp('/')
|
37
|
-
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)",
|
37
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
|
38
38
|
|
39
39
|
[host, location, match, app]
|
40
40
|
}.sort_by do |(host, location, _, _)|
|
data/lib/puma/rack_default.rb
CHANGED
@@ -2,8 +2,23 @@
|
|
2
2
|
|
3
3
|
require_relative '../rack/handler/puma'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# rackup was removed in Rack 3, it is now a separate gem
|
6
|
+
if Object.const_defined? :Rackup
|
7
|
+
module Rackup
|
8
|
+
module Handler
|
9
|
+
def self.default(options = {})
|
10
|
+
::Rackup::Handler::Puma
|
11
|
+
end
|
12
|
+
end
|
8
13
|
end
|
14
|
+
elsif Object.const_defined?(:Rack) && Rack.release < '3'
|
15
|
+
module Rack
|
16
|
+
module Handler
|
17
|
+
def self.default(options = {})
|
18
|
+
::Rack::Handler::Puma
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise "Rack 3 must be used with the Rackup gem"
|
9
24
|
end
|
data/lib/puma/reactor.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'queue_close' unless ::Queue.instance_methods.include? :close
|
4
|
-
|
5
3
|
module Puma
|
6
4
|
class UnsupportedBackend < StandardError; end
|
7
5
|
|
@@ -22,10 +20,12 @@ module Puma
|
|
22
20
|
# its timeout elapses, or when the Reactor shuts down.
|
23
21
|
def initialize(backend, &block)
|
24
22
|
require 'nio'
|
25
|
-
|
26
|
-
|
23
|
+
valid_backends = [:auto, *::NIO::Selector.backends]
|
24
|
+
unless valid_backends.include?(backend)
|
25
|
+
raise ArgumentError.new("unsupported IO selector backend: #{backend} (available backends: #{valid_backends.join(', ')})")
|
27
26
|
end
|
28
|
-
|
27
|
+
|
28
|
+
@selector = ::NIO::Selector.new(NIO::Selector.backends.delete(backend))
|
29
29
|
@input = Queue.new
|
30
30
|
@timeouts = []
|
31
31
|
@block = block
|
@@ -50,7 +50,7 @@ module Puma
|
|
50
50
|
@input << client
|
51
51
|
@selector.wakeup
|
52
52
|
true
|
53
|
-
rescue ClosedQueueError
|
53
|
+
rescue ClosedQueueError, IOError # Ignore if selector is already closed
|
54
54
|
false
|
55
55
|
end
|
56
56
|
|
@@ -67,6 +67,7 @@ module Puma
|
|
67
67
|
private
|
68
68
|
|
69
69
|
def select_loop
|
70
|
+
close_selector = true
|
70
71
|
begin
|
71
72
|
until @input.closed? && @input.empty?
|
72
73
|
# Wakeup any registered object that receives incoming data.
|
@@ -89,11 +90,19 @@ module Puma
|
|
89
90
|
rescue StandardError => e
|
90
91
|
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
91
92
|
STDERR.puts e.backtrace
|
92
|
-
|
93
|
+
|
94
|
+
# NoMethodError may be rarely raised when calling @selector.select, which
|
95
|
+
# is odd. Regardless, it may continue for thousands of calls if retried.
|
96
|
+
# Also, when it raises, @selector.close also raises an error.
|
97
|
+
if NoMethodError === e
|
98
|
+
close_selector = false
|
99
|
+
else
|
100
|
+
retry
|
101
|
+
end
|
93
102
|
end
|
94
103
|
# Wakeup all remaining objects on shutdown.
|
95
104
|
@timeouts.each(&@block)
|
96
|
-
@selector.close
|
105
|
+
@selector.close if close_selector
|
97
106
|
end
|
98
107
|
|
99
108
|
# Start monitoring the object.
|