class RunLoop::ProcessTerminator

A class for terminating processes and waiting for them to die.

Constants

DEFAULT_OPTIONS

@!visibility private The default options for waiting on a process to terminate.

Attributes

display_name[R]

@!attribute [r] display_name The process name to use log messages and exceptions. Not used to find

or otherwise interact with the process.

@return [String] The display name.

kill_signal[R]

@!attribute [r] kill_signal The kill signal to send to the process. Can be a Unix signal name or an

Integer.

@return [Integer, String] The kill signal.

options[R]

@!attribute [r] options Options to control the behavior of ‘kill_process`. @return [Hash] A hash of options.

pid[R]

@!attribute [r] pid The process id of the process. @return [Integer] The pid.

Public Class Methods

new(pid, kill_signal, display_name, options={}) click to toggle source

Create a new process terminator.

@param pid The process pid. @param[String, Integer] kill_signal The kill signal to send to the process. @param display_name The name of the process to kill. Used only

in log messages and exceptions.

@option options [Float] :timeout (2.0) How long to wait for the process to

terminate.

@option options [Float] :interval (0.1) The polling interval. @option options [Boolean] :raise_on_no_terminate (false) Should an error

be raised if process does not terminate.
# File lib/run_loop/process_terminator.rb, line 39
def initialize(pid, kill_signal, display_name, options={})
  @options = DEFAULT_OPTIONS.merge(options)
  @pid = pid.to_i
  @kill_signal = kill_signal
  @display_name = display_name
end

Public Instance Methods

kill_process() click to toggle source

Try to kill the process identified by ‘pid`.

After sending ‘kill_signal` to `pid`, wait for the process to terminate.

@return [Boolean] Returns true if the process was terminated or is no

longer alive.

@raise [SignalException] Raised on an unhandled ‘Process.kill` exception.

Errno:ESRCH and Errno:EPERM are _handled_ exceptions; all others will
be raised.
# File lib/run_loop/process_terminator.rb, line 55
def kill_process
  return true unless process_alive?

  begin
    RunLoop.log_debug("Sending '#{kill_signal}' to #{display_name} process '#{pid}'")
    Process.kill(kill_signal, pid.to_i)
    # Don't wait.
    # We might not own this process and a WNOHANG would be a nop.
    # Process.wait(pid, Process::WNOHANG)
  rescue Errno::ESRCH
    RunLoop.log_debug("Process with pid '#{pid}' does not exist; nothing to do.")
    # Return early; there is no need to wait if the process does not exist.
    return true
  rescue Errno::EPERM
    RunLoop.log_debug("Cannot kill process '#{pid}' with '#{kill_signal}'; not a child of this process")
  rescue SignalException => e
    raise e.message
  end
  if options[:timeout].to_f <= 0.0
    RunLoop.log_debug("Not waiting for process #{display_name} : #{pid} to terminate")
  else
    RunLoop.log_debug("Waiting for #{display_name} with pid '#{pid}' to terminate")
    wait_for_process_to_terminate
  end
end
process_alive?() click to toggle source

Is the process ‘pid` alive? @return [Boolean] Returns true if the process is still alive.

# File lib/run_loop/process_terminator.rb, line 83
def process_alive?
  begin
    Process.kill(0, pid.to_i)
    true
  rescue Errno::ESRCH
    false
  rescue Errno::EPERM
    true
  end
end

Private Instance Methods

ps_details() click to toggle source

@!visibility private The details of the process reported by ‘ps`.

# File lib/run_loop/process_terminator.rb, line 107
def ps_details
  `ps -p #{pid} -o pid,comm | grep #{pid}`.strip
end
wait_for_process_to_terminate() click to toggle source

@!visibility private Wait for the process to terminate by polling.

# File lib/run_loop/process_terminator.rb, line 113
def wait_for_process_to_terminate
  now = Time.now
  poll_until = now + options[:timeout]
  delay = options[:interval]
  has_terminated = false
  while Time.now < poll_until
    has_terminated = !process_alive?
    break if has_terminated
    sleep delay
  end

  RunLoop.log_debug("Waited for #{Time.now - now} seconds for #{display_name} with pid '#{pid}' to terminate")

  if @options[:raise_on_no_terminate] and !has_terminated
    raise "Waited #{options[:timeout]} seconds for #{display_name} (#{ps_details}) to terminate"
  end
  has_terminated
end