2013-07-05 19:08:13 -04:00
|
|
|
require 'puma/runner'
|
2016-09-05 14:29:16 -04:00
|
|
|
require 'puma/util'
|
|
|
|
require 'puma/plugin'
|
|
|
|
|
2016-03-05 19:12:16 -05:00
|
|
|
require 'time'
|
2013-07-05 19:08:13 -04:00
|
|
|
|
|
|
|
module Puma
|
|
|
|
class Cluster < Runner
|
2016-12-21 14:48:39 -05:00
|
|
|
WORKER_CHECK_INTERVAL = 5
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
def initialize(cli, events)
|
|
|
|
super cli, events
|
2013-07-05 19:08:13 -04:00
|
|
|
|
|
|
|
@phase = 0
|
|
|
|
@workers = []
|
2014-01-25 19:54:40 -05:00
|
|
|
@next_check = nil
|
2013-07-05 19:08:13 -04:00
|
|
|
|
|
|
|
@phased_state = :idle
|
2013-07-06 00:13:29 -04:00
|
|
|
@phased_restart = false
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def stop_workers
|
|
|
|
log "- Gracefully shutting down workers..."
|
|
|
|
@workers.each { |x| x.term }
|
|
|
|
|
|
|
|
begin
|
2017-11-20 08:24:02 -05:00
|
|
|
@workers.each { |w| Process.waitpid(w.pid) }
|
2013-07-05 19:08:13 -04:00
|
|
|
rescue Interrupt
|
|
|
|
log "! Cancelled waiting for workers"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def start_phased_restart
|
|
|
|
@phase += 1
|
|
|
|
log "- Starting phased worker restart, phase: #{@phase}"
|
2014-02-17 08:34:40 -05:00
|
|
|
|
|
|
|
# Be sure to change the directory again before loading
|
|
|
|
# the app. This way we can pick up new code.
|
2016-03-20 17:09:30 -04:00
|
|
|
dir = @launcher.restart_dir
|
|
|
|
log "+ Changing to #{dir}"
|
|
|
|
Dir.chdir dir
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
2014-07-27 10:01:05 -04:00
|
|
|
def redirect_io
|
|
|
|
super
|
|
|
|
|
|
|
|
@workers.each { |x| x.hup }
|
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
class Worker
|
2015-01-15 07:58:16 -05:00
|
|
|
def initialize(idx, pid, phase, options)
|
2014-01-25 16:53:02 -05:00
|
|
|
@index = idx
|
2013-07-05 19:08:13 -04:00
|
|
|
@pid = pid
|
|
|
|
@phase = phase
|
|
|
|
@stage = :started
|
2013-08-30 14:52:39 -04:00
|
|
|
@signal = "TERM"
|
2015-01-15 07:58:16 -05:00
|
|
|
@options = options
|
|
|
|
@first_term_sent = nil
|
2014-01-25 19:54:40 -05:00
|
|
|
@last_checkin = Time.now
|
2016-02-18 14:35:14 -05:00
|
|
|
@last_status = '{}'
|
2016-02-06 22:00:29 -05:00
|
|
|
@dead = false
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
2016-02-18 14:35:14 -05:00
|
|
|
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
2013-07-05 19:08:13 -04:00
|
|
|
|
|
|
|
def booted?
|
|
|
|
@stage == :booted
|
|
|
|
end
|
|
|
|
|
|
|
|
def boot!
|
2014-01-25 19:54:40 -05:00
|
|
|
@last_checkin = Time.now
|
2013-07-05 19:08:13 -04:00
|
|
|
@stage = :booted
|
|
|
|
end
|
|
|
|
|
2016-01-14 20:01:35 -05:00
|
|
|
def dead?
|
|
|
|
@dead
|
|
|
|
end
|
|
|
|
|
|
|
|
def dead!
|
|
|
|
@dead = true
|
|
|
|
end
|
|
|
|
|
2016-02-18 14:35:14 -05:00
|
|
|
def ping!(status)
|
2014-01-25 19:54:40 -05:00
|
|
|
@last_checkin = Time.now
|
2016-02-18 14:35:14 -05:00
|
|
|
@last_status = status
|
2014-01-25 19:54:40 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def ping_timeout?(which)
|
|
|
|
Time.now - @last_checkin > which
|
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
def term
|
|
|
|
begin
|
2016-02-19 20:05:45 -05:00
|
|
|
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
2013-08-30 14:52:39 -04:00
|
|
|
@signal = "KILL"
|
|
|
|
else
|
2016-02-19 20:05:45 -05:00
|
|
|
@first_term_sent ||= Time.now
|
2013-08-30 14:52:39 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
Process.kill @signal, @pid
|
2013-07-05 19:08:13 -04:00
|
|
|
rescue Errno::ESRCH
|
|
|
|
end
|
|
|
|
end
|
2014-01-25 19:54:40 -05:00
|
|
|
|
|
|
|
def kill
|
|
|
|
Process.kill "KILL", @pid
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
end
|
2014-07-27 10:01:05 -04:00
|
|
|
|
|
|
|
def hup
|
|
|
|
Process.kill "HUP", @pid
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
end
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def spawn_workers
|
|
|
|
diff = @options[:workers] - @workers.size
|
2016-12-21 14:48:39 -05:00
|
|
|
return if diff < 1
|
2013-07-05 19:08:13 -04:00
|
|
|
|
2013-07-06 00:13:29 -04:00
|
|
|
master = Process.pid
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
diff.times do
|
2014-01-25 16:53:02 -05:00
|
|
|
idx = next_worker_index
|
2016-02-07 17:51:54 -05:00
|
|
|
@launcher.config.run_hooks :before_worker_fork, idx
|
2014-01-25 16:53:02 -05:00
|
|
|
|
2014-02-17 08:34:40 -05:00
|
|
|
pid = fork { worker(idx, master) }
|
2016-04-07 14:41:53 -04:00
|
|
|
if !pid
|
|
|
|
log "! Complete inability to spawn new workers detected"
|
|
|
|
log "! Seppuku is the only choice."
|
|
|
|
exit! 1
|
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
debug "Spawned worker: #{pid}"
|
2015-01-15 07:58:16 -05:00
|
|
|
@workers << Worker.new(idx, pid, @phase, @options)
|
2016-02-07 17:51:54 -05:00
|
|
|
|
|
|
|
@launcher.config.run_hooks :after_worker_fork, idx
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
if diff > 0
|
|
|
|
@phased_state = :idle
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-21 14:48:39 -05:00
|
|
|
def cull_workers
|
|
|
|
diff = @workers.size - @options[:workers]
|
|
|
|
return if diff < 1
|
|
|
|
|
|
|
|
debug "Culling #{diff.inspect} workers"
|
|
|
|
|
|
|
|
workers_to_cull = @workers[-diff,diff]
|
|
|
|
debug "Workers to cull: #{workers_to_cull.inspect}"
|
|
|
|
|
|
|
|
workers_to_cull.each do |worker|
|
|
|
|
log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
|
|
|
|
worker.term
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-01-25 16:53:02 -05:00
|
|
|
def next_worker_index
|
2014-07-27 10:01:05 -04:00
|
|
|
all_positions = 0...@options[:workers]
|
2014-01-27 07:13:48 -05:00
|
|
|
occupied_positions = @workers.map { |w| w.index }
|
2014-07-27 10:01:05 -04:00
|
|
|
available_positions = all_positions.to_a - occupied_positions
|
2014-01-27 07:13:48 -05:00
|
|
|
available_positions.first
|
2014-01-25 16:53:02 -05:00
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
def all_workers_booted?
|
|
|
|
@workers.count { |w| !w.booted? } == 0
|
|
|
|
end
|
|
|
|
|
2014-09-05 14:30:22 -04:00
|
|
|
def check_workers(force=false)
|
|
|
|
return if !force && @next_check && @next_check >= Time.now
|
2014-01-25 19:54:40 -05:00
|
|
|
|
2016-12-21 14:48:39 -05:00
|
|
|
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
2014-01-25 19:54:40 -05:00
|
|
|
|
|
|
|
any = false
|
|
|
|
|
|
|
|
@workers.each do |w|
|
2015-11-06 11:46:48 -05:00
|
|
|
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
2014-01-25 19:54:40 -05:00
|
|
|
if w.ping_timeout?(@options[:worker_timeout])
|
|
|
|
log "! Terminating timed out worker: #{w.pid}"
|
|
|
|
w.kill
|
|
|
|
any = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# If we killed any timed out workers, try to catch them
|
|
|
|
# during this loop by giving the kernel time to kill them.
|
|
|
|
sleep 1 if any
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
while @workers.any?
|
|
|
|
pid = Process.waitpid(-1, Process::WNOHANG)
|
|
|
|
break unless pid
|
|
|
|
|
|
|
|
@workers.delete_if { |w| w.pid == pid }
|
|
|
|
end
|
|
|
|
|
2016-01-14 20:01:35 -05:00
|
|
|
@workers.delete_if(&:dead?)
|
|
|
|
|
2016-12-21 14:48:39 -05:00
|
|
|
cull_workers
|
2013-07-05 19:08:13 -04:00
|
|
|
spawn_workers
|
|
|
|
|
2013-08-30 14:52:39 -04:00
|
|
|
if all_workers_booted?
|
2013-07-05 19:08:13 -04:00
|
|
|
# If we're running at proper capacity, check to see if
|
|
|
|
# we need to phase any workers out (which will restart
|
|
|
|
# in the right phase).
|
|
|
|
#
|
|
|
|
w = @workers.find { |x| x.phase != @phase }
|
|
|
|
|
|
|
|
if w
|
2013-08-30 14:52:39 -04:00
|
|
|
if @phased_state == :idle
|
|
|
|
@phased_state = :waiting
|
|
|
|
log "- Stopping #{w.pid} for phased upgrade..."
|
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
w.term
|
2013-08-30 14:52:39 -04:00
|
|
|
log "- #{w.signal} sent to #{w.pid}..."
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def wakeup!
|
2015-07-14 14:31:14 -04:00
|
|
|
return unless @wakeup
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
begin
|
|
|
|
@wakeup.write "!" unless @wakeup.closed?
|
|
|
|
rescue SystemCallError, IOError
|
2017-07-19 14:22:36 -04:00
|
|
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-17 08:34:40 -05:00
|
|
|
def worker(index, master)
|
2017-08-02 21:02:40 -04:00
|
|
|
title = "puma: cluster worker #{index}: #{master}"
|
|
|
|
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
2015-01-20 13:03:34 -05:00
|
|
|
$0 = title
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
Signal.trap "SIGINT", "IGNORE"
|
|
|
|
|
2014-07-27 10:01:05 -04:00
|
|
|
@workers = []
|
2013-07-05 19:08:13 -04:00
|
|
|
@master_read.close
|
|
|
|
@suicide_pipe.close
|
|
|
|
|
|
|
|
Thread.new do
|
|
|
|
IO.select [@check_pipe]
|
|
|
|
log "! Detected parent died, dying"
|
|
|
|
exit! 1
|
|
|
|
end
|
|
|
|
|
2014-02-28 17:17:37 -05:00
|
|
|
# If we're not running under a Bundler context, then
|
|
|
|
# report the info about the context we will be using
|
2016-03-08 08:19:33 -05:00
|
|
|
if !ENV['BUNDLE_GEMFILE']
|
|
|
|
if File.exist?("Gemfile")
|
|
|
|
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
|
|
|
elsif File.exist?("gems.rb")
|
|
|
|
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
|
|
|
end
|
2014-02-28 17:17:37 -05:00
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
# Invoke any worker boot hooks so they can get
|
|
|
|
# things in shape before booting the app.
|
2016-02-07 17:51:54 -05:00
|
|
|
@launcher.config.run_hooks :before_worker_boot, index
|
2013-07-05 19:08:13 -04:00
|
|
|
|
2013-07-06 00:13:29 -04:00
|
|
|
server = start_server
|
2013-07-05 19:08:13 -04:00
|
|
|
|
|
|
|
Signal.trap "SIGTERM" do
|
|
|
|
server.stop
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
@worker_write << "b#{Process.pid}\n"
|
|
|
|
rescue SystemCallError, IOError
|
2017-07-19 14:22:36 -04:00
|
|
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
2015-10-17 13:31:55 -04:00
|
|
|
STDERR.puts "Master seems to have exited, exiting."
|
2013-07-05 19:08:13 -04:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2014-01-25 19:54:40 -05:00
|
|
|
Thread.new(@worker_write) do |io|
|
2016-02-18 14:35:14 -05:00
|
|
|
base_payload = "p#{Process.pid}"
|
2014-01-25 19:54:40 -05:00
|
|
|
|
|
|
|
while true
|
2016-12-21 14:48:39 -05:00
|
|
|
sleep WORKER_CHECK_INTERVAL
|
2016-02-06 22:00:29 -05:00
|
|
|
begin
|
2017-12-11 16:05:36 -05:00
|
|
|
b = server.backlog || 0
|
|
|
|
r = server.running || 0
|
2016-02-18 14:35:14 -05:00
|
|
|
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
|
2016-02-06 22:00:29 -05:00
|
|
|
io << payload
|
|
|
|
rescue IOError
|
2017-07-19 14:22:36 -04:00
|
|
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
2016-02-06 22:00:29 -05:00
|
|
|
break
|
|
|
|
end
|
2014-01-25 19:54:40 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
server.run.join
|
|
|
|
|
2014-08-01 06:04:39 -04:00
|
|
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
2014-08-01 06:28:33 -04:00
|
|
|
# exiting until any background operations are completed
|
2016-02-07 17:51:54 -05:00
|
|
|
@launcher.config.run_hooks :before_worker_shutdown, index
|
2013-07-05 19:08:13 -04:00
|
|
|
ensure
|
2016-01-14 20:01:35 -05:00
|
|
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
2013-07-05 19:08:13 -04:00
|
|
|
@worker_write.close
|
|
|
|
end
|
|
|
|
|
|
|
|
def restart
|
|
|
|
@restart = true
|
2013-07-06 00:13:29 -04:00
|
|
|
stop
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def phased_restart
|
|
|
|
return false if @options[:preload_app]
|
|
|
|
|
|
|
|
@phased_restart = true
|
|
|
|
wakeup!
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop
|
|
|
|
@status = :stop
|
|
|
|
wakeup!
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop_blocked
|
|
|
|
@status = :stop if @status == :run
|
|
|
|
wakeup!
|
2013-07-09 01:36:43 -04:00
|
|
|
@control.stop(true) if @control
|
2013-07-05 19:08:13 -04:00
|
|
|
Process.waitall
|
|
|
|
end
|
|
|
|
|
|
|
|
def halt
|
|
|
|
@status = :halt
|
|
|
|
wakeup!
|
|
|
|
end
|
|
|
|
|
2014-02-25 08:52:20 -05:00
|
|
|
def reload_worker_directory
|
2016-03-20 17:09:30 -04:00
|
|
|
dir = @launcher.restart_dir
|
|
|
|
log "+ Changing to #{dir}"
|
|
|
|
Dir.chdir dir
|
2014-02-25 08:52:20 -05:00
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
def stats
|
2016-01-15 12:20:47 -05:00
|
|
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
|
|
|
booted_worker_count = @workers.count { |w| w.booted? }
|
2017-11-30 10:52:40 -05:00
|
|
|
worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
2016-02-18 14:35:14 -05:00
|
|
|
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
2013-07-06 00:13:29 -04:00
|
|
|
def preload?
|
|
|
|
@options[:preload_app]
|
|
|
|
end
|
|
|
|
|
2016-07-14 15:12:50 -04:00
|
|
|
# We do this in a separate method to keep the lambda scope
|
2016-02-25 16:54:33 -05:00
|
|
|
# of the signals handlers as small as possible.
|
|
|
|
def setup_signals
|
|
|
|
Signal.trap "SIGCHLD" do
|
|
|
|
wakeup!
|
|
|
|
end
|
|
|
|
|
|
|
|
Signal.trap "TTIN" do
|
|
|
|
@options[:workers] += 1
|
|
|
|
wakeup!
|
|
|
|
end
|
|
|
|
|
|
|
|
Signal.trap "TTOU" do
|
|
|
|
@options[:workers] -= 1 if @options[:workers] >= 2
|
|
|
|
wakeup!
|
|
|
|
end
|
|
|
|
|
|
|
|
master_pid = Process.pid
|
|
|
|
|
|
|
|
Signal.trap "SIGTERM" do
|
|
|
|
# The worker installs their own SIGTERM when booted.
|
|
|
|
# Until then, this is run by the worker and the worker
|
|
|
|
# should just exit if they get it.
|
|
|
|
if Process.pid != master_pid
|
|
|
|
log "Early termination of worker"
|
|
|
|
exit! 0
|
|
|
|
else
|
2017-08-16 11:23:51 -04:00
|
|
|
stop_workers
|
2016-02-25 16:54:33 -05:00
|
|
|
stop
|
2017-08-16 11:23:51 -04:00
|
|
|
|
|
|
|
raise SignalException, "SIGTERM"
|
2016-02-25 16:54:33 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
def run
|
|
|
|
@status = :run
|
|
|
|
|
2013-07-05 20:09:18 -04:00
|
|
|
output_header "cluster"
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
log "* Process workers: #{@options[:workers]}"
|
|
|
|
|
2014-11-23 21:55:34 -05:00
|
|
|
before = Thread.list
|
|
|
|
|
2013-07-06 00:13:29 -04:00
|
|
|
if preload?
|
2013-07-05 19:08:13 -04:00
|
|
|
log "* Preloading application"
|
|
|
|
load_and_bind
|
2014-11-23 21:55:34 -05:00
|
|
|
|
|
|
|
after = Thread.list
|
|
|
|
|
|
|
|
if after.size > before.size
|
|
|
|
threads = (after - before)
|
|
|
|
if threads.first.respond_to? :backtrace
|
2014-11-23 22:00:15 -05:00
|
|
|
log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot:"
|
2014-11-23 21:55:34 -05:00
|
|
|
threads.each do |t|
|
2015-01-29 09:04:52 -05:00
|
|
|
log "! #{t.inspect} - #{t.backtrace ? t.backtrace.first : ''}"
|
2014-11-23 21:55:34 -05:00
|
|
|
end
|
2014-11-23 22:00:15 -05:00
|
|
|
else
|
|
|
|
log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot"
|
2014-11-23 21:55:34 -05:00
|
|
|
end
|
|
|
|
end
|
2013-07-05 19:08:13 -04:00
|
|
|
else
|
|
|
|
log "* Phased restart available"
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
unless @launcher.config.app_configured?
|
2013-07-05 19:08:13 -04:00
|
|
|
error "No application configured, nothing to run"
|
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@launcher.binder.parse @options[:binds], self
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
read, @wakeup = Puma::Util.pipe
|
|
|
|
|
2016-02-25 16:54:33 -05:00
|
|
|
setup_signals
|
2013-07-05 19:08:13 -04:00
|
|
|
|
|
|
|
# Used by the workers to detect if the master process dies.
|
|
|
|
# If select says that @check_pipe is ready, it's because the
|
|
|
|
# master has exited and @suicide_pipe has been automatically
|
|
|
|
# closed.
|
|
|
|
#
|
|
|
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
|
|
|
|
2013-07-05 20:09:18 -04:00
|
|
|
if daemon?
|
|
|
|
log "* Daemonizing..."
|
2013-07-05 19:08:13 -04:00
|
|
|
Process.daemon(true)
|
|
|
|
else
|
|
|
|
log "Use Ctrl-C to stop"
|
|
|
|
end
|
|
|
|
|
|
|
|
redirect_io
|
|
|
|
|
2016-07-24 19:00:57 -04:00
|
|
|
Plugins.fire_background
|
2013-07-09 01:36:43 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@launcher.write_state
|
2013-07-05 19:08:13 -04:00
|
|
|
|
2016-07-24 19:00:57 -04:00
|
|
|
start_control
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
@master_read, @worker_write = read, @wakeup
|
2015-08-05 20:03:36 -04:00
|
|
|
|
2016-02-07 17:51:54 -05:00
|
|
|
@launcher.config.run_hooks :before_fork, nil
|
2015-08-05 20:03:36 -04:00
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
spawn_workers
|
|
|
|
|
|
|
|
Signal.trap "SIGINT" do
|
2013-07-06 00:13:29 -04:00
|
|
|
stop
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@launcher.events.fire_on_booted!
|
2013-07-05 19:54:15 -04:00
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
begin
|
2016-03-20 17:14:35 -04:00
|
|
|
force_check = false
|
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
while @status == :run
|
|
|
|
begin
|
2016-03-20 17:14:35 -04:00
|
|
|
if @phased_restart
|
|
|
|
start_phased_restart
|
|
|
|
@phased_restart = false
|
|
|
|
end
|
|
|
|
|
|
|
|
check_workers force_check
|
2013-07-05 19:08:13 -04:00
|
|
|
|
2014-09-05 14:30:22 -04:00
|
|
|
force_check = false
|
|
|
|
|
2016-12-21 14:48:39 -05:00
|
|
|
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
2016-03-20 17:14:35 -04:00
|
|
|
|
2013-07-05 19:08:13 -04:00
|
|
|
if res
|
|
|
|
req = read.read_nonblock(1)
|
|
|
|
|
2014-01-25 19:54:40 -05:00
|
|
|
next if !req || req == "!"
|
|
|
|
|
2016-02-18 14:35:14 -05:00
|
|
|
result = read.gets
|
|
|
|
pid = result.to_i
|
2014-01-25 19:54:40 -05:00
|
|
|
|
|
|
|
if w = @workers.find { |x| x.pid == pid }
|
|
|
|
case req
|
|
|
|
when "b"
|
2013-07-05 19:08:13 -04:00
|
|
|
w.boot!
|
2014-01-25 16:53:02 -05:00
|
|
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
2014-09-05 14:30:22 -04:00
|
|
|
force_check = true
|
2016-01-14 20:01:35 -05:00
|
|
|
when "t"
|
|
|
|
w.dead!
|
|
|
|
force_check = true
|
2014-01-25 19:54:40 -05:00
|
|
|
when "p"
|
2016-02-18 14:35:14 -05:00
|
|
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
2014-01-25 19:54:40 -05:00
|
|
|
else
|
|
|
|
log "! Out-of-sync worker list, no #{pid} worker"
|
2013-07-05 19:08:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
rescue Interrupt
|
|
|
|
@status = :stop
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
stop_workers unless @status == :halt
|
|
|
|
ensure
|
|
|
|
@check_pipe.close
|
|
|
|
@suicide_pipe.close
|
|
|
|
read.close
|
|
|
|
@wakeup.close
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|