puma 3.0.0.rc1 → 5.0.0.beta1
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.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/{History.txt → History.md} +703 -70
- data/LICENSE +23 -20
- data/README.md +173 -163
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
- data/docs/fork_worker.md +31 -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 +13 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +1 -1
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +57 -3
- data/docs/systemd.md +228 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +16 -0
- data/ext/puma_http11/http11_parser.c +287 -468
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +159 -10
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/ext/puma_http11/puma_http11.c +6 -38
- data/lib/puma.rb +25 -5
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +53 -26
- data/lib/puma/binder.rb +150 -119
- data/lib/puma/cli.rb +56 -38
- data/lib/puma/client.rb +277 -80
- data/lib/puma/cluster.rb +326 -130
- data/lib/puma/commonlogger.rb +21 -20
- data/lib/puma/configuration.rb +160 -161
- data/lib/puma/const.rb +50 -47
- data/lib/puma/control_cli.rb +104 -63
- data/lib/puma/detect.rb +13 -1
- data/lib/puma/dsl.rb +463 -114
- data/lib/puma/events.rb +22 -13
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/launcher.rb +195 -105
- data/lib/puma/minissl.rb +110 -4
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +9 -14
- data/lib/puma/plugin.rb +32 -12
- data/lib/puma/plugin/tmp_restart.rb +19 -6
- data/lib/puma/rack/builder.rb +7 -5
- data/lib/puma/rack/urlmap.rb +11 -8
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +242 -32
- data/lib/puma/runner.rb +41 -30
- data/lib/puma/server.rb +265 -183
- data/lib/puma/single.rb +22 -63
- data/lib/puma/state_file.rb +9 -2
- data/lib/puma/thread_pool.rb +179 -68
- data/lib/puma/util.rb +3 -11
- data/lib/rack/handler/puma.rb +60 -11
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +1 -2
- metadata +35 -99
- data/COPYING +0 -55
- data/Gemfile +0 -13
- data/Manifest.txt +0 -79
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/capistrano.rb +0 -94
- data/lib/puma/compat.rb +0 -18
- 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_18.rb +0 -56
- data/lib/puma/rack/backports/uri/common_192.rb +0 -52
- data/lib/puma/rack/backports/uri/common_193.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -32
- data/puma.gemspec +0 -52
- data/tools/jungle/README.md +0 -9
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -394
- data/tools/jungle/init.d/run-puma +0 -3
data/lib/puma/cluster.rb
CHANGED
@@ -1,15 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
4
|
+
require 'puma/util'
|
5
|
+
require 'puma/plugin'
|
6
|
+
|
7
|
+
require 'time'
|
8
|
+
require 'json'
|
2
9
|
|
3
10
|
module Puma
|
11
|
+
# This class is instantiated by the `Puma::Launcher` and used
|
12
|
+
# to boot and serve a Ruby application when puma "workers" are needed
|
13
|
+
# i.e. when using multi-processes. For example `$ puma -w 5`
|
14
|
+
#
|
15
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
16
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
17
|
+
# that this inherits from.
|
18
|
+
#
|
19
|
+
# An instance of this class will spawn the number of processes passed in
|
20
|
+
# via the `spawn_workers` method call. Each worker will have it's own
|
21
|
+
# instance of a `Puma::Server`.
|
4
22
|
class Cluster < Runner
|
5
23
|
def initialize(cli, events)
|
6
24
|
super cli, events
|
7
25
|
|
8
26
|
@phase = 0
|
9
27
|
@workers = []
|
10
|
-
@next_check =
|
28
|
+
@next_check = Time.now
|
11
29
|
|
12
|
-
@phased_state = :idle
|
13
30
|
@phased_restart = false
|
14
31
|
end
|
15
32
|
|
@@ -18,7 +35,11 @@ module Puma
|
|
18
35
|
@workers.each { |x| x.term }
|
19
36
|
|
20
37
|
begin
|
21
|
-
|
38
|
+
loop do
|
39
|
+
wait_workers
|
40
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
41
|
+
sleep 0.2
|
42
|
+
end
|
22
43
|
rescue Interrupt
|
23
44
|
log "! Cancelled waiting for workers"
|
24
45
|
end
|
@@ -30,10 +51,9 @@ module Puma
|
|
30
51
|
|
31
52
|
# Be sure to change the directory again before loading
|
32
53
|
# the app. This way we can pick up new code.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
54
|
+
dir = @launcher.restart_dir
|
55
|
+
log "+ Changing to #{dir}"
|
56
|
+
Dir.chdir dir
|
37
57
|
end
|
38
58
|
|
39
59
|
def redirect_io
|
@@ -51,11 +71,14 @@ module Puma
|
|
51
71
|
@signal = "TERM"
|
52
72
|
@options = options
|
53
73
|
@first_term_sent = nil
|
74
|
+
@started_at = Time.now
|
54
75
|
@last_checkin = Time.now
|
55
|
-
@
|
76
|
+
@last_status = {}
|
77
|
+
@term = false
|
56
78
|
end
|
57
79
|
|
58
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin
|
80
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
81
|
+
attr_writer :pid, :phase
|
59
82
|
|
60
83
|
def booted?
|
61
84
|
@stage == :booted
|
@@ -66,20 +89,21 @@ module Puma
|
|
66
89
|
@stage = :booted
|
67
90
|
end
|
68
91
|
|
69
|
-
def
|
70
|
-
@
|
71
|
-
end
|
72
|
-
|
73
|
-
def dead!
|
74
|
-
@dead = true
|
92
|
+
def term?
|
93
|
+
@term
|
75
94
|
end
|
76
95
|
|
77
|
-
def ping!
|
96
|
+
def ping!(status)
|
78
97
|
@last_checkin = Time.now
|
98
|
+
@last_status = JSON.parse(status, symbolize_names: true)
|
79
99
|
end
|
80
100
|
|
81
|
-
def ping_timeout
|
82
|
-
|
101
|
+
def ping_timeout
|
102
|
+
@last_checkin +
|
103
|
+
(booted? ?
|
104
|
+
@options[:worker_timeout] :
|
105
|
+
@options[:worker_boot_timeout]
|
106
|
+
)
|
83
107
|
end
|
84
108
|
|
85
109
|
def term
|
@@ -87,17 +111,17 @@ module Puma
|
|
87
111
|
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
88
112
|
@signal = "KILL"
|
89
113
|
else
|
114
|
+
@term ||= true
|
90
115
|
@first_term_sent ||= Time.now
|
91
116
|
end
|
92
|
-
|
93
|
-
Process.kill @signal, @pid
|
117
|
+
Process.kill @signal, @pid if @pid
|
94
118
|
rescue Errno::ESRCH
|
95
119
|
end
|
96
120
|
end
|
97
121
|
|
98
122
|
def kill
|
99
|
-
|
100
|
-
|
123
|
+
@signal = 'KILL'
|
124
|
+
term
|
101
125
|
end
|
102
126
|
|
103
127
|
def hup
|
@@ -108,22 +132,60 @@ module Puma
|
|
108
132
|
|
109
133
|
def spawn_workers
|
110
134
|
diff = @options[:workers] - @workers.size
|
135
|
+
return if diff < 1
|
111
136
|
|
112
137
|
master = Process.pid
|
138
|
+
if @options[:fork_worker]
|
139
|
+
@fork_writer << "-1\n"
|
140
|
+
end
|
113
141
|
|
114
142
|
diff.times do
|
115
143
|
idx = next_worker_index
|
116
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
117
144
|
|
118
|
-
|
145
|
+
if @options[:fork_worker] && idx != 0
|
146
|
+
@fork_writer << "#{idx}\n"
|
147
|
+
pid = nil
|
148
|
+
else
|
149
|
+
pid = spawn_worker(idx, master)
|
150
|
+
end
|
151
|
+
|
119
152
|
debug "Spawned worker: #{pid}"
|
120
153
|
@workers << Worker.new(idx, pid, @phase, @options)
|
154
|
+
end
|
155
|
+
|
156
|
+
if @options[:fork_worker] &&
|
157
|
+
@workers.all? {|x| x.phase == @phase}
|
158
|
+
|
159
|
+
@fork_writer << "0\n"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def spawn_worker(idx, master)
|
164
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
121
165
|
|
122
|
-
|
166
|
+
pid = fork { worker(idx, master) }
|
167
|
+
if !pid
|
168
|
+
log "! Complete inability to spawn new workers detected"
|
169
|
+
log "! Seppuku is the only choice."
|
170
|
+
exit! 1
|
123
171
|
end
|
124
172
|
|
125
|
-
|
126
|
-
|
173
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
174
|
+
pid
|
175
|
+
end
|
176
|
+
|
177
|
+
def cull_workers
|
178
|
+
diff = @workers.size - @options[:workers]
|
179
|
+
return if diff < 1
|
180
|
+
|
181
|
+
debug "Culling #{diff.inspect} workers"
|
182
|
+
|
183
|
+
workers_to_cull = @workers[-diff,diff]
|
184
|
+
debug "Workers to cull: #{workers_to_cull.inspect}"
|
185
|
+
|
186
|
+
workers_to_cull.each do |worker|
|
187
|
+
log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
|
188
|
+
worker.term
|
127
189
|
end
|
128
190
|
end
|
129
191
|
|
@@ -138,35 +200,14 @@ module Puma
|
|
138
200
|
@workers.count { |w| !w.booted? } == 0
|
139
201
|
end
|
140
202
|
|
141
|
-
def check_workers
|
142
|
-
return if
|
143
|
-
|
144
|
-
@next_check = Time.now + 5
|
145
|
-
|
146
|
-
any = false
|
147
|
-
|
148
|
-
@workers.each do |w|
|
149
|
-
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
150
|
-
if w.ping_timeout?(@options[:worker_timeout])
|
151
|
-
log "! Terminating timed out worker: #{w.pid}"
|
152
|
-
w.kill
|
153
|
-
any = true
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# If we killed any timed out workers, try to catch them
|
158
|
-
# during this loop by giving the kernel time to kill them.
|
159
|
-
sleep 1 if any
|
160
|
-
|
161
|
-
while @workers.any?
|
162
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
163
|
-
break unless pid
|
164
|
-
|
165
|
-
@workers.delete_if { |w| w.pid == pid }
|
166
|
-
end
|
203
|
+
def check_workers
|
204
|
+
return if @next_check >= Time.now
|
167
205
|
|
168
|
-
@
|
206
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
169
207
|
|
208
|
+
timeout_workers
|
209
|
+
wait_workers
|
210
|
+
cull_workers
|
170
211
|
spawn_workers
|
171
212
|
|
172
213
|
if all_workers_booted?
|
@@ -177,15 +218,18 @@ module Puma
|
|
177
218
|
w = @workers.find { |x| x.phase != @phase }
|
178
219
|
|
179
220
|
if w
|
180
|
-
|
181
|
-
|
182
|
-
|
221
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
222
|
+
unless w.term?
|
223
|
+
w.term
|
224
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
183
225
|
end
|
184
|
-
|
185
|
-
w.term
|
186
|
-
log "- #{w.signal} sent to #{w.pid}..."
|
187
226
|
end
|
188
227
|
end
|
228
|
+
|
229
|
+
@next_check = [
|
230
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
231
|
+
@next_check
|
232
|
+
].compact.min
|
189
233
|
end
|
190
234
|
|
191
235
|
def wakeup!
|
@@ -194,21 +238,28 @@ module Puma
|
|
194
238
|
begin
|
195
239
|
@wakeup.write "!" unless @wakeup.closed?
|
196
240
|
rescue SystemCallError, IOError
|
241
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
197
242
|
end
|
198
243
|
end
|
199
244
|
|
200
245
|
def worker(index, master)
|
201
|
-
title
|
202
|
-
title
|
246
|
+
title = "puma: cluster worker #{index}: #{master}"
|
247
|
+
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
203
248
|
$0 = title
|
204
249
|
|
205
250
|
Signal.trap "SIGINT", "IGNORE"
|
206
251
|
|
252
|
+
fork_worker = @options[:fork_worker] && index == 0
|
253
|
+
|
207
254
|
@workers = []
|
208
|
-
|
209
|
-
|
255
|
+
if !@options[:fork_worker] || fork_worker
|
256
|
+
@master_read.close
|
257
|
+
@suicide_pipe.close
|
258
|
+
@fork_writer.close
|
259
|
+
end
|
210
260
|
|
211
261
|
Thread.new do
|
262
|
+
Puma.set_thread_name "worker check pipe"
|
212
263
|
IO.select [@check_pipe]
|
213
264
|
log "! Detected parent died, dying"
|
214
265
|
exit! 1
|
@@ -216,45 +267,82 @@ module Puma
|
|
216
267
|
|
217
268
|
# If we're not running under a Bundler context, then
|
218
269
|
# report the info about the context we will be using
|
219
|
-
if !ENV['BUNDLE_GEMFILE']
|
220
|
-
|
270
|
+
if !ENV['BUNDLE_GEMFILE']
|
271
|
+
if File.exist?("Gemfile")
|
272
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
273
|
+
elsif File.exist?("gems.rb")
|
274
|
+
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
275
|
+
end
|
221
276
|
end
|
222
277
|
|
223
278
|
# Invoke any worker boot hooks so they can get
|
224
279
|
# things in shape before booting the app.
|
225
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
280
|
+
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
281
|
+
|
282
|
+
server = @server ||= start_server
|
283
|
+
restart_server = Queue.new << true << false
|
284
|
+
|
285
|
+
if fork_worker
|
286
|
+
restart_server.clear
|
287
|
+
Signal.trap "SIGCHLD" do
|
288
|
+
Process.wait(-1, Process::WNOHANG) rescue nil
|
289
|
+
wakeup!
|
290
|
+
end
|
226
291
|
|
227
|
-
|
292
|
+
Thread.new do
|
293
|
+
Puma.set_thread_name "worker fork pipe"
|
294
|
+
while (idx = @fork_pipe.gets)
|
295
|
+
idx = idx.to_i
|
296
|
+
if idx == -1 # stop server
|
297
|
+
if restart_server.length > 0
|
298
|
+
restart_server.clear
|
299
|
+
server.begin_restart(true)
|
300
|
+
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
301
|
+
nakayoshi_gc
|
302
|
+
end
|
303
|
+
elsif idx == 0 # restart server
|
304
|
+
restart_server << true << false
|
305
|
+
else # fork worker
|
306
|
+
pid = spawn_worker(idx, master)
|
307
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
228
312
|
|
229
313
|
Signal.trap "SIGTERM" do
|
314
|
+
@worker_write << "e#{Process.pid}\n" rescue nil
|
230
315
|
server.stop
|
316
|
+
restart_server << false
|
231
317
|
end
|
232
318
|
|
233
319
|
begin
|
234
|
-
@worker_write << "b#{Process.pid}\n"
|
320
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
235
321
|
rescue SystemCallError, IOError
|
322
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
236
323
|
STDERR.puts "Master seems to have exited, exiting."
|
237
324
|
return
|
238
325
|
end
|
239
326
|
|
240
327
|
Thread.new(@worker_write) do |io|
|
241
|
-
|
328
|
+
Puma.set_thread_name "stat payload"
|
242
329
|
|
243
330
|
while true
|
244
|
-
sleep
|
331
|
+
sleep Const::WORKER_CHECK_INTERVAL
|
245
332
|
begin
|
246
|
-
io <<
|
333
|
+
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
247
334
|
rescue IOError
|
335
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
248
336
|
break
|
249
337
|
end
|
250
338
|
end
|
251
339
|
end
|
252
340
|
|
253
|
-
server.run.join
|
341
|
+
server.run.join while restart_server.pop
|
254
342
|
|
255
343
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
256
344
|
# exiting until any background operations are completed
|
257
|
-
@launcher.config.run_hooks :before_worker_shutdown, index
|
345
|
+
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
258
346
|
ensure
|
259
347
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
260
348
|
@worker_write.close
|
@@ -292,22 +380,101 @@ module Puma
|
|
292
380
|
end
|
293
381
|
|
294
382
|
def reload_worker_directory
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
end
|
383
|
+
dir = @launcher.restart_dir
|
384
|
+
log "+ Changing to #{dir}"
|
385
|
+
Dir.chdir dir
|
299
386
|
end
|
300
387
|
|
388
|
+
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
389
|
+
# the master process.
|
301
390
|
def stats
|
302
391
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
303
|
-
|
304
|
-
|
392
|
+
worker_status = @workers.map do |w|
|
393
|
+
{
|
394
|
+
started_at: w.started_at.utc.iso8601,
|
395
|
+
pid: w.pid,
|
396
|
+
index: w.index,
|
397
|
+
phase: w.phase,
|
398
|
+
booted: w.booted?,
|
399
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
400
|
+
last_status: w.last_status,
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
{
|
405
|
+
started_at: @started_at.utc.iso8601,
|
406
|
+
workers: @workers.size,
|
407
|
+
phase: @phase,
|
408
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
409
|
+
old_workers: old_worker_count,
|
410
|
+
worker_status: worker_status,
|
411
|
+
}
|
305
412
|
end
|
306
413
|
|
307
414
|
def preload?
|
308
415
|
@options[:preload_app]
|
309
416
|
end
|
310
417
|
|
418
|
+
def fork_worker!
|
419
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
420
|
+
worker.phase += 1
|
421
|
+
end
|
422
|
+
phased_restart
|
423
|
+
end
|
424
|
+
|
425
|
+
# We do this in a separate method to keep the lambda scope
|
426
|
+
# of the signals handlers as small as possible.
|
427
|
+
def setup_signals
|
428
|
+
if @options[:fork_worker]
|
429
|
+
Signal.trap "SIGURG" do
|
430
|
+
fork_worker!
|
431
|
+
end
|
432
|
+
|
433
|
+
# Auto-fork after the specified number of requests.
|
434
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
435
|
+
@launcher.events.register(:ping!) do |w|
|
436
|
+
fork_worker! if w.index == 0 &&
|
437
|
+
w.phase == 0 &&
|
438
|
+
w.last_status[:requests_count] >= fork_requests
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
Signal.trap "SIGCHLD" do
|
444
|
+
wakeup!
|
445
|
+
end
|
446
|
+
|
447
|
+
Signal.trap "TTIN" do
|
448
|
+
@options[:workers] += 1
|
449
|
+
wakeup!
|
450
|
+
end
|
451
|
+
|
452
|
+
Signal.trap "TTOU" do
|
453
|
+
@options[:workers] -= 1 if @options[:workers] >= 2
|
454
|
+
wakeup!
|
455
|
+
end
|
456
|
+
|
457
|
+
master_pid = Process.pid
|
458
|
+
|
459
|
+
Signal.trap "SIGTERM" do
|
460
|
+
# The worker installs their own SIGTERM when booted.
|
461
|
+
# Until then, this is run by the worker and the worker
|
462
|
+
# should just exit if they get it.
|
463
|
+
if Process.pid != master_pid
|
464
|
+
log "Early termination of worker"
|
465
|
+
exit! 0
|
466
|
+
else
|
467
|
+
@launcher.close_binder_listeners
|
468
|
+
|
469
|
+
stop_workers
|
470
|
+
stop
|
471
|
+
|
472
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
473
|
+
exit 0 # Clean exit, workers were stopped
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
311
478
|
def run
|
312
479
|
@status = :run
|
313
480
|
|
@@ -347,34 +514,7 @@ module Puma
|
|
347
514
|
|
348
515
|
read, @wakeup = Puma::Util.pipe
|
349
516
|
|
350
|
-
|
351
|
-
wakeup!
|
352
|
-
end
|
353
|
-
|
354
|
-
Signal.trap "TTIN" do
|
355
|
-
@options[:workers] += 1
|
356
|
-
wakeup!
|
357
|
-
end
|
358
|
-
|
359
|
-
Signal.trap "TTOU" do
|
360
|
-
@options[:workers] -= 1 if @options[:workers] >= 2
|
361
|
-
@workers.last.term
|
362
|
-
wakeup!
|
363
|
-
end
|
364
|
-
|
365
|
-
master_pid = Process.pid
|
366
|
-
|
367
|
-
Signal.trap "SIGTERM" do
|
368
|
-
# The worker installs their own SIGTERM when booted.
|
369
|
-
# Until then, this is run by the worker and the worker
|
370
|
-
# should just exit if they get it.
|
371
|
-
if Process.pid != master_pid
|
372
|
-
log "Early termination of worker"
|
373
|
-
exit! 0
|
374
|
-
else
|
375
|
-
stop
|
376
|
-
end
|
377
|
-
end
|
517
|
+
setup_signals
|
378
518
|
|
379
519
|
# Used by the workers to detect if the master process dies.
|
380
520
|
# If select says that @check_pipe is ready, it's because the
|
@@ -383,22 +523,24 @@ module Puma
|
|
383
523
|
#
|
384
524
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
385
525
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
end
|
526
|
+
# Separate pipe used by worker 0 to receive commands to
|
527
|
+
# fork new worker processes.
|
528
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
529
|
+
|
530
|
+
log "Use Ctrl-C to stop"
|
392
531
|
|
393
532
|
redirect_io
|
394
533
|
|
395
|
-
|
534
|
+
Plugins.fire_background
|
396
535
|
|
397
536
|
@launcher.write_state
|
398
537
|
|
538
|
+
start_control
|
539
|
+
|
399
540
|
@master_read, @worker_write = read, @wakeup
|
400
541
|
|
401
|
-
@launcher.config.run_hooks :before_fork, nil
|
542
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
543
|
+
nakayoshi_gc
|
402
544
|
|
403
545
|
spawn_workers
|
404
546
|
|
@@ -411,41 +553,50 @@ module Puma
|
|
411
553
|
begin
|
412
554
|
while @status == :run
|
413
555
|
begin
|
414
|
-
|
556
|
+
if @phased_restart
|
557
|
+
start_phased_restart
|
558
|
+
@phased_restart = false
|
559
|
+
end
|
415
560
|
|
416
|
-
|
561
|
+
check_workers
|
562
|
+
|
563
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
417
564
|
|
418
565
|
if res
|
419
566
|
req = read.read_nonblock(1)
|
420
567
|
|
568
|
+
@next_check = Time.now if req == "!"
|
421
569
|
next if !req || req == "!"
|
422
570
|
|
423
|
-
|
571
|
+
result = read.gets
|
572
|
+
pid = result.to_i
|
573
|
+
|
574
|
+
if req == "b" || req == "f"
|
575
|
+
pid, idx = result.split(':').map(&:to_i)
|
576
|
+
w = @workers.find {|x| x.index == idx}
|
577
|
+
w.pid = pid if w.pid.nil?
|
578
|
+
end
|
424
579
|
|
425
580
|
if w = @workers.find { |x| x.pid == pid }
|
426
581
|
case req
|
427
582
|
when "b"
|
428
583
|
w.boot!
|
429
584
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
430
|
-
|
585
|
+
@next_check = Time.now
|
586
|
+
when "e"
|
587
|
+
# external term, see worker method, Signal.trap "SIGTERM"
|
588
|
+
w.instance_variable_set :@term, true
|
431
589
|
when "t"
|
432
|
-
w.
|
433
|
-
force_check = true
|
590
|
+
w.term unless w.term?
|
434
591
|
when "p"
|
435
|
-
w.ping!
|
592
|
+
w.ping!(result.sub(/^\d+/,'').chomp)
|
593
|
+
@launcher.events.fire(:ping!, w)
|
436
594
|
end
|
437
595
|
else
|
438
596
|
log "! Out-of-sync worker list, no #{pid} worker"
|
439
597
|
end
|
440
598
|
end
|
441
599
|
|
442
|
-
if @phased_restart
|
443
|
-
start_phased_restart
|
444
|
-
@phased_restart = false
|
445
|
-
end
|
446
|
-
|
447
|
-
check_workers force_check
|
448
|
-
|
449
600
|
rescue Interrupt
|
450
601
|
@status = :stop
|
451
602
|
end
|
@@ -459,5 +610,50 @@ module Puma
|
|
459
610
|
@wakeup.close
|
460
611
|
end
|
461
612
|
end
|
613
|
+
|
614
|
+
private
|
615
|
+
|
616
|
+
# loops thru @workers, removing workers that exited, and calling
|
617
|
+
# `#term` if needed
|
618
|
+
def wait_workers
|
619
|
+
@workers.reject! do |w|
|
620
|
+
next false if w.pid.nil?
|
621
|
+
begin
|
622
|
+
if Process.wait(w.pid, Process::WNOHANG)
|
623
|
+
true
|
624
|
+
else
|
625
|
+
w.term if w.term?
|
626
|
+
nil
|
627
|
+
end
|
628
|
+
rescue Errno::ECHILD
|
629
|
+
begin
|
630
|
+
Process.kill(0, w.pid)
|
631
|
+
false # child still alive, but has another parent
|
632
|
+
rescue Errno::ESRCH, Errno::EPERM
|
633
|
+
true # child is already terminated
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def timeout_workers
|
640
|
+
@workers.each do |w|
|
641
|
+
if !w.term? && w.ping_timeout <= Time.now
|
642
|
+
log "! Terminating timed out worker: #{w.pid}"
|
643
|
+
w.kill
|
644
|
+
end
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
def nakayoshi_gc
|
649
|
+
return unless @options[:nakayoshi_fork]
|
650
|
+
log "! Promoting existing objects to old generation..."
|
651
|
+
4.times { GC.start(full_mark: false) }
|
652
|
+
if GC.respond_to?(:compact)
|
653
|
+
log "! Compacting..."
|
654
|
+
GC.compact
|
655
|
+
end
|
656
|
+
log "! Friendly fork preparation complete."
|
657
|
+
end
|
462
658
|
end
|
463
659
|
end
|