class PerfectQueue::Multiprocess::ChildProcessMonitor

Attributes

pid[R]

Public Class Methods

new(log, pid, rpipe) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 23
def initialize(log, pid, rpipe)
  @log = log
  @pid = pid
  @rpipe = rpipe
  @last_heartbeat = Time.now.to_i

  @kill_start_time = nil
  @last_kill_time = nil
  @kill_immediate = false

  @rbuf = ''
end

Public Instance Methods

check_heartbeat(limit) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 38
def check_heartbeat(limit)
  @rpipe.read_nonblock(1024, @rbuf)
  @last_heartbeat = Time.now.to_i
  return true
rescue Errno::EINTR, Errno::EAGAIN
  return Time.now.to_i - @last_heartbeat <= limit
end
cleanup() click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 98
def cleanup
  @rpipe.close unless @rpipe.closed?
end
killing_status() click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 62
def killing_status
  if @kill_start_time
    if @kill_immediate
      return true
    else
      return false
    end
  else
    return nil
  end
end
send_signal(sig) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 102
def send_signal(sig)
  begin
    Process.kill(sig, @pid)
  rescue Errno::ESRCH, Errno::EPERM
    # TODO log?
  end
end
start_killing(immediate, delay=0) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 46
def start_killing(immediate, delay=0)
  if immediate && !@kill_immediate
    @kill_immediate = true  # escalation
  elsif @kill_start_time
    return
  end

  now = Time.now.to_i
  if delay == 0
    @last_kill_time = @kill_start_time = now
    kill_children(now, nil)
  else
    @last_kill_time = @kill_start_time = now + delay
  end
end
try_join(kill_interval, graceful_kill_limit) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 74
def try_join(kill_interval, graceful_kill_limit)
  return nil unless @kill_start_time

  begin
    if Process.waitpid(@pid, Process::WNOHANG)
      @log.info "Processor exited and joined pid=#{@pid}"
      return true
    end
  rescue Errno::ECHILD
    # SIGCHLD is trapped in Supervisor#install_signal_handlers
    @log.info "Processor exited pid=#{@pid}"
    return true
  end

  # resend signal
  now = Time.now.to_i
  if @last_kill_time + kill_interval <= now
    kill_children(now, graceful_kill_limit)
    @last_kill_time = now
  end

  return false
end

Private Instance Methods

collect_child_pids(ppid_pids, results, parent_pid) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 137
def collect_child_pids(ppid_pids, results, parent_pid)
  if pids = ppid_pids[parent_pid]
    pids.each {|pid|
      results << pid
      collect_child_pids(ppid_pids, results, pid)
    }
  end
  results
end
get_ppid_pids_map() click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 124
def get_ppid_pids_map
  ppid_pids = {}  # {ppid => [pid]}
  `ps axo pid,ppid`.each_line do |line|
    if m = /^\s*(\d+)\s+(\d+)\s*$/.match(line)
      (ppid_pids[m[2].to_i] ||= []) << m[1].to_i
    end
  end
  return ppid_pids
# We can ignore errors but not necessary
#rescue
#  return {}
end
kill_children(now, graceful_kill_limit) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 111
def kill_children(now, graceful_kill_limit)
  immediate = @kill_immediate || (graceful_kill_limit && @kill_start_time + graceful_kill_limit < now)

  if immediate
    pids = collect_child_pids(get_ppid_pids_map, [@pid], @pid)
    pids.reverse_each {|pid|
      kill_process(pid, true)
    }
  else
    kill_process(@pid, false)
  end
end
kill_process(pid, immediate) click to toggle source
# File lib/perfectqueue/multiprocess/child_process_monitor.rb, line 147
def kill_process(pid, immediate)
  begin
    if immediate
      @log.debug "sending SIGKILL to pid=#{pid} for immediate stop"
      Process.kill(:KILL, pid)
    else
      @log.debug "sending SIGTERM to pid=#{pid} for graceful stop"
      Process.kill(:TERM, pid)
    end
  rescue Errno::ESRCH, Errno::EPERM
    # TODO log?
  end
end