class Forkomatic

Attributes

jobs[RW]
max_children[RW]
max_iterations[RW]
wait_for_children[RW]
work_interval[RW]

Public Class Methods

new(args) click to toggle source

Initialize the runners.

# File lib/forkomatic.rb, line 21
def initialize(args)
  params = {}
  if args.is_a?(String)
    # Load config from a file.
    params = load_config(args)
  elsif args.is_a?(Integer)
    # Given an integer, forkomatic will only run N runners 1 time.
    params[:wait_for_children] = true
    params[:max_children] = args
    params[:work_interval] = 0
    params[:max_iterations] = 1
  elsif args.is_a?(Hash)
    # Specify the parameters directly.
    params = args
  end
  t = params
  params.inject({}) {|t, (key, val)| t[key.to_sym] = val; t}
  self.jobs = []
  self.max_children = params[:max_children] || 1
  self.work_interval = params[:work_interval].nil? || params[:work_interval] < 0 ? 0 : params[:work_interval]
  self.max_iterations = params[:max_iterations]
  self.wait_for_children = params[:wait_for_children].nil? ? true : params[:wait_for_children]
end

Public Instance Methods

available() click to toggle source

See how many children are available.

# File lib/forkomatic.rb, line 130
def available
  # Initialize if need be.
  return @max_children if @jobs.nil? || @jobs.empty?
  # Reap children runners without waiting.
  reap_all
  @max_children - @jobs.length
end
build_jobs(count) click to toggle source

Create workers.

# File lib/forkomatic.rb, line 101
def build_jobs(count)
  (1..count).each.collect {Forkomatic::Job.new}
end
child_pids() click to toggle source

Get a list of current process IDs.

# File lib/forkomatic.rb, line 139
def child_pids
  reap_all
  pids = []
  @jobs.each {|job| pids.push(job.pid) if job.pid}
  pids
end
load_config(config_file) click to toggle source

Load a configuration from a file.

# File lib/forkomatic.rb, line 46
def load_config(config_file)
  params = {}
  # Allowed options.
  options = ['max_children', 'work_interval', 'max_iterations', 'wait_for_children']
  begin
    # Try to read the config file, and store the values.
    data = File.open(config_file, "r").read.split(/\n/)
    data.each do |line|
      if line =~ /^\s*([a-zA-Z_]+)\s+([0-9]+)/
        config_item = $1
        config_value = $2
        # Make sure option is valid.
        if options.map(&:downcase).include?(config_item.downcase)
          params[config_item.to_sym] = config_value.to_i
        end
      end
    end
  rescue => e
    puts "Error loading config file: #{e.to_s}"
  end
  params
end
reap(pid) click to toggle source

Reap child processes that finished.

# File lib/forkomatic.rb, line 106
def reap(pid)
  return true if pid.nil?
  begin
    return true if Process.waitpid(pid, Process::WNOHANG)
  rescue Errno::ECHILD
    return true
  rescue => e
    puts "ERROR: #{e.to_s}"
  end
  return false
end
reap_all() click to toggle source

Try to reap all child processes.

# File lib/forkomatic.rb, line 119
def reap_all
  finished = []
  @jobs.each do |job|
    if reap(job.pid)
      finished.push(job.pid)
    end
  end
  @jobs.delete_if {|job| finished.include?(job.pid)}
end
run() click to toggle source

Do work.

# File lib/forkomatic.rb, line 82
def run
  Signal.trap("INT")  { shutdown(true) }
  iteration = 0
  while (@max_iterations.nil? || iteration < @max_iterations) do
    iteration += 1
    current_jobs = build_jobs(available)
    current_jobs.each do |job|
      pid = Process.fork do
        job.work!
      end
      job.pid = pid
      @jobs.push(job)
    end
    sleep @work_interval if @work_interval > 0
  end
  Process.waitall if @wait_for_children
end
shutdown(all) click to toggle source

Kill all child processes and shutdown.

# File lib/forkomatic.rb, line 70
def shutdown(all)
  child_pids.each do |pid|
    begin
      Process.kill("TERM", pid)
    rescue => e
      puts e.to_s
    end
  end
  exit if all
end