puma 3.12.6 → 5.3.2
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 +1400 -451
- data/LICENSE +23 -20
- data/README.md +131 -60
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +24 -19
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +38 -13
- data/docs/fork_worker.md +33 -0
- 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/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +20 -10
- data/docs/rails_dev_mode.md +29 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +7 -6
- data/docs/stats.md +142 -0
- data/docs/systemd.md +48 -70
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +27 -0
- data/ext/puma_http11/http11_parser.c +81 -108
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +254 -91
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
- data/ext/puma_http11/puma_http11.c +34 -50
- data/lib/puma.rb +54 -0
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +191 -139
- data/lib/puma/cli.rb +15 -15
- data/lib/puma/client.rb +247 -226
- data/lib/puma/cluster.rb +221 -212
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +32 -20
- data/lib/puma/control_cli.rb +109 -67
- data/lib/puma/detect.rb +24 -3
- data/lib/puma/dsl.rb +519 -121
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +178 -68
- data/lib/puma/minissl.rb +147 -48
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin.rb +6 -12
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +2 -4
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +467 -0
- data/lib/puma/runner.rb +31 -52
- data/lib/puma/server.rb +275 -726
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +8 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +129 -81
- data/lib/puma/util.rb +13 -6
- data/lib/rack/handler/puma.rb +5 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -28
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/cluster.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'puma/runner'
|
4
4
|
require 'puma/util'
|
5
5
|
require 'puma/plugin'
|
6
|
+
require 'puma/cluster/worker_handle'
|
7
|
+
require 'puma/cluster/worker'
|
6
8
|
|
7
9
|
require 'time'
|
8
10
|
|
@@ -11,24 +13,17 @@ module Puma
|
|
11
13
|
# to boot and serve a Ruby application when puma "workers" are needed
|
12
14
|
# i.e. when using multi-processes. For example `$ puma -w 5`
|
13
15
|
#
|
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
16
|
# An instance of this class will spawn the number of processes passed in
|
19
17
|
# via the `spawn_workers` method call. Each worker will have it's own
|
20
18
|
# instance of a `Puma::Server`.
|
21
19
|
class Cluster < Runner
|
22
|
-
WORKER_CHECK_INTERVAL = 5
|
23
|
-
|
24
20
|
def initialize(cli, events)
|
25
21
|
super cli, events
|
26
22
|
|
27
23
|
@phase = 0
|
28
24
|
@workers = []
|
29
|
-
@next_check =
|
25
|
+
@next_check = Time.now
|
30
26
|
|
31
|
-
@phased_state = :idle
|
32
27
|
@phased_restart = false
|
33
28
|
end
|
34
29
|
|
@@ -37,13 +32,18 @@ module Puma
|
|
37
32
|
@workers.each { |x| x.term }
|
38
33
|
|
39
34
|
begin
|
40
|
-
|
35
|
+
loop do
|
36
|
+
wait_workers
|
37
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
38
|
+
sleep 0.2
|
39
|
+
end
|
41
40
|
rescue Interrupt
|
42
41
|
log "! Cancelled waiting for workers"
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
46
45
|
def start_phased_restart
|
46
|
+
@events.fire_on_restart!
|
47
47
|
@phase += 1
|
48
48
|
log "- Starting phased worker restart, phase: #{@phase}"
|
49
49
|
|
@@ -60,98 +60,49 @@ module Puma
|
|
60
60
|
@workers.each { |x| x.hup }
|
61
61
|
end
|
62
62
|
|
63
|
-
class Worker
|
64
|
-
def initialize(idx, pid, phase, options)
|
65
|
-
@index = idx
|
66
|
-
@pid = pid
|
67
|
-
@phase = phase
|
68
|
-
@stage = :started
|
69
|
-
@signal = "TERM"
|
70
|
-
@options = options
|
71
|
-
@first_term_sent = nil
|
72
|
-
@last_checkin = Time.now
|
73
|
-
@last_status = '{}'
|
74
|
-
@dead = false
|
75
|
-
end
|
76
|
-
|
77
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
78
|
-
|
79
|
-
def booted?
|
80
|
-
@stage == :booted
|
81
|
-
end
|
82
|
-
|
83
|
-
def boot!
|
84
|
-
@last_checkin = Time.now
|
85
|
-
@stage = :booted
|
86
|
-
end
|
87
|
-
|
88
|
-
def dead?
|
89
|
-
@dead
|
90
|
-
end
|
91
|
-
|
92
|
-
def dead!
|
93
|
-
@dead = true
|
94
|
-
end
|
95
|
-
|
96
|
-
def ping!(status)
|
97
|
-
@last_checkin = Time.now
|
98
|
-
@last_status = status
|
99
|
-
end
|
100
|
-
|
101
|
-
def ping_timeout?(which)
|
102
|
-
Time.now - @last_checkin > which
|
103
|
-
end
|
104
|
-
|
105
|
-
def term
|
106
|
-
begin
|
107
|
-
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
108
|
-
@signal = "KILL"
|
109
|
-
else
|
110
|
-
@first_term_sent ||= Time.now
|
111
|
-
end
|
112
|
-
|
113
|
-
Process.kill @signal, @pid
|
114
|
-
rescue Errno::ESRCH
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def kill
|
119
|
-
Process.kill "KILL", @pid
|
120
|
-
rescue Errno::ESRCH
|
121
|
-
end
|
122
|
-
|
123
|
-
def hup
|
124
|
-
Process.kill "HUP", @pid
|
125
|
-
rescue Errno::ESRCH
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
63
|
def spawn_workers
|
130
64
|
diff = @options[:workers] - @workers.size
|
131
65
|
return if diff < 1
|
132
66
|
|
133
67
|
master = Process.pid
|
68
|
+
if @options[:fork_worker]
|
69
|
+
@fork_writer << "-1\n"
|
70
|
+
end
|
134
71
|
|
135
72
|
diff.times do
|
136
73
|
idx = next_worker_index
|
137
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
138
74
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
75
|
+
if @options[:fork_worker] && idx != 0
|
76
|
+
@fork_writer << "#{idx}\n"
|
77
|
+
pid = nil
|
78
|
+
else
|
79
|
+
pid = spawn_worker(idx, master)
|
144
80
|
end
|
145
81
|
|
146
82
|
debug "Spawned worker: #{pid}"
|
147
|
-
@workers <<
|
83
|
+
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
84
|
+
end
|
85
|
+
|
86
|
+
if @options[:fork_worker] &&
|
87
|
+
@workers.all? {|x| x.phase == @phase}
|
148
88
|
|
149
|
-
@
|
89
|
+
@fork_writer << "0\n"
|
150
90
|
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @version 5.0.0
|
94
|
+
def spawn_worker(idx, master)
|
95
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
151
96
|
|
152
|
-
|
153
|
-
|
97
|
+
pid = fork { worker(idx, master) }
|
98
|
+
if !pid
|
99
|
+
log "! Complete inability to spawn new workers detected"
|
100
|
+
log "! Seppuku is the only choice."
|
101
|
+
exit! 1
|
154
102
|
end
|
103
|
+
|
104
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
105
|
+
pid
|
155
106
|
end
|
156
107
|
|
157
108
|
def cull_workers
|
@@ -164,11 +115,12 @@ module Puma
|
|
164
115
|
debug "Workers to cull: #{workers_to_cull.inspect}"
|
165
116
|
|
166
117
|
workers_to_cull.each do |worker|
|
167
|
-
log "- Worker #{worker.index} (
|
118
|
+
log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
|
168
119
|
worker.term
|
169
120
|
end
|
170
121
|
end
|
171
122
|
|
123
|
+
# @!attribute [r] next_worker_index
|
172
124
|
def next_worker_index
|
173
125
|
all_positions = 0...@options[:workers]
|
174
126
|
occupied_positions = @workers.map { |w| w.index }
|
@@ -180,35 +132,13 @@ module Puma
|
|
180
132
|
@workers.count { |w| !w.booted? } == 0
|
181
133
|
end
|
182
134
|
|
183
|
-
def check_workers
|
184
|
-
return if
|
185
|
-
|
186
|
-
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
187
|
-
|
188
|
-
any = false
|
189
|
-
|
190
|
-
@workers.each do |w|
|
191
|
-
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
192
|
-
if w.ping_timeout?(@options[:worker_timeout])
|
193
|
-
log "! Terminating timed out worker: #{w.pid}"
|
194
|
-
w.kill
|
195
|
-
any = true
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
# If we killed any timed out workers, try to catch them
|
200
|
-
# during this loop by giving the kernel time to kill them.
|
201
|
-
sleep 1 if any
|
202
|
-
|
203
|
-
while @workers.any?
|
204
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
205
|
-
break unless pid
|
206
|
-
|
207
|
-
@workers.delete_if { |w| w.pid == pid }
|
208
|
-
end
|
135
|
+
def check_workers
|
136
|
+
return if @next_check >= Time.now
|
209
137
|
|
210
|
-
@
|
138
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
211
139
|
|
140
|
+
timeout_workers
|
141
|
+
wait_workers
|
212
142
|
cull_workers
|
213
143
|
spawn_workers
|
214
144
|
|
@@ -220,15 +150,18 @@ module Puma
|
|
220
150
|
w = @workers.find { |x| x.phase != @phase }
|
221
151
|
|
222
152
|
if w
|
223
|
-
|
224
|
-
|
225
|
-
|
153
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
154
|
+
unless w.term?
|
155
|
+
w.term
|
156
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
226
157
|
end
|
227
|
-
|
228
|
-
w.term
|
229
|
-
log "- #{w.signal} sent to #{w.pid}..."
|
230
158
|
end
|
231
159
|
end
|
160
|
+
|
161
|
+
@next_check = [
|
162
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
163
|
+
@next_check
|
164
|
+
].compact.min
|
232
165
|
end
|
233
166
|
|
234
167
|
def wakeup!
|
@@ -242,77 +175,25 @@ module Puma
|
|
242
175
|
end
|
243
176
|
|
244
177
|
def worker(index, master)
|
245
|
-
title = "puma: cluster worker #{index}: #{master}"
|
246
|
-
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
247
|
-
$0 = title
|
248
|
-
|
249
|
-
Signal.trap "SIGINT", "IGNORE"
|
250
|
-
|
251
178
|
@workers = []
|
179
|
+
|
252
180
|
@master_read.close
|
253
181
|
@suicide_pipe.close
|
182
|
+
@fork_writer.close
|
254
183
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
184
|
+
pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
|
185
|
+
if @options[:fork_worker]
|
186
|
+
pipes[:fork_pipe] = @fork_pipe
|
187
|
+
pipes[:wakeup] = @wakeup
|
259
188
|
end
|
260
189
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
# Invoke any worker boot hooks so they can get
|
272
|
-
# things in shape before booting the app.
|
273
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
274
|
-
|
275
|
-
server = start_server
|
276
|
-
|
277
|
-
Signal.trap "SIGTERM" do
|
278
|
-
server.stop
|
279
|
-
end
|
280
|
-
|
281
|
-
begin
|
282
|
-
@worker_write << "b#{Process.pid}\n"
|
283
|
-
rescue SystemCallError, IOError
|
284
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
285
|
-
STDERR.puts "Master seems to have exited, exiting."
|
286
|
-
return
|
287
|
-
end
|
288
|
-
|
289
|
-
Thread.new(@worker_write) do |io|
|
290
|
-
base_payload = "p#{Process.pid}"
|
291
|
-
|
292
|
-
while true
|
293
|
-
sleep WORKER_CHECK_INTERVAL
|
294
|
-
begin
|
295
|
-
b = server.backlog || 0
|
296
|
-
r = server.running || 0
|
297
|
-
t = server.pool_capacity || 0
|
298
|
-
m = server.max_threads || 0
|
299
|
-
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
300
|
-
io << payload
|
301
|
-
rescue IOError
|
302
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
303
|
-
break
|
304
|
-
end
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
server.run.join
|
309
|
-
|
310
|
-
# Invoke any worker shutdown hooks so they can prevent the worker
|
311
|
-
# exiting until any background operations are completed
|
312
|
-
@launcher.config.run_hooks :before_worker_shutdown, index
|
313
|
-
ensure
|
314
|
-
@worker_write << "t#{Process.pid}\n" rescue nil
|
315
|
-
@worker_write.close
|
190
|
+
server = start_server if preload?
|
191
|
+
new_worker = Worker.new index: index,
|
192
|
+
master: master,
|
193
|
+
launcher: @launcher,
|
194
|
+
pipes: pipes,
|
195
|
+
server: server
|
196
|
+
new_worker.run
|
316
197
|
end
|
317
198
|
|
318
199
|
def restart
|
@@ -352,20 +233,63 @@ module Puma
|
|
352
233
|
Dir.chdir dir
|
353
234
|
end
|
354
235
|
|
236
|
+
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
237
|
+
# the master process.
|
238
|
+
# @!attribute [r] stats
|
355
239
|
def stats
|
356
240
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
357
|
-
|
358
|
-
|
359
|
-
|
241
|
+
worker_status = @workers.map do |w|
|
242
|
+
{
|
243
|
+
started_at: w.started_at.utc.iso8601,
|
244
|
+
pid: w.pid,
|
245
|
+
index: w.index,
|
246
|
+
phase: w.phase,
|
247
|
+
booted: w.booted?,
|
248
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
249
|
+
last_status: w.last_status,
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
{
|
254
|
+
started_at: @started_at.utc.iso8601,
|
255
|
+
workers: @workers.size,
|
256
|
+
phase: @phase,
|
257
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
258
|
+
old_workers: old_worker_count,
|
259
|
+
worker_status: worker_status,
|
260
|
+
}
|
360
261
|
end
|
361
262
|
|
362
263
|
def preload?
|
363
264
|
@options[:preload_app]
|
364
265
|
end
|
365
266
|
|
267
|
+
# @version 5.0.0
|
268
|
+
def fork_worker!
|
269
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
270
|
+
worker.phase += 1
|
271
|
+
end
|
272
|
+
phased_restart
|
273
|
+
end
|
274
|
+
|
366
275
|
# We do this in a separate method to keep the lambda scope
|
367
276
|
# of the signals handlers as small as possible.
|
368
277
|
def setup_signals
|
278
|
+
if @options[:fork_worker]
|
279
|
+
Signal.trap "SIGURG" do
|
280
|
+
fork_worker!
|
281
|
+
end
|
282
|
+
|
283
|
+
# Auto-fork after the specified number of requests.
|
284
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
285
|
+
@launcher.events.register(:ping!) do |w|
|
286
|
+
fork_worker! if w.index == 0 &&
|
287
|
+
w.phase == 0 &&
|
288
|
+
w.last_status[:requests_count] >= fork_requests
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
369
293
|
Signal.trap "SIGCHLD" do
|
370
294
|
wakeup!
|
371
295
|
end
|
@@ -390,10 +314,13 @@ module Puma
|
|
390
314
|
log "Early termination of worker"
|
391
315
|
exit! 0
|
392
316
|
else
|
317
|
+
@launcher.close_binder_listeners
|
318
|
+
|
393
319
|
stop_workers
|
394
320
|
stop
|
395
|
-
|
396
|
-
raise
|
321
|
+
@events.fire_on_stopped!
|
322
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
323
|
+
exit 0 # Clean exit, workers were stopped
|
397
324
|
end
|
398
325
|
end
|
399
326
|
end
|
@@ -403,15 +330,25 @@ module Puma
|
|
403
330
|
|
404
331
|
output_header "cluster"
|
405
332
|
|
406
|
-
|
407
|
-
|
408
|
-
before = Thread.list
|
333
|
+
# This is aligned with the output from Runner, see Runner#output_header
|
334
|
+
log "* Workers: #{@options[:workers]}"
|
409
335
|
|
410
336
|
if preload?
|
337
|
+
# Threads explicitly marked as fork safe will be ignored. Used in Rails,
|
338
|
+
# but may be used by anyone. Note that we need to explicit
|
339
|
+
# Process::Waiter check here because there's a bug in Ruby 2.6 and below
|
340
|
+
# where calling thread_variable_get on a Process::Waiter will segfault.
|
341
|
+
# We can drop that clause once those versions of Ruby are no longer
|
342
|
+
# supported.
|
343
|
+
fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
|
344
|
+
|
345
|
+
before = Thread.list.reject(&fork_safe)
|
346
|
+
|
347
|
+
log "* Restarts: (\u2714) hot (\u2716) phased"
|
411
348
|
log "* Preloading application"
|
412
349
|
load_and_bind
|
413
350
|
|
414
|
-
after = Thread.list
|
351
|
+
after = Thread.list.reject(&fork_safe)
|
415
352
|
|
416
353
|
if after.size > before.size
|
417
354
|
threads = (after - before)
|
@@ -425,7 +362,7 @@ module Puma
|
|
425
362
|
end
|
426
363
|
end
|
427
364
|
else
|
428
|
-
log "*
|
365
|
+
log "* Restarts: (\u2714) hot (\u2714) phased"
|
429
366
|
|
430
367
|
unless @launcher.config.app_configured?
|
431
368
|
error "No application configured, nothing to run"
|
@@ -446,12 +383,13 @@ module Puma
|
|
446
383
|
#
|
447
384
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
448
385
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
386
|
+
# Separate pipe used by worker 0 to receive commands to
|
387
|
+
# fork new worker processes.
|
388
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
389
|
+
|
390
|
+
log "Use Ctrl-C to stop"
|
391
|
+
|
392
|
+
single_worker_warning
|
455
393
|
|
456
394
|
redirect_io
|
457
395
|
|
@@ -463,7 +401,8 @@ module Puma
|
|
463
401
|
|
464
402
|
@master_read, @worker_write = read, @wakeup
|
465
403
|
|
466
|
-
@launcher.config.run_hooks :before_fork, nil
|
404
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
405
|
+
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
467
406
|
|
468
407
|
spawn_workers
|
469
408
|
|
@@ -471,48 +410,67 @@ module Puma
|
|
471
410
|
stop
|
472
411
|
end
|
473
412
|
|
474
|
-
@launcher.events.fire_on_booted!
|
475
|
-
|
476
413
|
begin
|
477
|
-
|
414
|
+
booted = false
|
415
|
+
in_phased_restart = false
|
416
|
+
workers_not_booted = @options[:workers]
|
478
417
|
|
479
418
|
while @status == :run
|
480
419
|
begin
|
481
420
|
if @phased_restart
|
482
421
|
start_phased_restart
|
483
422
|
@phased_restart = false
|
423
|
+
in_phased_restart = true
|
424
|
+
workers_not_booted = @options[:workers]
|
484
425
|
end
|
485
426
|
|
486
|
-
check_workers
|
427
|
+
check_workers
|
487
428
|
|
488
|
-
|
489
|
-
|
490
|
-
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
429
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
491
430
|
|
492
431
|
if res
|
493
432
|
req = read.read_nonblock(1)
|
494
433
|
|
434
|
+
@next_check = Time.now if req == "!"
|
495
435
|
next if !req || req == "!"
|
496
436
|
|
497
437
|
result = read.gets
|
498
438
|
pid = result.to_i
|
499
439
|
|
440
|
+
if req == "b" || req == "f"
|
441
|
+
pid, idx = result.split(':').map(&:to_i)
|
442
|
+
w = @workers.find {|x| x.index == idx}
|
443
|
+
w.pid = pid if w.pid.nil?
|
444
|
+
end
|
445
|
+
|
500
446
|
if w = @workers.find { |x| x.pid == pid }
|
501
447
|
case req
|
502
448
|
when "b"
|
503
449
|
w.boot!
|
504
|
-
log "- Worker #{w.index} (
|
505
|
-
|
450
|
+
log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
|
451
|
+
@next_check = Time.now
|
452
|
+
workers_not_booted -= 1
|
453
|
+
when "e"
|
454
|
+
# external term, see worker method, Signal.trap "SIGTERM"
|
455
|
+
w.instance_variable_set :@term, true
|
506
456
|
when "t"
|
507
|
-
w.
|
508
|
-
force_check = true
|
457
|
+
w.term unless w.term?
|
509
458
|
when "p"
|
510
459
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
460
|
+
@launcher.events.fire(:ping!, w)
|
461
|
+
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
462
|
+
@launcher.events.fire_on_booted!
|
463
|
+
booted = true
|
464
|
+
end
|
511
465
|
end
|
512
466
|
else
|
513
467
|
log "! Out-of-sync worker list, no #{pid} worker"
|
514
468
|
end
|
515
469
|
end
|
470
|
+
if in_phased_restart && workers_not_booted.zero?
|
471
|
+
@events.fire_on_booted!
|
472
|
+
in_phased_restart = false
|
473
|
+
end
|
516
474
|
|
517
475
|
rescue Interrupt
|
518
476
|
@status = :stop
|
@@ -527,5 +485,56 @@ module Puma
|
|
527
485
|
@wakeup.close
|
528
486
|
end
|
529
487
|
end
|
488
|
+
|
489
|
+
private
|
490
|
+
|
491
|
+
def single_worker_warning
|
492
|
+
return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
|
493
|
+
|
494
|
+
log "! WARNING: Detected running cluster mode with 1 worker."
|
495
|
+
log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
|
496
|
+
log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
|
497
|
+
log "! Set the `silence_single_worker_warning` option to silence this warning message."
|
498
|
+
end
|
499
|
+
|
500
|
+
# loops thru @workers, removing workers that exited, and calling
|
501
|
+
# `#term` if needed
|
502
|
+
def wait_workers
|
503
|
+
@workers.reject! do |w|
|
504
|
+
next false if w.pid.nil?
|
505
|
+
begin
|
506
|
+
if Process.wait(w.pid, Process::WNOHANG)
|
507
|
+
true
|
508
|
+
else
|
509
|
+
w.term if w.term?
|
510
|
+
nil
|
511
|
+
end
|
512
|
+
rescue Errno::ECHILD
|
513
|
+
begin
|
514
|
+
Process.kill(0, w.pid)
|
515
|
+
# child still alive but has another parent (e.g., using fork_worker)
|
516
|
+
w.term if w.term?
|
517
|
+
false
|
518
|
+
rescue Errno::ESRCH, Errno::EPERM
|
519
|
+
true # child is already terminated
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
# @version 5.0.0
|
526
|
+
def timeout_workers
|
527
|
+
@workers.each do |w|
|
528
|
+
if !w.term? && w.ping_timeout <= Time.now
|
529
|
+
details = if w.booted?
|
530
|
+
"(worker failed to check in within #{@options[:worker_timeout]} seconds)"
|
531
|
+
else
|
532
|
+
"(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
|
533
|
+
end
|
534
|
+
log "! Terminating timed out worker #{details}: #{w.pid}"
|
535
|
+
w.kill
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
530
539
|
end
|
531
540
|
end
|