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