class Sleepier::Supervisor

`Sleepier::Supervisor` manages a set of `Sleepier::ChildSpec` objects according to the guidance passed to it via the constructor.

Constants

VALID_RESTART_STRATEGIES

@todo implement strategies other than :one_for_one

Determines how to handle restarts for all processes supervised

  • :one_for_one - Only restart the process that failed if one process terminates

  • :one_for_all - Restart all processes if one process terminates

  • :rest_for_one - Restart all processes after the process, in the order they started, that terminates as well as the process that terminated

Public Class Methods

new(child_specs, restart_strategy, max_restart_count=3, max_restart_window=5) click to toggle source

Create the supervisor. Does not start it.

@param child_specs [Sleepier::ChildSpec] What processes to start and monitor @param restart_strategy [VALID_RESTART_STRATEGIES] Managing how restarts are handled for the group of processes @param max_restart_count [int] How many times within `max_restart_window` a process can restart. @param max_restart_window [int] A moving window in seconds during which a process may terminate `max_restart_count` times before the supervisor gives up.

# File lib/sleepier.rb, line 170
def initialize(child_specs, restart_strategy, max_restart_count=3, max_restart_window=5)
    @child_specs = Hash.new
    child_specs.each {|child_spec| @child_specs[child_spec.child_id] = child_spec}

    @max_restart_count = max_restart_count
    @max_restart_window = max_restart_window

    if VALID_RESTART_STRATEGIES.include?(restart_strategy)
        @restart_strategy = restart_strategy
    else
        raise Exception.new('Invalid restart strategy')
    end

    @started = false
end

Public Instance Methods

handle_finished_process(pid, status) click to toggle source

Internal function that handles a process finishing

@param pid [int] The pid of the finished process, used to find the right child process @param status [int] Status code. 0 is normal, anything else is abnormal termination

@return [true,false] Returns true if the process should have been restarted and was, false otherwise

# File lib/sleepier.rb, line 250
def handle_finished_process(pid, status)
    @child_specs.each do |child_id, child_spec|
        if child_spec.pid == pid
            if child_spec.should_restart?(status, @max_restart_count, @max_restart_window)
                child_spec.restarted
                self.start_process(child_id)
                return true
            else
                Sleepier.logger.info("#{child_spec.restart.to_s.capitalize} child #{child_spec.child_id} finished.  Will not be restarted")
                return false
            end
        end
    end
end
monitor() click to toggle source

Watches for processes to terminate. Sends the pid and status returned by the process to `handle_finished_process`

@note This may be called before calling start to minimize chances of a process terminating before monitoring starts

# File lib/sleepier.rb, line 189
def monitor
    while true
        begin
            pid, status = Process.wait2
        rescue Errno::ECHILD
            if @started
                Sleepier.logger.warn("No children, exiting")
                break
            end
        end

        self.handle_finished_process(pid, status)
    end
end
start() click to toggle source

Start all the child processes

# File lib/sleepier.rb, line 205
def start
    @child_specs.each do |child_id, child_spec|
        self.start_process(child_id)
    end
    @started = true
end
start_new_child(child_spec) click to toggle source

Add a new child process and start it

@param child_spec [Sleepier::ChildSpec] spec to use and start

# File lib/sleepier.rb, line 215
def start_new_child(child_spec)
    @child_specs[child_spec.child_id] = child_spec
    self.start_process(child_spec.child_id)
end
start_process(child_id) click to toggle source

Internal function used by `start_new_child` and `start`

# File lib/sleepier.rb, line 266
def start_process(child_id)
    child_spec = @child_specs[child_id]
    pid = Process.fork do
        child_spec.start_func.call(*(child_spec.args))
    end

    child_spec.pid = pid
    Sleepier.logger.info("Started #{child_spec.child_id} with pid #{pid}")
end
terminate_child(child_id) click to toggle source

Starts termination of a process. This does not wait for the process to finish.

@param child_id Which child to terminate

@todo Add a callback for when the process finishes here

# File lib/sleepier.rb, line 225
def terminate_child(child_id)
    child_spec = @child_specs[child_id]
    child_spec.terminating = true

    case child_spec.shutdown
    when :brutal_kill
        Process.kill("KILL", child_spec.pid)
    when :timeout
        Process.kill("TERM", child_spec.pid)

        Thread.new do
            sleep(child_spec.shutdown_timeout)
            Process.kill("KILL", child_spec.pid)
        end
    when :infinity
       Process.kill("TERM", child_spec.pid)
    end
end