puma 3.12.0 → 5.3.1
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 +1413 -439
- 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/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 +84 -109
- 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 +4 -2
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +262 -87
- 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/app/status.rb +68 -49
- data/lib/puma/binder.rb +197 -144
- data/lib/puma/cli.rb +17 -15
- data/lib/puma/client.rb +257 -226
- data/lib/puma/cluster/worker.rb +176 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/cluster.rb +223 -212
- data/lib/puma/commonlogger.rb +4 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +41 -19
- data/lib/puma/control_cli.rb +117 -73
- data/lib/puma/detect.rb +26 -3
- data/lib/puma/dsl.rb +531 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +57 -31
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +182 -70
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/minissl.rb +149 -48
- data/lib/puma/null_io.rb +15 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +8 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +4 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +87 -316
- data/lib/puma/request.rb +456 -0
- data/lib/puma/runner.rb +33 -52
- data/lib/puma/server.rb +288 -679
- data/lib/puma/single.rb +13 -67
- data/lib/puma/state_file.rb +10 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +131 -81
- data/lib/puma/util.rb +14 -6
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +8 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -29
- 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 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -39
- 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
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
2
4
|
require 'puma/util'
|
3
5
|
require 'puma/plugin'
|
6
|
+
require 'puma/cluster/worker_handle'
|
7
|
+
require 'puma/cluster/worker'
|
4
8
|
|
5
9
|
require 'time'
|
6
10
|
|
@@ -9,24 +13,17 @@ module Puma
|
|
9
13
|
# to boot and serve a Ruby application when puma "workers" are needed
|
10
14
|
# i.e. when using multi-processes. For example `$ puma -w 5`
|
11
15
|
#
|
12
|
-
# At the core of this class is running an instance of `Puma::Server` which
|
13
|
-
# gets created via the `start_server` method from the `Puma::Runner` class
|
14
|
-
# that this inherits from.
|
15
|
-
#
|
16
16
|
# An instance of this class will spawn the number of processes passed in
|
17
17
|
# via the `spawn_workers` method call. Each worker will have it's own
|
18
18
|
# instance of a `Puma::Server`.
|
19
19
|
class Cluster < Runner
|
20
|
-
WORKER_CHECK_INTERVAL = 5
|
21
|
-
|
22
20
|
def initialize(cli, events)
|
23
21
|
super cli, events
|
24
22
|
|
25
23
|
@phase = 0
|
26
24
|
@workers = []
|
27
|
-
@next_check =
|
25
|
+
@next_check = Time.now
|
28
26
|
|
29
|
-
@phased_state = :idle
|
30
27
|
@phased_restart = false
|
31
28
|
end
|
32
29
|
|
@@ -35,13 +32,18 @@ module Puma
|
|
35
32
|
@workers.each { |x| x.term }
|
36
33
|
|
37
34
|
begin
|
38
|
-
|
35
|
+
loop do
|
36
|
+
wait_workers
|
37
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
38
|
+
sleep 0.2
|
39
|
+
end
|
39
40
|
rescue Interrupt
|
40
41
|
log "! Cancelled waiting for workers"
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
45
|
def start_phased_restart
|
46
|
+
@events.fire_on_restart!
|
45
47
|
@phase += 1
|
46
48
|
log "- Starting phased worker restart, phase: #{@phase}"
|
47
49
|
|
@@ -58,98 +60,49 @@ module Puma
|
|
58
60
|
@workers.each { |x| x.hup }
|
59
61
|
end
|
60
62
|
|
61
|
-
class Worker
|
62
|
-
def initialize(idx, pid, phase, options)
|
63
|
-
@index = idx
|
64
|
-
@pid = pid
|
65
|
-
@phase = phase
|
66
|
-
@stage = :started
|
67
|
-
@signal = "TERM"
|
68
|
-
@options = options
|
69
|
-
@first_term_sent = nil
|
70
|
-
@last_checkin = Time.now
|
71
|
-
@last_status = '{}'
|
72
|
-
@dead = false
|
73
|
-
end
|
74
|
-
|
75
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
76
|
-
|
77
|
-
def booted?
|
78
|
-
@stage == :booted
|
79
|
-
end
|
80
|
-
|
81
|
-
def boot!
|
82
|
-
@last_checkin = Time.now
|
83
|
-
@stage = :booted
|
84
|
-
end
|
85
|
-
|
86
|
-
def dead?
|
87
|
-
@dead
|
88
|
-
end
|
89
|
-
|
90
|
-
def dead!
|
91
|
-
@dead = true
|
92
|
-
end
|
93
|
-
|
94
|
-
def ping!(status)
|
95
|
-
@last_checkin = Time.now
|
96
|
-
@last_status = status
|
97
|
-
end
|
98
|
-
|
99
|
-
def ping_timeout?(which)
|
100
|
-
Time.now - @last_checkin > which
|
101
|
-
end
|
102
|
-
|
103
|
-
def term
|
104
|
-
begin
|
105
|
-
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
106
|
-
@signal = "KILL"
|
107
|
-
else
|
108
|
-
@first_term_sent ||= Time.now
|
109
|
-
end
|
110
|
-
|
111
|
-
Process.kill @signal, @pid
|
112
|
-
rescue Errno::ESRCH
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def kill
|
117
|
-
Process.kill "KILL", @pid
|
118
|
-
rescue Errno::ESRCH
|
119
|
-
end
|
120
|
-
|
121
|
-
def hup
|
122
|
-
Process.kill "HUP", @pid
|
123
|
-
rescue Errno::ESRCH
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
63
|
def spawn_workers
|
128
64
|
diff = @options[:workers] - @workers.size
|
129
65
|
return if diff < 1
|
130
66
|
|
131
67
|
master = Process.pid
|
68
|
+
if @options[:fork_worker]
|
69
|
+
@fork_writer << "-1\n"
|
70
|
+
end
|
132
71
|
|
133
72
|
diff.times do
|
134
73
|
idx = next_worker_index
|
135
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
136
74
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
75
|
+
if @options[:fork_worker] && idx != 0
|
76
|
+
@fork_writer << "#{idx}\n"
|
77
|
+
pid = nil
|
78
|
+
else
|
79
|
+
pid = spawn_worker(idx, master)
|
142
80
|
end
|
143
81
|
|
144
82
|
debug "Spawned worker: #{pid}"
|
145
|
-
@workers <<
|
83
|
+
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
84
|
+
end
|
146
85
|
|
147
|
-
|
86
|
+
if @options[:fork_worker] &&
|
87
|
+
@workers.all? {|x| x.phase == @phase}
|
88
|
+
|
89
|
+
@fork_writer << "0\n"
|
148
90
|
end
|
91
|
+
end
|
149
92
|
|
150
|
-
|
151
|
-
|
93
|
+
# @version 5.0.0
|
94
|
+
def spawn_worker(idx, master)
|
95
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
96
|
+
|
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
|
152
102
|
end
|
103
|
+
|
104
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
105
|
+
pid
|
153
106
|
end
|
154
107
|
|
155
108
|
def cull_workers
|
@@ -162,11 +115,12 @@ module Puma
|
|
162
115
|
debug "Workers to cull: #{workers_to_cull.inspect}"
|
163
116
|
|
164
117
|
workers_to_cull.each do |worker|
|
165
|
-
log "- Worker #{worker.index} (
|
118
|
+
log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
|
166
119
|
worker.term
|
167
120
|
end
|
168
121
|
end
|
169
122
|
|
123
|
+
# @!attribute [r] next_worker_index
|
170
124
|
def next_worker_index
|
171
125
|
all_positions = 0...@options[:workers]
|
172
126
|
occupied_positions = @workers.map { |w| w.index }
|
@@ -178,35 +132,13 @@ module Puma
|
|
178
132
|
@workers.count { |w| !w.booted? } == 0
|
179
133
|
end
|
180
134
|
|
181
|
-
def check_workers
|
182
|
-
return if
|
183
|
-
|
184
|
-
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
185
|
-
|
186
|
-
any = false
|
187
|
-
|
188
|
-
@workers.each do |w|
|
189
|
-
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
190
|
-
if w.ping_timeout?(@options[:worker_timeout])
|
191
|
-
log "! Terminating timed out worker: #{w.pid}"
|
192
|
-
w.kill
|
193
|
-
any = true
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
# If we killed any timed out workers, try to catch them
|
198
|
-
# during this loop by giving the kernel time to kill them.
|
199
|
-
sleep 1 if any
|
200
|
-
|
201
|
-
while @workers.any?
|
202
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
203
|
-
break unless pid
|
204
|
-
|
205
|
-
@workers.delete_if { |w| w.pid == pid }
|
206
|
-
end
|
135
|
+
def check_workers
|
136
|
+
return if @next_check >= Time.now
|
207
137
|
|
208
|
-
@
|
138
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
209
139
|
|
140
|
+
timeout_workers
|
141
|
+
wait_workers
|
210
142
|
cull_workers
|
211
143
|
spawn_workers
|
212
144
|
|
@@ -218,15 +150,18 @@ module Puma
|
|
218
150
|
w = @workers.find { |x| x.phase != @phase }
|
219
151
|
|
220
152
|
if w
|
221
|
-
|
222
|
-
|
223
|
-
|
153
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
154
|
+
unless w.term?
|
155
|
+
w.term
|
156
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
224
157
|
end
|
225
|
-
|
226
|
-
w.term
|
227
|
-
log "- #{w.signal} sent to #{w.pid}..."
|
228
158
|
end
|
229
159
|
end
|
160
|
+
|
161
|
+
@next_check = [
|
162
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
163
|
+
@next_check
|
164
|
+
].compact.min
|
230
165
|
end
|
231
166
|
|
232
167
|
def wakeup!
|
@@ -240,77 +175,25 @@ module Puma
|
|
240
175
|
end
|
241
176
|
|
242
177
|
def worker(index, master)
|
243
|
-
title = "puma: cluster worker #{index}: #{master}"
|
244
|
-
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
245
|
-
$0 = title
|
246
|
-
|
247
|
-
Signal.trap "SIGINT", "IGNORE"
|
248
|
-
|
249
178
|
@workers = []
|
179
|
+
|
250
180
|
@master_read.close
|
251
181
|
@suicide_pipe.close
|
182
|
+
@fork_writer.close
|
252
183
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
end
|
258
|
-
|
259
|
-
# If we're not running under a Bundler context, then
|
260
|
-
# report the info about the context we will be using
|
261
|
-
if !ENV['BUNDLE_GEMFILE']
|
262
|
-
if File.exist?("Gemfile")
|
263
|
-
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
264
|
-
elsif File.exist?("gems.rb")
|
265
|
-
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
# Invoke any worker boot hooks so they can get
|
270
|
-
# things in shape before booting the app.
|
271
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
272
|
-
|
273
|
-
server = start_server
|
274
|
-
|
275
|
-
Signal.trap "SIGTERM" do
|
276
|
-
server.stop
|
277
|
-
end
|
278
|
-
|
279
|
-
begin
|
280
|
-
@worker_write << "b#{Process.pid}\n"
|
281
|
-
rescue SystemCallError, IOError
|
282
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
283
|
-
STDERR.puts "Master seems to have exited, exiting."
|
284
|
-
return
|
285
|
-
end
|
286
|
-
|
287
|
-
Thread.new(@worker_write) do |io|
|
288
|
-
base_payload = "p#{Process.pid}"
|
289
|
-
|
290
|
-
while true
|
291
|
-
sleep WORKER_CHECK_INTERVAL
|
292
|
-
begin
|
293
|
-
b = server.backlog || 0
|
294
|
-
r = server.running || 0
|
295
|
-
t = server.pool_capacity || 0
|
296
|
-
m = server.max_threads || 0
|
297
|
-
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
298
|
-
io << payload
|
299
|
-
rescue IOError
|
300
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
301
|
-
break
|
302
|
-
end
|
303
|
-
end
|
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
|
304
188
|
end
|
305
189
|
|
306
|
-
server
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
@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
|
314
197
|
end
|
315
198
|
|
316
199
|
def restart
|
@@ -350,20 +233,63 @@ module Puma
|
|
350
233
|
Dir.chdir dir
|
351
234
|
end
|
352
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
|
353
239
|
def stats
|
354
240
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
355
|
-
|
356
|
-
|
357
|
-
|
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
|
+
}
|
358
261
|
end
|
359
262
|
|
360
263
|
def preload?
|
361
264
|
@options[:preload_app]
|
362
265
|
end
|
363
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
|
+
|
364
275
|
# We do this in a separate method to keep the lambda scope
|
365
276
|
# of the signals handlers as small as possible.
|
366
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
|
+
|
367
293
|
Signal.trap "SIGCHLD" do
|
368
294
|
wakeup!
|
369
295
|
end
|
@@ -388,10 +314,13 @@ module Puma
|
|
388
314
|
log "Early termination of worker"
|
389
315
|
exit! 0
|
390
316
|
else
|
317
|
+
@launcher.close_binder_listeners
|
318
|
+
|
391
319
|
stop_workers
|
392
320
|
stop
|
393
|
-
|
394
|
-
raise
|
321
|
+
@events.fire_on_stopped!
|
322
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
323
|
+
exit 0 # Clean exit, workers were stopped
|
395
324
|
end
|
396
325
|
end
|
397
326
|
end
|
@@ -401,15 +330,25 @@ module Puma
|
|
401
330
|
|
402
331
|
output_header "cluster"
|
403
332
|
|
404
|
-
|
405
|
-
|
406
|
-
before = Thread.list
|
333
|
+
# This is aligned with the output from Runner, see Runner#output_header
|
334
|
+
log "* Workers: #{@options[:workers]}"
|
407
335
|
|
408
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"
|
409
348
|
log "* Preloading application"
|
410
349
|
load_and_bind
|
411
350
|
|
412
|
-
after = Thread.list
|
351
|
+
after = Thread.list.reject(&fork_safe)
|
413
352
|
|
414
353
|
if after.size > before.size
|
415
354
|
threads = (after - before)
|
@@ -423,7 +362,7 @@ module Puma
|
|
423
362
|
end
|
424
363
|
end
|
425
364
|
else
|
426
|
-
log "*
|
365
|
+
log "* Restarts: (\u2714) hot (\u2714) phased"
|
427
366
|
|
428
367
|
unless @launcher.config.app_configured?
|
429
368
|
error "No application configured, nothing to run"
|
@@ -444,12 +383,13 @@ module Puma
|
|
444
383
|
#
|
445
384
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
446
385
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
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
|
453
393
|
|
454
394
|
redirect_io
|
455
395
|
|
@@ -461,7 +401,8 @@ module Puma
|
|
461
401
|
|
462
402
|
@master_read, @worker_write = read, @wakeup
|
463
403
|
|
464
|
-
@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]
|
465
406
|
|
466
407
|
spawn_workers
|
467
408
|
|
@@ -469,48 +410,67 @@ module Puma
|
|
469
410
|
stop
|
470
411
|
end
|
471
412
|
|
472
|
-
@launcher.events.fire_on_booted!
|
473
|
-
|
474
413
|
begin
|
475
|
-
|
414
|
+
booted = false
|
415
|
+
in_phased_restart = false
|
416
|
+
workers_not_booted = @options[:workers]
|
476
417
|
|
477
418
|
while @status == :run
|
478
419
|
begin
|
479
420
|
if @phased_restart
|
480
421
|
start_phased_restart
|
481
422
|
@phased_restart = false
|
423
|
+
in_phased_restart = true
|
424
|
+
workers_not_booted = @options[:workers]
|
482
425
|
end
|
483
426
|
|
484
|
-
check_workers
|
485
|
-
|
486
|
-
force_check = false
|
427
|
+
check_workers
|
487
428
|
|
488
|
-
res = IO.select([read], nil, nil,
|
429
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
489
430
|
|
490
431
|
if res
|
491
432
|
req = read.read_nonblock(1)
|
492
433
|
|
434
|
+
@next_check = Time.now if req == "!"
|
493
435
|
next if !req || req == "!"
|
494
436
|
|
495
437
|
result = read.gets
|
496
438
|
pid = result.to_i
|
497
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
|
+
|
498
446
|
if w = @workers.find { |x| x.pid == pid }
|
499
447
|
case req
|
500
448
|
when "b"
|
501
449
|
w.boot!
|
502
|
-
log "- Worker #{w.index} (
|
503
|
-
|
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
|
504
456
|
when "t"
|
505
|
-
w.
|
506
|
-
force_check = true
|
457
|
+
w.term unless w.term?
|
507
458
|
when "p"
|
508
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
|
509
465
|
end
|
510
466
|
else
|
511
467
|
log "! Out-of-sync worker list, no #{pid} worker"
|
512
468
|
end
|
513
469
|
end
|
470
|
+
if in_phased_restart && workers_not_booted.zero?
|
471
|
+
@events.fire_on_booted!
|
472
|
+
in_phased_restart = false
|
473
|
+
end
|
514
474
|
|
515
475
|
rescue Interrupt
|
516
476
|
@status = :stop
|
@@ -525,5 +485,56 @@ module Puma
|
|
525
485
|
@wakeup.close
|
526
486
|
end
|
527
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
|
528
539
|
end
|
529
540
|
end
|
data/lib/puma/commonlogger.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
# Rack::CommonLogger forwards every request to the given +app+, and
|
3
5
|
# logs a line in the
|
4
|
-
# {Apache common log format}[
|
6
|
+
# {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
|
5
7
|
# to the +logger+.
|
6
8
|
#
|
7
9
|
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
|
@@ -14,7 +16,7 @@ module Puma
|
|
14
16
|
# (which is called without arguments in order to make the error appear for
|
15
17
|
# sure)
|
16
18
|
class CommonLogger
|
17
|
-
# Common Log Format:
|
19
|
+
# Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
|
18
20
|
#
|
19
21
|
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
20
22
|
#
|