class Forkr

Attributes

child_count[R]

The number of children I should maintain This can be adjusted up or down with the TTIN and TTOU signals. @return [Integer]

children[R]

Child process pids. @return [Array<Fixnum>]

inbound[R]
master_pid[R]

The PID of the Forkr master @return [Fixnum]

outbound[R]

Public Class Methods

new(forklet, num_kids = 1) click to toggle source

@param forklet [Object] the worker object @param num_kids [Integer] how many children to spawn

# File lib/forkr.rb, line 18
def initialize(forklet, num_kids = 1)
  @worker_client = forklet 
  @master_pid = $$
  @children = []
  @child_count = num_kids
  @in_shutdown = false
end

Public Instance Methods

run() click to toggle source

Start the master, and spawn workers @return [nil]

# File lib/forkr.rb, line 28
def run
  @inbound, @outbound = IO.pipe
  Signal.trap('CHLD') { dead_child }
  Signal.trap('INT') { interrupt }
  Signal.trap('TERM') { shutdown }
  Signal.trap('QUIT') { core_dump_quit }
  Signal.trap('TTIN') { add_worker }
  Signal.trap('TTOU') { remove_worker }
  master_loop
end

Protected Instance Methods

add_worker() click to toggle source
# File lib/forkr.rb, line 53
def add_worker
  send_wake_notice("+")
end
child_dead?(pid) click to toggle source
# File lib/forkr.rb, line 171
def child_dead?(pid)
  status = Process.waitpid(pid, Process::WNOHANG)
  unless status.nil?
    $stderr.puts "Process #{pid} dead: #{status}"
  end
  !status.nil?
end
core_dump_quit() click to toggle source
# File lib/forkr.rb, line 49
def core_dump_quit
  send_wake_notice("Q")
end
dead_child() click to toggle source
# File lib/forkr.rb, line 69
def dead_child
  send_wake_notice("D")
end
decrement_workers() click to toggle source
# File lib/forkr.rb, line 85
def decrement_workers
  if @child_count > 1
    @child_count = @child_count - 1
  end
end
ensure_right_worker_count() click to toggle source
# File lib/forkr.rb, line 133
def ensure_right_worker_count
  existing_workers = @children.length
  off_by = @child_count - @children.length
  if off_by > 0
    off_by.times do
      spawn_worker
    end
  elsif off_by < 0
    @children.take(off_by.abs).each do |kid|
      signal_worker(kid, :TERM)
    end
  end
end
increment_workers() click to toggle source
# File lib/forkr.rb, line 81
def increment_workers
  @child_count = @child_count + 1
end
interrupt() click to toggle source
# File lib/forkr.rb, line 61
def interrupt
  send_wake_notice("I")
end
master_loop() click to toggle source
# File lib/forkr.rb, line 97
def master_loop
  catch(:bail_because_im_a_worker) do
    ensure_right_worker_count
    loop do
      fds = IO.select([@inbound],nil,nil,2)
      unless fds.nil?
        data_read = fds.first.first.read(1)
        if data_read == "I"
          shutdown_using(:INT)
        elsif data_read == "T"
          shutdown_using(:TERM)
        elsif data_read == "Q"
          shutdown_using(:QUIT)
        elsif data_read == "+"
          increment_workers
        elsif data_read == "-"
          decrement_workers
        end
      end
      prune_workers
      ensure_right_worker_count
    end
    reap_all_workers
    @outbound.close
    @inbound.close
  end
end
prune_workers() click to toggle source
# File lib/forkr.rb, line 158
def prune_workers
  @children = @children.reject { |pid| child_dead?(pid) }
end
reap_all_workers() click to toggle source
# File lib/forkr.rb, line 125
def reap_all_workers
  begin
    wpid, status = Process.waitpid2(-1, Process::WNOHANG)
  rescue Errno::ECHILD
    break
  end while true
end
remove_worker() click to toggle source
# File lib/forkr.rb, line 57
def remove_worker
  send_wake_notice("-")
end
send_wake_notice(notice) click to toggle source
# File lib/forkr.rb, line 43
def send_wake_notice(notice)
  return(nil) if $$ != master_pid
  return(nil) if @in_shutdown
  @outbound.write(notice)
end
shutdown() click to toggle source
# File lib/forkr.rb, line 65
def shutdown
  send_wake_notice("T")
end
shutdown_using(sig) click to toggle source
# File lib/forkr.rb, line 91
def shutdown_using(sig)
  @in_shutdown = true
  signal_all_workers(sig)
  raise StopIteration.new
end
signal_all_workers(sig) click to toggle source
# File lib/forkr.rb, line 147
def signal_all_workers(sig)
  @children.each { |c| signal_worker(c, sig) }
end
signal_worker(wpid, signal) click to toggle source
# File lib/forkr.rb, line 151
def signal_worker(wpid, signal)
  begin
    Process.kill(signal, wpid)
  rescue Errno::ESRCH
  end
end
spawn_worker() click to toggle source
# File lib/forkr.rb, line 73
def spawn_worker
  if new_pid = fork
    @children << new_pid
  else
    worker_loop
  end
end
worker_loop() click to toggle source
# File lib/forkr.rb, line 162
def worker_loop
  @worker_client.after_fork if @worker_client.respond_to?(:after_fork)
  @inbound.close
  @outbound.close
  $stderr.puts "Worker spawned as #{$$}!"
  @worker_client.run
  throw(:bail_because_im_a_worker)
end