puma 8.0.3-java → 8.1.0-java
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 +115 -0
- data/README.md +20 -5
- data/docs/6.0-Upgrade.md +1 -1
- data/docs/8.0-Upgrade.md +2 -2
- data/docs/deployment.md +3 -3
- data/docs/fork_worker.md +34 -4
- data/docs/kubernetes.md +3 -3
- data/docs/plugins.md +2 -2
- data/docs/puma_support.md +78 -0
- data/docs/rails_dev_mode.md +2 -2
- data/docs/restart.md +4 -3
- data/docs/signals.md +2 -2
- data/docs/stats.md +5 -5
- data/docs/systemd.md +4 -2
- data/docs/testing_benchmarks_local_files.md +1 -1
- data/docs/testing_test_rackup_ci_files.md +2 -2
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +6 -2
- data/ext/puma_http11/http11_parser_common.rl +4 -1
- data/ext/puma_http11/mini_ssl.c +8 -4
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +36 -36
- data/ext/puma_http11/puma_http11.c +24 -0
- data/lib/puma/binder.rb +29 -0
- data/lib/puma/cli.rb +6 -0
- data/lib/puma/client.rb +75 -29
- data/lib/puma/client_env.rb +32 -18
- data/lib/puma/cluster/worker.rb +109 -33
- data/lib/puma/cluster/worker_handle.rb +16 -0
- data/lib/puma/cluster.rb +173 -44
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +22 -3
- data/lib/puma/const.rb +11 -9
- data/lib/puma/dsl.rb +265 -91
- data/lib/puma/launcher.rb +6 -4
- data/lib/puma/minissl.rb +1 -1
- data/lib/puma/plugin/systemd.rb +1 -1
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/response.rb +13 -5
- data/lib/puma/runner.rb +5 -1
- data/lib/puma/sd_notify.rb +4 -1
- data/lib/puma/server.rb +45 -20
- data/lib/puma/thread_pool.rb +12 -1
- data/lib/puma.rb +1 -1
- metadata +3 -2
data/lib/puma/cluster/worker.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Puma
|
|
|
26
26
|
@app = app
|
|
27
27
|
@server = nil
|
|
28
28
|
@hook_data = {}
|
|
29
|
+
@worker_pids = []
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def run
|
|
@@ -72,12 +73,7 @@ module Puma
|
|
|
72
73
|
|
|
73
74
|
if fork_worker
|
|
74
75
|
restart_server.clear
|
|
75
|
-
|
|
76
|
-
Signal.trap "SIGCHLD" do
|
|
77
|
-
wakeup! if worker_pids.reject! do |p|
|
|
78
|
-
Process.wait(p, Process::WNOHANG) rescue true
|
|
79
|
-
end
|
|
80
|
-
end
|
|
76
|
+
trap_sigchld
|
|
81
77
|
|
|
82
78
|
Thread.new do
|
|
83
79
|
Puma.set_thread_name "wrkr fork"
|
|
@@ -94,13 +90,24 @@ module Puma
|
|
|
94
90
|
elsif idx == 0 # restart server
|
|
95
91
|
restart_server << true << false
|
|
96
92
|
else # fork worker
|
|
97
|
-
|
|
93
|
+
pid = spawn_worker(idx)
|
|
98
94
|
@worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n" rescue nil
|
|
99
95
|
end
|
|
100
96
|
end
|
|
101
97
|
end
|
|
102
98
|
end
|
|
103
99
|
|
|
100
|
+
mold = false
|
|
101
|
+
|
|
102
|
+
if @options[:mold_worker]
|
|
103
|
+
Signal.trap "SIGURG" do
|
|
104
|
+
mold = true
|
|
105
|
+
restart_server.clear
|
|
106
|
+
@server.begin_restart
|
|
107
|
+
restart_server << false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
104
111
|
Signal.trap "SIGTERM" do
|
|
105
112
|
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
|
106
113
|
restart_server.clear
|
|
@@ -117,38 +124,24 @@ module Puma
|
|
|
117
124
|
|
|
118
125
|
while restart_server.pop
|
|
119
126
|
server_thread = @server.run
|
|
127
|
+
# A promotion received before the server was running had no effect.
|
|
128
|
+
@server.begin_restart if mold
|
|
120
129
|
|
|
121
130
|
if @log_writer.debug? && index == 0
|
|
122
131
|
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
|
123
132
|
end
|
|
124
133
|
|
|
125
|
-
|
|
126
|
-
Puma.set_thread_name "stat pld"
|
|
127
|
-
base_payload = "#{PIPE_PING}#{Process.pid}"
|
|
128
|
-
|
|
129
|
-
while true
|
|
130
|
-
begin
|
|
131
|
-
payload = base_payload.dup
|
|
132
|
-
|
|
133
|
-
hsh = @server.stats
|
|
134
|
-
hsh.each do |k, v|
|
|
135
|
-
payload << %Q! "#{k}":#{v || 0},!
|
|
136
|
-
end
|
|
137
|
-
# sub call properly adds 'closing' string
|
|
138
|
-
io << payload.sub(/,\z/, " }\n")
|
|
139
|
-
@server.reset_max
|
|
140
|
-
rescue IOError
|
|
141
|
-
break
|
|
142
|
-
end
|
|
143
|
-
sleep @options[:worker_check_interval]
|
|
144
|
-
end
|
|
145
|
-
end
|
|
134
|
+
start_stat_thread
|
|
146
135
|
server_thread.join
|
|
147
136
|
end
|
|
148
137
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
138
|
+
if mold
|
|
139
|
+
run_mold
|
|
140
|
+
else
|
|
141
|
+
# Invoke any worker shutdown hooks so they can prevent the worker
|
|
142
|
+
# exiting until any background operations are completed
|
|
143
|
+
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
|
144
|
+
end
|
|
152
145
|
ensure
|
|
153
146
|
@worker_write << "#{PIPE_TERM}#{Process.pid}\n" rescue nil
|
|
154
147
|
@worker_write.close
|
|
@@ -156,15 +149,97 @@ module Puma
|
|
|
156
149
|
|
|
157
150
|
private
|
|
158
151
|
|
|
152
|
+
def start_stat_thread
|
|
153
|
+
@stat_thread ||= Thread.new(@worker_write) do |io|
|
|
154
|
+
Puma.set_thread_name "stat pld"
|
|
155
|
+
base_payload = "#{PIPE_PING}#{Process.pid}"
|
|
156
|
+
|
|
157
|
+
while true
|
|
158
|
+
begin
|
|
159
|
+
payload = base_payload.dup
|
|
160
|
+
|
|
161
|
+
hsh = @server.stats
|
|
162
|
+
hsh.each do |k, v|
|
|
163
|
+
payload << %Q! "#{k}":#{v || 0},!
|
|
164
|
+
end
|
|
165
|
+
# sub call properly adds 'closing' string
|
|
166
|
+
io << payload.sub(/,\z/, " }\n")
|
|
167
|
+
@server.reset_max
|
|
168
|
+
rescue SystemCallError, IOError
|
|
169
|
+
break
|
|
170
|
+
end
|
|
171
|
+
sleep @options[:worker_check_interval]
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def run_mold
|
|
177
|
+
$0 = $0.sub("cluster worker", "mold")
|
|
178
|
+
|
|
179
|
+
retired = false
|
|
180
|
+
stopping = false
|
|
181
|
+
stop_read, stop_write = @mold_stop_pipe = Puma::Util.pipe
|
|
182
|
+
Signal.trap "SIGURG" do
|
|
183
|
+
retired = true
|
|
184
|
+
stop_write.write_nonblock(".", exception: false)
|
|
185
|
+
end
|
|
186
|
+
Signal.trap "SIGTERM" do
|
|
187
|
+
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
|
188
|
+
stopping = true
|
|
189
|
+
stop_write.write_nonblock(".", exception: false)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
trap_sigchld
|
|
193
|
+
|
|
194
|
+
@config.run_hooks(:after_mold_promotion, index, @log_writer, @hook_data)
|
|
195
|
+
start_stat_thread
|
|
196
|
+
@worker_write << "#{PIPE_MOLD}#{Process.pid}\n"
|
|
197
|
+
|
|
198
|
+
loop do
|
|
199
|
+
IO.select([@fork_pipe, stop_read])
|
|
200
|
+
# A retired mold must not read indices meant for its replacement.
|
|
201
|
+
break if retired || stopping
|
|
202
|
+
|
|
203
|
+
idx = @fork_pipe.sysread(MOLD_FORK_BYTES).unpack1(MOLD_FORK_FORMAT)
|
|
204
|
+
pid = spawn_worker(idx)
|
|
205
|
+
@worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n"
|
|
206
|
+
rescue EOFError
|
|
207
|
+
break
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if retired
|
|
211
|
+
# Stay alive until stopped, so the workers this mold forked are
|
|
212
|
+
# reaped and the master is woken as they exit.
|
|
213
|
+
stop_read.read(1) until stopping
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
@config.run_hooks(:before_mold_shutdown, index, @log_writer, @hook_data)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def trap_sigchld
|
|
220
|
+
Signal.trap "SIGCHLD" do
|
|
221
|
+
wakeup! if @worker_pids.reject! do |p|
|
|
222
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
159
227
|
def spawn_worker(idx)
|
|
160
228
|
@config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
|
|
161
229
|
|
|
230
|
+
pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
|
|
231
|
+
if @options[:mold_worker]
|
|
232
|
+
pipes[:fork_pipe] = @fork_pipe
|
|
233
|
+
pipes[:wakeup] = @wakeup
|
|
234
|
+
end
|
|
235
|
+
|
|
162
236
|
pid = fork do
|
|
237
|
+
@mold_stop_pipe&.each(&:close)
|
|
238
|
+
|
|
163
239
|
new_worker = Worker.new index: idx,
|
|
164
240
|
master: master,
|
|
165
241
|
launcher: @launcher,
|
|
166
|
-
pipes:
|
|
167
|
-
worker_write: @worker_write },
|
|
242
|
+
pipes: pipes,
|
|
168
243
|
app: @app
|
|
169
244
|
new_worker.run
|
|
170
245
|
end
|
|
@@ -175,6 +250,7 @@ module Puma
|
|
|
175
250
|
exit! 1
|
|
176
251
|
end
|
|
177
252
|
|
|
253
|
+
@worker_pids << pid
|
|
178
254
|
@config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
|
|
179
255
|
pid
|
|
180
256
|
end
|
|
@@ -25,6 +25,7 @@ module Puma
|
|
|
25
25
|
@last_checkin = Time.now
|
|
26
26
|
@last_status = {}
|
|
27
27
|
@term = false
|
|
28
|
+
@mold = false
|
|
28
29
|
@worker_max = Array.new WORKER_MAX_KEYS.length, 0
|
|
29
30
|
end
|
|
30
31
|
|
|
@@ -54,6 +55,21 @@ module Puma
|
|
|
54
55
|
@term
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
def mold?
|
|
59
|
+
@mold
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def mold!
|
|
63
|
+
@mold = true
|
|
64
|
+
Process.kill "URG", @pid
|
|
65
|
+
rescue Errno::ESRCH
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def retire!
|
|
69
|
+
Process.kill "URG", @pid
|
|
70
|
+
rescue Errno::ESRCH
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
def ping!(status)
|
|
58
74
|
hsh = {}
|
|
59
75
|
k, v = nil, nil
|
data/lib/puma/cluster.rb
CHANGED
|
@@ -15,6 +15,9 @@ module Puma
|
|
|
15
15
|
# via the `spawn_workers` method call. Each worker will have it's own
|
|
16
16
|
# instance of a `Puma::Server`.
|
|
17
17
|
class Cluster < Runner
|
|
18
|
+
MOLD_FORK_FORMAT = "L"
|
|
19
|
+
MOLD_FORK_BYTES = 4
|
|
20
|
+
|
|
18
21
|
def initialize(launcher)
|
|
19
22
|
super(launcher)
|
|
20
23
|
|
|
@@ -24,6 +27,11 @@ module Puma
|
|
|
24
27
|
|
|
25
28
|
@worker_max = [] # keeps track of 'max' stat values
|
|
26
29
|
@pending_phased_restart = false
|
|
30
|
+
@promoted_mold = nil
|
|
31
|
+
@mold = nil
|
|
32
|
+
@retired_molds = []
|
|
33
|
+
@mold_ready = false
|
|
34
|
+
@mold_threshold_index = 0
|
|
27
35
|
end
|
|
28
36
|
|
|
29
37
|
# Returns the list of cluster worker handles.
|
|
@@ -32,12 +40,12 @@ module Puma
|
|
|
32
40
|
|
|
33
41
|
def stop_workers
|
|
34
42
|
log "- Gracefully shutting down workers..."
|
|
35
|
-
@workers.each { |x| x.term }
|
|
43
|
+
(@workers + molds).each { |x| x.term }
|
|
36
44
|
|
|
37
45
|
begin
|
|
38
46
|
loop do
|
|
39
47
|
wait_workers
|
|
40
|
-
break if @workers.reject {|w| w.pid.nil?}.empty?
|
|
48
|
+
break if @workers.reject {|w| w.pid.nil?}.empty? && molds.empty?
|
|
41
49
|
sleep 0.2
|
|
42
50
|
end
|
|
43
51
|
rescue Interrupt
|
|
@@ -69,7 +77,7 @@ module Puma
|
|
|
69
77
|
|
|
70
78
|
def spawn_workers
|
|
71
79
|
diff = @options[:workers] - @workers.size
|
|
72
|
-
return if diff < 1
|
|
80
|
+
return if diff < 1 || @promoted_mold
|
|
73
81
|
|
|
74
82
|
master = Process.pid
|
|
75
83
|
if @options[:fork_worker]
|
|
@@ -82,6 +90,9 @@ module Puma
|
|
|
82
90
|
if @options[:fork_worker] && idx != 0
|
|
83
91
|
@fork_writer << "#{idx}\n"
|
|
84
92
|
pid = nil
|
|
93
|
+
elsif @mold
|
|
94
|
+
@fork_writer << [idx].pack(MOLD_FORK_FORMAT)
|
|
95
|
+
pid = nil
|
|
85
96
|
else
|
|
86
97
|
pid = spawn_worker(idx, master)
|
|
87
98
|
end
|
|
@@ -168,6 +179,8 @@ module Puma
|
|
|
168
179
|
end
|
|
169
180
|
|
|
170
181
|
def all_workers_idle_timed_out?
|
|
182
|
+
return false if @pending_phased_restart || @workers.empty?
|
|
183
|
+
|
|
171
184
|
(@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
|
|
172
185
|
end
|
|
173
186
|
|
|
@@ -206,6 +219,10 @@ module Puma
|
|
|
206
219
|
t.map!(&:ping_timeout)
|
|
207
220
|
|
|
208
221
|
@next_check = [t.min, @next_check].compact.min
|
|
222
|
+
if @options[:fork_worker] && @workers.any?(&:term?)
|
|
223
|
+
# Orphaned workers cannot wake the master through worker 0 as they exit.
|
|
224
|
+
@next_check = [@next_check, Time.now + 0.1].min
|
|
225
|
+
end
|
|
209
226
|
end
|
|
210
227
|
|
|
211
228
|
def worker(index, master)
|
|
@@ -216,7 +233,7 @@ module Puma
|
|
|
216
233
|
@fork_writer.close
|
|
217
234
|
|
|
218
235
|
pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
|
|
219
|
-
if
|
|
236
|
+
if refork?
|
|
220
237
|
pipes[:fork_pipe] = @fork_pipe
|
|
221
238
|
pipes[:wakeup] = @wakeup
|
|
222
239
|
end
|
|
@@ -299,14 +316,22 @@ module Puma
|
|
|
299
316
|
@options[:preload_app]
|
|
300
317
|
end
|
|
301
318
|
|
|
319
|
+
def refork?
|
|
320
|
+
@options[:fork_worker] || @options[:mold_worker]
|
|
321
|
+
end
|
|
322
|
+
|
|
302
323
|
# @version 5.0.0
|
|
303
324
|
def fork_worker!
|
|
304
325
|
if (worker = worker_at 0)
|
|
305
|
-
worker.phase
|
|
326
|
+
worker.phase = @phase + 1
|
|
306
327
|
end
|
|
307
328
|
phased_restart(true)
|
|
308
329
|
end
|
|
309
330
|
|
|
331
|
+
def mold_worker!
|
|
332
|
+
phased_restart(true)
|
|
333
|
+
end
|
|
334
|
+
|
|
310
335
|
# We do this in a separate method to keep the lambda scope
|
|
311
336
|
# of the signals handlers as small as possible.
|
|
312
337
|
def setup_signals
|
|
@@ -325,6 +350,24 @@ module Puma
|
|
|
325
350
|
end
|
|
326
351
|
end
|
|
327
352
|
|
|
353
|
+
if @options[:mold_worker]
|
|
354
|
+
Signal.trap "SIGURG" do
|
|
355
|
+
mold_worker!
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
@events.register(:ping!) do |w|
|
|
359
|
+
threshold = @options[:mold_worker][@mold_threshold_index]
|
|
360
|
+
next unless threshold &&
|
|
361
|
+
!w.mold? &&
|
|
362
|
+
!@pending_phased_restart &&
|
|
363
|
+
w.last_status[:requests_count] >= threshold &&
|
|
364
|
+
all_workers_in_phase?
|
|
365
|
+
|
|
366
|
+
@mold_threshold_index += 1
|
|
367
|
+
mold_worker!
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
328
371
|
Signal.trap "SIGCHLD" do
|
|
329
372
|
wakeup!
|
|
330
373
|
end
|
|
@@ -375,7 +418,7 @@ module Puma
|
|
|
375
418
|
|
|
376
419
|
before = Thread.list.reject(&fork_safe)
|
|
377
420
|
|
|
378
|
-
log "* Restarts: (\u2714) hot (\u2716) phased (#{
|
|
421
|
+
log "* Restarts: (\u2714) hot (\u2716) phased (#{refork? ? "\u2714" : "\u2716"}) refork"
|
|
379
422
|
log "* Preloading application"
|
|
380
423
|
load_and_bind
|
|
381
424
|
|
|
@@ -393,7 +436,7 @@ module Puma
|
|
|
393
436
|
end
|
|
394
437
|
end
|
|
395
438
|
else
|
|
396
|
-
log "* Restarts: (\u2714) hot (\u2714) phased (#{
|
|
439
|
+
log "* Restarts: (\u2714) hot (\u2714) phased (#{refork? ? "\u2714" : "\u2716"}) refork"
|
|
397
440
|
|
|
398
441
|
unless @config.app_configured?
|
|
399
442
|
error "No application configured, nothing to run"
|
|
@@ -437,6 +480,9 @@ module Puma
|
|
|
437
480
|
|
|
438
481
|
@config.run_hooks(:before_fork, nil, @log_writer)
|
|
439
482
|
|
|
483
|
+
# Reparent workers to the master if their mold exits.
|
|
484
|
+
Puma.enable_child_subreaper if @options[:mold_worker]
|
|
485
|
+
|
|
440
486
|
spawn_workers
|
|
441
487
|
|
|
442
488
|
Signal.trap "SIGINT" do
|
|
@@ -446,7 +492,6 @@ module Puma
|
|
|
446
492
|
begin
|
|
447
493
|
booted = false
|
|
448
494
|
in_phased_restart = false
|
|
449
|
-
workers_not_booted = @options[:workers]
|
|
450
495
|
|
|
451
496
|
while @status == :run
|
|
452
497
|
begin
|
|
@@ -455,15 +500,11 @@ module Puma
|
|
|
455
500
|
break
|
|
456
501
|
end
|
|
457
502
|
|
|
458
|
-
if @pending_phased_restart
|
|
503
|
+
if @pending_phased_restart && prepare_phased_restart
|
|
459
504
|
start_phased_restart(@pending_phased_restart == :refork)
|
|
460
505
|
|
|
461
506
|
in_phased_restart = @pending_phased_restart
|
|
462
507
|
@pending_phased_restart = false
|
|
463
|
-
|
|
464
|
-
workers_not_booted = @options[:workers]
|
|
465
|
-
# worker 0 is not restarted on refork
|
|
466
|
-
workers_not_booted -= 1 if in_phased_restart == :refork
|
|
467
508
|
end
|
|
468
509
|
|
|
469
510
|
check_workers(in_phased_restart == :refork)
|
|
@@ -479,20 +520,28 @@ module Puma
|
|
|
479
520
|
|
|
480
521
|
result = read.gets
|
|
481
522
|
pid = result.to_i
|
|
523
|
+
w = (@workers + molds).find { |x| x.pid == pid }
|
|
482
524
|
|
|
483
|
-
if req == PIPE_BOOT || req == PIPE_FORK
|
|
525
|
+
if !w && (req == PIPE_BOOT || req == PIPE_FORK)
|
|
484
526
|
pid, idx = result.split(':').map(&:to_i)
|
|
485
527
|
w = worker_at idx
|
|
486
|
-
w
|
|
528
|
+
if w && w.pid.nil?
|
|
529
|
+
w.pid = pid
|
|
530
|
+
else
|
|
531
|
+
w = nil
|
|
532
|
+
begin
|
|
533
|
+
Process.kill "KILL", pid
|
|
534
|
+
rescue Errno::ESRCH
|
|
535
|
+
end
|
|
536
|
+
end
|
|
487
537
|
end
|
|
488
538
|
|
|
489
|
-
if w
|
|
539
|
+
if w
|
|
490
540
|
case req
|
|
491
541
|
when PIPE_BOOT
|
|
492
542
|
w.boot!
|
|
493
543
|
log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
|
|
494
544
|
@next_check = Time.now
|
|
495
|
-
workers_not_booted -= 1
|
|
496
545
|
when PIPE_EXTERNAL_TERM
|
|
497
546
|
# external term, see worker method, Signal.trap "SIGTERM"
|
|
498
547
|
w.term!
|
|
@@ -503,7 +552,7 @@ module Puma
|
|
|
503
552
|
w.ping!(status)
|
|
504
553
|
@events.fire(:ping!, w)
|
|
505
554
|
|
|
506
|
-
if in_phased_restart && @options[:fork_worker] &&
|
|
555
|
+
if in_phased_restart && @options[:fork_worker] && w0 = worker_at(0)
|
|
507
556
|
w0.ping!(status)
|
|
508
557
|
@events.fire(:ping!, w0)
|
|
509
558
|
end
|
|
@@ -519,13 +568,17 @@ module Puma
|
|
|
519
568
|
else
|
|
520
569
|
idle_workers[pid] = true
|
|
521
570
|
end
|
|
571
|
+
when PIPE_MOLD
|
|
572
|
+
activate_mold(w)
|
|
573
|
+
@next_check = Time.now
|
|
522
574
|
end
|
|
523
575
|
else
|
|
524
576
|
log "! Out-of-sync worker list, no #{pid} worker"
|
|
525
577
|
end
|
|
526
578
|
end
|
|
527
579
|
|
|
528
|
-
if in_phased_restart &&
|
|
580
|
+
if in_phased_restart && @workers.size == @options[:workers] && all_workers_booted? && all_workers_in_phase?
|
|
581
|
+
@retired_molds.each(&:term)
|
|
529
582
|
@events.fire_after_booted!
|
|
530
583
|
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
|
531
584
|
in_phased_restart = false
|
|
@@ -574,43 +627,119 @@ module Puma
|
|
|
574
627
|
end
|
|
575
628
|
end
|
|
576
629
|
|
|
630
|
+
fork_worker_exited = false
|
|
577
631
|
@workers.reject! do |w|
|
|
578
|
-
next
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (status = reaped_children.delete(w.pid) || Process.wait2(w.pid, Process::WNOHANG)&.last)
|
|
586
|
-
w.process_status = status
|
|
587
|
-
@config.run_hooks(:after_worker_shutdown, w, @log_writer)
|
|
588
|
-
true
|
|
589
|
-
else
|
|
590
|
-
w.term if w.term?
|
|
591
|
-
nil
|
|
592
|
-
end
|
|
593
|
-
rescue Errno::ECHILD
|
|
594
|
-
begin
|
|
595
|
-
Process.kill(0, w.pid)
|
|
596
|
-
# child still alive but has another parent (e.g., using fork_worker)
|
|
597
|
-
w.term if w.term?
|
|
598
|
-
false
|
|
599
|
-
rescue Errno::ESRCH, Errno::EPERM
|
|
600
|
-
true # child is already terminated
|
|
601
|
-
end
|
|
632
|
+
next w.term? if w.pid.nil?
|
|
633
|
+
|
|
634
|
+
status = reap(w, reaped_children)
|
|
635
|
+
fork_worker_exited = true if status && @options[:fork_worker] && w.index == 0
|
|
636
|
+
if status.is_a?(Process::Status)
|
|
637
|
+
w.process_status = status
|
|
638
|
+
@config.run_hooks(:after_worker_shutdown, w, @log_writer)
|
|
602
639
|
end
|
|
640
|
+
status
|
|
603
641
|
end
|
|
604
642
|
|
|
643
|
+
clear_pending_workers if fork_worker_exited
|
|
644
|
+
@promoted_mold = nil if @promoted_mold && reap(@promoted_mold, reaped_children)
|
|
645
|
+
if @mold && reap(@mold, reaped_children)
|
|
646
|
+
@mold = nil
|
|
647
|
+
clear_pending_workers
|
|
648
|
+
end
|
|
649
|
+
@retired_molds.reject! { |m| reap(m, reaped_children) }
|
|
650
|
+
|
|
605
651
|
# Log unknown children
|
|
606
652
|
reaped_children.each do |pid, status|
|
|
607
653
|
log "! reaped unknown child process pid=#{pid} status=#{status}"
|
|
608
654
|
end
|
|
609
655
|
end
|
|
610
656
|
|
|
657
|
+
def clear_pending_workers
|
|
658
|
+
@workers.reject! { |w| w.pid.nil? }
|
|
659
|
+
while @fork_pipe.read_nonblock(4096, exception: false).is_a?(String)
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
# Returns the process status once the process has exited, true if it has
|
|
664
|
+
# exited but its status cannot be collected, or nil if it is still running.
|
|
665
|
+
def reap(handle, reaped_children)
|
|
666
|
+
# We may need to check the PID individually because:
|
|
667
|
+
# 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
|
|
668
|
+
# `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
|
|
669
|
+
# 2. When `fork_worker` or `mold_worker` is enabled, some workers may not be direct children,
|
|
670
|
+
# but grandchildren. Because of this they won't be reaped by `Process.wait2(-1)`.
|
|
671
|
+
status = reaped_children.delete(handle.pid) || Process.wait2(handle.pid, Process::WNOHANG)&.last
|
|
672
|
+
handle.term if !status && handle.term?
|
|
673
|
+
status
|
|
674
|
+
rescue Errno::ECHILD
|
|
675
|
+
begin
|
|
676
|
+
Process.kill(0, handle.pid)
|
|
677
|
+
# child still alive but has another parent (e.g., using fork_worker)
|
|
678
|
+
handle.term if handle.term?
|
|
679
|
+
nil
|
|
680
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
681
|
+
true # child is already terminated
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def molds
|
|
686
|
+
[@promoted_mold, @mold, *@retired_molds].compact
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Returns true once workers can be restarted. With `mold_worker`, a refork
|
|
690
|
+
# first promotes a new mold and waits for it, while a phased restart
|
|
691
|
+
# retires all molds so workers are forked from the master.
|
|
692
|
+
def prepare_phased_restart
|
|
693
|
+
return true unless @options[:mold_worker]
|
|
694
|
+
|
|
695
|
+
if @pending_phased_restart != :refork
|
|
696
|
+
retire_molds
|
|
697
|
+
@mold_ready = false
|
|
698
|
+
@mold_threshold_index = 0
|
|
699
|
+
true
|
|
700
|
+
elsif @mold_ready
|
|
701
|
+
@mold_ready = false
|
|
702
|
+
true
|
|
703
|
+
else
|
|
704
|
+
promote_mold unless @promoted_mold
|
|
705
|
+
false
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def promote_mold
|
|
710
|
+
candidate = @workers.select { |w| w.booted? && !w.term? }
|
|
711
|
+
.max_by { |w| w.last_status[:requests_count].to_i }
|
|
712
|
+
return unless candidate
|
|
713
|
+
|
|
714
|
+
log "- Promoting worker #{candidate.index} (PID: #{candidate.pid}) to mold"
|
|
715
|
+
@workers.delete candidate
|
|
716
|
+
@promoted_mold = candidate
|
|
717
|
+
candidate.mold!
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def activate_mold(mold)
|
|
721
|
+
return unless mold == @promoted_mold
|
|
722
|
+
|
|
723
|
+
log "- Mold (PID: #{mold.pid}) ready"
|
|
724
|
+
@promoted_mold = nil
|
|
725
|
+
retire_molds
|
|
726
|
+
@mold = mold
|
|
727
|
+
@mold_ready = true
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# A promoted mold has forked no workers, so it is stopped now. An active
|
|
731
|
+
# mold stops forking, and is stopped once the restart completes.
|
|
732
|
+
def retire_molds
|
|
733
|
+
@promoted_mold&.term
|
|
734
|
+
@mold&.retire!
|
|
735
|
+
@retired_molds.concat [@promoted_mold, @mold].compact
|
|
736
|
+
@promoted_mold = nil
|
|
737
|
+
@mold = nil
|
|
738
|
+
end
|
|
739
|
+
|
|
611
740
|
# @version 5.0.0
|
|
612
741
|
def timeout_workers
|
|
613
|
-
@workers.each do |w|
|
|
742
|
+
(@workers + molds).each do |w|
|
|
614
743
|
if !w.term? && w.ping_timeout <= Time.now
|
|
615
744
|
details = if w.booted?
|
|
616
745
|
"(Worker #{w.index} failed to check in within #{@options[:worker_timeout]} seconds)"
|
data/lib/puma/commonlogger.rb
CHANGED
|
@@ -73,7 +73,7 @@ module Puma
|
|
|
73
73
|
env[SERVER_PROTOCOL],
|
|
74
74
|
now - began_at ]
|
|
75
75
|
|
|
76
|
-
write(msg)
|
|
76
|
+
write(env, msg)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def log(env, status, header, began_at)
|
|
@@ -92,10 +92,10 @@ module Puma
|
|
|
92
92
|
length,
|
|
93
93
|
now - began_at ]
|
|
94
94
|
|
|
95
|
-
write(msg)
|
|
95
|
+
write(env, msg)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
def write(msg)
|
|
98
|
+
def write(env, msg)
|
|
99
99
|
logger = @logger || env['rack.errors']
|
|
100
100
|
|
|
101
101
|
# Standard library logger doesn't support write but it supports << which actually
|