puma 5.6.8 → 6.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +332 -16
- data/README.md +79 -29
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/restart.md +1 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +16 -9
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +127 -19
- data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -53
- data/lib/puma/cli.rb +16 -18
- data/lib/puma/client.rb +59 -19
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +102 -40
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +77 -59
- data/lib/puma/const.rb +137 -92
- data/lib/puma/control_cli.rb +15 -11
- data/lib/puma/detect.rb +7 -4
- data/lib/puma/dsl.rb +250 -56
- data/lib/puma/error_logger.rb +18 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +102 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +26 -12
- data/lib/puma/minissl.rb +104 -11
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +380 -172
- data/lib/puma/runner.rb +56 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +137 -89
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +3 -6
- data/lib/puma/thread_pool.rb +57 -19
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +9 -10
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- metadata +11 -7
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
- data/lib/rack/version_restriction.rb +0 -15
data/lib/puma/client.rb
CHANGED
@@ -8,9 +8,9 @@ class IO
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
require_relative 'detect'
|
12
|
+
require_relative 'io_buffer'
|
12
13
|
require 'tempfile'
|
13
|
-
require 'forwardable'
|
14
14
|
|
15
15
|
if Puma::IS_JRUBY
|
16
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -25,6 +25,9 @@ module Puma
|
|
25
25
|
|
26
26
|
class HttpParserError501 < IOError; end
|
27
27
|
|
28
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
29
|
+
|
30
|
+
|
28
31
|
# An instance of this class represents a unique request from a client.
|
29
32
|
# For example, this could be a web request from a browser or from CURL.
|
30
33
|
#
|
@@ -38,7 +41,7 @@ module Puma
|
|
38
41
|
# the header and body are fully buffered via the `try_to_finish` method.
|
39
42
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
43
|
#
|
41
|
-
class Client
|
44
|
+
class Client # :nodoc:
|
42
45
|
|
43
46
|
# this tests all values but the last, which must be chunked
|
44
47
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
@@ -66,17 +69,13 @@ module Puma
|
|
66
69
|
EmptyBody = NullIO.new
|
67
70
|
|
68
71
|
include Puma::Const
|
69
|
-
extend Forwardable
|
70
72
|
|
71
73
|
def initialize(io, env=nil)
|
72
74
|
@io = io
|
73
75
|
@to_io = io.to_io
|
76
|
+
@io_buffer = IOBuffer.new
|
74
77
|
@proto_env = env
|
75
|
-
|
76
|
-
@env = nil
|
77
|
-
else
|
78
|
-
@env = env.dup
|
79
|
-
end
|
78
|
+
@env = env&.dup
|
80
79
|
|
81
80
|
@parser = HttpParser.new
|
82
81
|
@parsed_bytes = 0
|
@@ -94,7 +93,11 @@ module Puma
|
|
94
93
|
@requests_served = 0
|
95
94
|
@hijacked = false
|
96
95
|
|
96
|
+
@http_content_length_limit = nil
|
97
|
+
@http_content_length_limit_exceeded = false
|
98
|
+
|
97
99
|
@peerip = nil
|
100
|
+
@peer_family = nil
|
98
101
|
@listener = nil
|
99
102
|
@remote_addr_header = nil
|
100
103
|
@expect_proxy_proto = false
|
@@ -102,16 +105,22 @@ module Puma
|
|
102
105
|
@body_remain = 0
|
103
106
|
|
104
107
|
@in_last_chunk = false
|
108
|
+
|
109
|
+
# need unfrozen ASCII-8BIT, +'' is UTF-8
|
110
|
+
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
105
111
|
end
|
106
112
|
|
107
113
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
108
|
-
:tempfile
|
114
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
109
115
|
|
110
|
-
attr_writer :peerip
|
116
|
+
attr_writer :peerip, :http_content_length_limit
|
111
117
|
|
112
118
|
attr_accessor :remote_addr_header, :listener
|
113
119
|
|
114
|
-
|
120
|
+
# Remove in Puma 7?
|
121
|
+
def closed?
|
122
|
+
@to_io.closed?
|
123
|
+
end
|
115
124
|
|
116
125
|
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
117
126
|
# used for MiniSSL::Socket
|
@@ -147,6 +156,7 @@ module Puma
|
|
147
156
|
|
148
157
|
def reset(fast_check=true)
|
149
158
|
@parser.reset
|
159
|
+
@io_buffer.reset
|
150
160
|
@read_header = true
|
151
161
|
@read_proxy = !!@expect_proxy_proto
|
152
162
|
@env = @proto_env.dup
|
@@ -157,6 +167,7 @@ module Puma
|
|
157
167
|
@body_remain = 0
|
158
168
|
@peerip = nil if @remote_addr_header
|
159
169
|
@in_last_chunk = false
|
170
|
+
@http_content_length_limit_exceeded = false
|
160
171
|
|
161
172
|
if @buffer
|
162
173
|
return false unless try_to_parse_proxy_protocol
|
@@ -216,6 +227,17 @@ module Puma
|
|
216
227
|
end
|
217
228
|
|
218
229
|
def try_to_finish
|
230
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
231
|
+
@http_content_length_limit_exceeded = true
|
232
|
+
end
|
233
|
+
|
234
|
+
if @http_content_length_limit_exceeded
|
235
|
+
@buffer = nil
|
236
|
+
@body = EmptyBody
|
237
|
+
set_ready
|
238
|
+
return true
|
239
|
+
end
|
240
|
+
|
219
241
|
return read_body if in_data_phase
|
220
242
|
|
221
243
|
begin
|
@@ -245,6 +267,10 @@ module Puma
|
|
245
267
|
|
246
268
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
247
269
|
|
270
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
271
|
+
@http_content_length_limit_exceeded = true
|
272
|
+
end
|
273
|
+
|
248
274
|
if @parser.finished?
|
249
275
|
return setup_body
|
250
276
|
elsif @parsed_bytes >= MAX_HEADER
|
@@ -282,7 +308,7 @@ module Puma
|
|
282
308
|
return @peerip if @peerip
|
283
309
|
|
284
310
|
if @remote_addr_header
|
285
|
-
hdr = (@env[@remote_addr_header] ||
|
311
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
286
312
|
@peerip = hdr
|
287
313
|
return hdr
|
288
314
|
end
|
@@ -290,6 +316,16 @@ module Puma
|
|
290
316
|
@peerip ||= @io.peeraddr.last
|
291
317
|
end
|
292
318
|
|
319
|
+
def peer_family
|
320
|
+
return @peer_family if @peer_family
|
321
|
+
|
322
|
+
@peer_family ||= begin
|
323
|
+
@io.local_address.afamily
|
324
|
+
rescue
|
325
|
+
Socket::AF_INET
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
293
329
|
# Returns true if the persistent connection can be closed immediately
|
294
330
|
# without waiting for the configured idle/shutdown timeout.
|
295
331
|
# @version 5.0.0
|
@@ -313,7 +349,7 @@ module Puma
|
|
313
349
|
private
|
314
350
|
|
315
351
|
def setup_body
|
316
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
352
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
317
353
|
|
318
354
|
if @env[HTTP_EXPECT] == CONTINUE
|
319
355
|
# TODO allow a hook here to check the headers before
|
@@ -357,7 +393,7 @@ module Puma
|
|
357
393
|
|
358
394
|
if cl
|
359
395
|
# cannot contain characters that are not \d, or be empty
|
360
|
-
if cl
|
396
|
+
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
361
397
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
362
398
|
end
|
363
399
|
else
|
@@ -410,7 +446,7 @@ module Puma
|
|
410
446
|
end
|
411
447
|
|
412
448
|
begin
|
413
|
-
chunk = @io.read_nonblock(want)
|
449
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
414
450
|
rescue IO::WaitReadable
|
415
451
|
return false
|
416
452
|
rescue SystemCallError, IOError
|
@@ -442,7 +478,7 @@ module Puma
|
|
442
478
|
def read_chunked_body
|
443
479
|
while true
|
444
480
|
begin
|
445
|
-
chunk = @io.read_nonblock(4096)
|
481
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
446
482
|
rescue IO::WaitReadable
|
447
483
|
return false
|
448
484
|
rescue SystemCallError, IOError
|
@@ -523,7 +559,7 @@ module Puma
|
|
523
559
|
# Puma doesn't process chunk extensions, but should parse if they're
|
524
560
|
# present, which is the reason for the semicolon regex
|
525
561
|
chunk_hex = line.strip[/\A[^;]+/]
|
526
|
-
if chunk_hex
|
562
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
527
563
|
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
528
564
|
end
|
529
565
|
len = chunk_hex.to_i(16)
|
@@ -610,10 +646,14 @@ module Puma
|
|
610
646
|
|
611
647
|
def set_ready
|
612
648
|
if @body_read_start
|
613
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
649
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
614
650
|
end
|
615
651
|
@requests_served += 1
|
616
652
|
@ready = true
|
617
653
|
end
|
654
|
+
|
655
|
+
def above_http_content_limit(value)
|
656
|
+
@http_content_length_limit&.< value
|
657
|
+
end
|
618
658
|
end
|
619
659
|
end
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -2,27 +2,29 @@
|
|
2
2
|
|
3
3
|
module Puma
|
4
4
|
class Cluster < Puma::Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
5
8
|
# This class is instantiated by the `Puma::Cluster` and represents a single
|
6
9
|
# worker process.
|
7
10
|
#
|
8
11
|
# At the core of this class is running an instance of `Puma::Server` which
|
9
12
|
# gets created via the `start_server` method from the `Puma::Runner` class
|
10
13
|
# that this inherits from.
|
11
|
-
class Worker < Puma::Runner
|
14
|
+
class Worker < Puma::Runner # :nodoc:
|
12
15
|
attr_reader :index, :master
|
13
16
|
|
14
17
|
def initialize(index:, master:, launcher:, pipes:, server: nil)
|
15
|
-
super
|
18
|
+
super(launcher)
|
16
19
|
|
17
20
|
@index = index
|
18
21
|
@master = master
|
19
|
-
@launcher = launcher
|
20
|
-
@options = launcher.options
|
21
22
|
@check_pipe = pipes[:check_pipe]
|
22
23
|
@worker_write = pipes[:worker_write]
|
23
24
|
@fork_pipe = pipes[:fork_pipe]
|
24
25
|
@wakeup = pipes[:wakeup]
|
25
26
|
@server = server
|
27
|
+
@hook_data = {}
|
26
28
|
end
|
27
29
|
|
28
30
|
def run
|
@@ -52,13 +54,14 @@ module Puma
|
|
52
54
|
|
53
55
|
# Invoke any worker boot hooks so they can get
|
54
56
|
# things in shape before booting the app.
|
55
|
-
@
|
57
|
+
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
56
58
|
|
57
59
|
begin
|
58
60
|
server = @server ||= start_server
|
59
61
|
rescue Exception => e
|
60
62
|
log "! Unable to start worker"
|
61
|
-
log e
|
63
|
+
log e
|
64
|
+
log e.backtrace.join("\n ")
|
62
65
|
exit 1
|
63
66
|
end
|
64
67
|
|
@@ -83,8 +86,7 @@ module Puma
|
|
83
86
|
if restart_server.length > 0
|
84
87
|
restart_server.clear
|
85
88
|
server.begin_restart(true)
|
86
|
-
@
|
87
|
-
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
89
|
+
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
88
90
|
end
|
89
91
|
elsif idx == 0 # restart server
|
90
92
|
restart_server << true << false
|
@@ -113,6 +115,11 @@ module Puma
|
|
113
115
|
|
114
116
|
while restart_server.pop
|
115
117
|
server_thread = server.run
|
118
|
+
|
119
|
+
if @log_writer.debug? && index == 0
|
120
|
+
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
121
|
+
end
|
122
|
+
|
116
123
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
117
124
|
Puma.set_thread_name "stat pld"
|
118
125
|
base_payload = "p#{Process.pid}"
|
@@ -138,7 +145,7 @@ module Puma
|
|
138
145
|
|
139
146
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
140
147
|
# exiting until any background operations are completed
|
141
|
-
@
|
148
|
+
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
142
149
|
ensure
|
143
150
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
144
151
|
@worker_write.close
|
@@ -147,7 +154,7 @@ module Puma
|
|
147
154
|
private
|
148
155
|
|
149
156
|
def spawn_worker(idx)
|
150
|
-
@
|
157
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
|
151
158
|
|
152
159
|
pid = fork do
|
153
160
|
new_worker = Worker.new index: idx,
|
@@ -165,7 +172,7 @@ module Puma
|
|
165
172
|
exit! 1
|
166
173
|
end
|
167
174
|
|
168
|
-
@
|
175
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
|
169
176
|
pid
|
170
177
|
end
|
171
178
|
end
|
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
module Puma
|
4
4
|
class Cluster < Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
5
8
|
# This class represents a worker process from the perspective of the puma
|
6
9
|
# master process. It contains information about the process and its health
|
7
10
|
# and it exposes methods to control the process via IPC. It does not
|
8
11
|
# include the actual logic executed by the worker process itself. For that,
|
9
12
|
# see Puma::Cluster::Worker.
|
10
|
-
class WorkerHandle
|
13
|
+
class WorkerHandle # :nodoc:
|
11
14
|
def initialize(idx, pid, phase, options)
|
12
15
|
@index = idx
|
13
16
|
@pid = pid
|
data/lib/puma/cluster.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require 'time'
|
3
|
+
require_relative 'runner'
|
4
|
+
require_relative 'util'
|
5
|
+
require_relative 'plugin'
|
6
|
+
require_relative 'cluster/worker_handle'
|
7
|
+
require_relative 'cluster/worker'
|
10
8
|
|
11
9
|
module Puma
|
12
10
|
# This class is instantiated by the `Puma::Launcher` and used
|
@@ -17,8 +15,8 @@ module Puma
|
|
17
15
|
# via the `spawn_workers` method call. Each worker will have it's own
|
18
16
|
# instance of a `Puma::Server`.
|
19
17
|
class Cluster < Runner
|
20
|
-
def initialize(
|
21
|
-
super
|
18
|
+
def initialize(launcher)
|
19
|
+
super(launcher)
|
22
20
|
|
23
21
|
@phase = 0
|
24
22
|
@workers = []
|
@@ -27,6 +25,10 @@ module Puma
|
|
27
25
|
@phased_restart = false
|
28
26
|
end
|
29
27
|
|
28
|
+
# Returns the list of cluster worker handles.
|
29
|
+
# @return [Array<Puma::Cluster::WorkerHandle>]
|
30
|
+
attr_reader :workers
|
31
|
+
|
30
32
|
def stop_workers
|
31
33
|
log "- Gracefully shutting down workers..."
|
32
34
|
@workers.each { |x| x.term }
|
@@ -83,16 +85,14 @@ module Puma
|
|
83
85
|
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
84
86
|
end
|
85
87
|
|
86
|
-
if @options[:fork_worker] &&
|
87
|
-
@workers.all? {|x| x.phase == @phase}
|
88
|
-
|
88
|
+
if @options[:fork_worker] && all_workers_in_phase?
|
89
89
|
@fork_writer << "0\n"
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
93
|
# @version 5.0.0
|
94
94
|
def spawn_worker(idx, master)
|
95
|
-
@
|
95
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer)
|
96
96
|
|
97
97
|
pid = fork { worker(idx, master) }
|
98
98
|
if !pid
|
@@ -101,7 +101,7 @@ module Puma
|
|
101
101
|
exit! 1
|
102
102
|
end
|
103
103
|
|
104
|
-
@
|
104
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer)
|
105
105
|
pid
|
106
106
|
end
|
107
107
|
|
@@ -146,10 +146,22 @@ module Puma
|
|
146
146
|
idx
|
147
147
|
end
|
148
148
|
|
149
|
+
def worker_at(idx)
|
150
|
+
@workers.find { |w| w.index == idx }
|
151
|
+
end
|
152
|
+
|
149
153
|
def all_workers_booted?
|
150
154
|
@workers.count { |w| !w.booted? } == 0
|
151
155
|
end
|
152
156
|
|
157
|
+
def all_workers_in_phase?
|
158
|
+
@workers.all? { |w| w.phase == @phase }
|
159
|
+
end
|
160
|
+
|
161
|
+
def all_workers_idle_timed_out?
|
162
|
+
(@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
|
163
|
+
end
|
164
|
+
|
153
165
|
def check_workers
|
154
166
|
return if @next_check >= Time.now
|
155
167
|
|
@@ -176,10 +188,10 @@ module Puma
|
|
176
188
|
end
|
177
189
|
end
|
178
190
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
].compact.min
|
191
|
+
t = @workers.reject(&:term?)
|
192
|
+
t.map!(&:ping_timeout)
|
193
|
+
|
194
|
+
@next_check = [t.min, @next_check].compact.min
|
183
195
|
end
|
184
196
|
|
185
197
|
def worker(index, master)
|
@@ -209,8 +221,8 @@ module Puma
|
|
209
221
|
stop
|
210
222
|
end
|
211
223
|
|
212
|
-
def phased_restart
|
213
|
-
return false if @options[:preload_app]
|
224
|
+
def phased_restart(refork = false)
|
225
|
+
return false if @options[:preload_app] && !refork
|
214
226
|
|
215
227
|
@phased_restart = true
|
216
228
|
wakeup!
|
@@ -226,7 +238,7 @@ module Puma
|
|
226
238
|
def stop_blocked
|
227
239
|
@status = :stop if @status == :run
|
228
240
|
wakeup!
|
229
|
-
@control
|
241
|
+
@control&.stop true
|
230
242
|
Process.waitall
|
231
243
|
end
|
232
244
|
|
@@ -248,24 +260,24 @@ module Puma
|
|
248
260
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
249
261
|
worker_status = @workers.map do |w|
|
250
262
|
{
|
251
|
-
started_at: w.started_at
|
263
|
+
started_at: utc_iso8601(w.started_at),
|
252
264
|
pid: w.pid,
|
253
265
|
index: w.index,
|
254
266
|
phase: w.phase,
|
255
267
|
booted: w.booted?,
|
256
|
-
last_checkin: w.last_checkin
|
268
|
+
last_checkin: utc_iso8601(w.last_checkin),
|
257
269
|
last_status: w.last_status,
|
258
270
|
}
|
259
271
|
end
|
260
272
|
|
261
273
|
{
|
262
|
-
started_at: @started_at
|
274
|
+
started_at: utc_iso8601(@started_at),
|
263
275
|
workers: @workers.size,
|
264
276
|
phase: @phase,
|
265
277
|
booted_workers: worker_status.count { |w| w[:booted] },
|
266
278
|
old_workers: old_worker_count,
|
267
279
|
worker_status: worker_status,
|
268
|
-
}
|
280
|
+
}.merge(super)
|
269
281
|
end
|
270
282
|
|
271
283
|
def preload?
|
@@ -274,10 +286,10 @@ module Puma
|
|
274
286
|
|
275
287
|
# @version 5.0.0
|
276
288
|
def fork_worker!
|
277
|
-
if (worker =
|
289
|
+
if (worker = worker_at 0)
|
278
290
|
worker.phase += 1
|
279
291
|
end
|
280
|
-
phased_restart
|
292
|
+
phased_restart(true)
|
281
293
|
end
|
282
294
|
|
283
295
|
# We do this in a separate method to keep the lambda scope
|
@@ -290,7 +302,7 @@ module Puma
|
|
290
302
|
|
291
303
|
# Auto-fork after the specified number of requests.
|
292
304
|
if (fork_requests = @options[:fork_worker].to_i) > 0
|
293
|
-
@
|
305
|
+
@events.register(:ping!) do |w|
|
294
306
|
fork_worker! if w.index == 0 &&
|
295
307
|
w.phase == 0 &&
|
296
308
|
w.last_status[:requests_count] >= fork_requests
|
@@ -336,6 +348,8 @@ module Puma
|
|
336
348
|
def run
|
337
349
|
@status = :run
|
338
350
|
|
351
|
+
@idle_workers = {}
|
352
|
+
|
339
353
|
output_header "cluster"
|
340
354
|
|
341
355
|
# This is aligned with the output from Runner, see Runner#output_header
|
@@ -372,12 +386,12 @@ module Puma
|
|
372
386
|
else
|
373
387
|
log "* Restarts: (\u2714) hot (\u2714) phased"
|
374
388
|
|
375
|
-
unless @
|
389
|
+
unless @config.app_configured?
|
376
390
|
error "No application configured, nothing to run"
|
377
391
|
exit 1
|
378
392
|
end
|
379
393
|
|
380
|
-
@launcher.binder.parse @options[:binds]
|
394
|
+
@launcher.binder.parse @options[:binds]
|
381
395
|
end
|
382
396
|
|
383
397
|
read, @wakeup = Puma::Util.pipe
|
@@ -409,8 +423,9 @@ module Puma
|
|
409
423
|
|
410
424
|
@master_read, @worker_write = read, @wakeup
|
411
425
|
|
412
|
-
@
|
413
|
-
|
426
|
+
@options[:worker_write] = @worker_write
|
427
|
+
|
428
|
+
@config.run_hooks(:before_fork, nil, @log_writer)
|
414
429
|
|
415
430
|
spawn_workers
|
416
431
|
|
@@ -425,6 +440,11 @@ module Puma
|
|
425
440
|
|
426
441
|
while @status == :run
|
427
442
|
begin
|
443
|
+
if all_workers_idle_timed_out?
|
444
|
+
log "- All workers reached idle timeout"
|
445
|
+
break
|
446
|
+
end
|
447
|
+
|
428
448
|
if @phased_restart
|
429
449
|
start_phased_restart
|
430
450
|
@phased_restart = false
|
@@ -445,7 +465,7 @@ module Puma
|
|
445
465
|
|
446
466
|
if req == "b" || req == "f"
|
447
467
|
pid, idx = result.split(':').map(&:to_i)
|
448
|
-
w =
|
468
|
+
w = worker_at idx
|
449
469
|
w.pid = pid if w.pid.nil?
|
450
470
|
end
|
451
471
|
|
@@ -462,22 +482,37 @@ module Puma
|
|
462
482
|
when "t"
|
463
483
|
w.term unless w.term?
|
464
484
|
when "p"
|
465
|
-
|
466
|
-
|
485
|
+
status = result.sub(/^\d+/,'').chomp
|
486
|
+
w.ping!(status)
|
487
|
+
@events.fire(:ping!, w)
|
488
|
+
|
489
|
+
if in_phased_restart && workers_not_booted.positive? && w0 = worker_at(0)
|
490
|
+
w0.ping!(status)
|
491
|
+
@events.fire(:ping!, w0)
|
492
|
+
end
|
493
|
+
|
467
494
|
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
468
|
-
@
|
495
|
+
@events.fire_on_booted!
|
496
|
+
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
469
497
|
booted = true
|
470
498
|
end
|
499
|
+
when "i"
|
500
|
+
if @idle_workers[pid]
|
501
|
+
@idle_workers.delete pid
|
502
|
+
else
|
503
|
+
@idle_workers[pid] = true
|
504
|
+
end
|
471
505
|
end
|
472
506
|
else
|
473
507
|
log "! Out-of-sync worker list, no #{pid} worker"
|
474
508
|
end
|
475
509
|
end
|
510
|
+
|
476
511
|
if in_phased_restart && workers_not_booted.zero?
|
477
512
|
@events.fire_on_booted!
|
513
|
+
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
478
514
|
in_phased_restart = false
|
479
515
|
end
|
480
|
-
|
481
516
|
rescue Interrupt
|
482
517
|
@status = :stop
|
483
518
|
end
|
@@ -506,10 +541,28 @@ module Puma
|
|
506
541
|
# loops thru @workers, removing workers that exited, and calling
|
507
542
|
# `#term` if needed
|
508
543
|
def wait_workers
|
544
|
+
# Reap all children, known workers or otherwise.
|
545
|
+
# If puma has PID 1, as it's common in containerized environments,
|
546
|
+
# then it's responsible for reaping orphaned processes, so we must reap
|
547
|
+
# all our dead children, regardless of whether they are workers we spawned
|
548
|
+
# or some reattached processes.
|
549
|
+
reaped_children = {}
|
550
|
+
loop do
|
551
|
+
begin
|
552
|
+
pid, status = Process.wait2(-1, Process::WNOHANG)
|
553
|
+
break unless pid
|
554
|
+
reaped_children[pid] = status
|
555
|
+
rescue Errno::ECHILD
|
556
|
+
break
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
509
560
|
@workers.reject! do |w|
|
510
561
|
next false if w.pid.nil?
|
511
562
|
begin
|
512
|
-
|
563
|
+
# When `fork_worker` is enabled, some worker may not be direct children, but grand children.
|
564
|
+
# Because of this they won't be reaped by `Process.wait2(-1)`, so we need to check them individually)
|
565
|
+
if reaped_children.delete(w.pid) || (@options[:fork_worker] && Process.wait(w.pid, Process::WNOHANG))
|
513
566
|
true
|
514
567
|
else
|
515
568
|
w.term if w.term?
|
@@ -526,6 +579,11 @@ module Puma
|
|
526
579
|
end
|
527
580
|
end
|
528
581
|
end
|
582
|
+
|
583
|
+
# Log unknown children
|
584
|
+
reaped_children.each do |pid, status|
|
585
|
+
log "! reaped unknown child process pid=#{pid} status=#{status}"
|
586
|
+
end
|
529
587
|
end
|
530
588
|
|
531
589
|
# @version 5.0.0
|
@@ -533,14 +591,18 @@ module Puma
|
|
533
591
|
@workers.each do |w|
|
534
592
|
if !w.term? && w.ping_timeout <= Time.now
|
535
593
|
details = if w.booted?
|
536
|
-
"(
|
594
|
+
"(Worker #{w.index} failed to check in within #{@options[:worker_timeout]} seconds)"
|
537
595
|
else
|
538
|
-
"(
|
596
|
+
"(Worker #{w.index} failed to boot within #{@options[:worker_boot_timeout]} seconds)"
|
539
597
|
end
|
540
598
|
log "! Terminating timed out worker #{details}: #{w.pid}"
|
541
599
|
w.kill
|
542
600
|
end
|
543
601
|
end
|
544
602
|
end
|
603
|
+
|
604
|
+
def idle_timed_out_worker_pids
|
605
|
+
@idle_workers.keys
|
606
|
+
end
|
545
607
|
end
|
546
608
|
end
|