Skip to content

Instantly share code, notes, and snippets.

@agile
Created October 11, 2011 14:55
Show Gist options
  • Save agile/1278303 to your computer and use it in GitHub Desktop.
Save agile/1278303 to your computer and use it in GitHub Desktop.
Example forking with signal handler and message pipe
#!/usr/bin/env ruby
#
# Forking example using pipes to communicate from child processes
def log(msg)
case msg
when Array then msg.each {|m| log(" #{m}") }
else
puts "[#{Time.now}][#{$$}] #{msg}"
end
end
def listen
return unless $$ == @master_pid
@pids ||= {}
@pipes ||= {}
log "listening to #{@pipes.size} remaining pipes.."
@pipes.each do |pid, pipe|
loop do
begin
tmp = ''
pipe.read_nonblock(1, tmp)
tmp += pipe.gets.strip
case tmp
when /hi/i then
log "Child #{pid} says hi.."
when /hungry/i then
log "Need to feed child.. #{pid}"
else
log "Child #{pid} wants something: #{tmp}"
end
rescue IO::WaitReadable
# ok, nothing in the pipe..
break
rescue EOFError, IOError
# this sucker's dead?
pipe.close unless pipe.closed?
@pipes.delete(pid)
break
rescue Object => err
log "failed to catch #{err.class}:#{err} while reading pipe on pid #{pid}"
break
end
end
end
end
def monitor_pids
return unless $$ == @master_pid
@pids ||= {}
@pipes ||= {}
log "monitoring #{@pids.size} remaining pids.."
@pids.each do |job, pid|
listen
found, status = Process.waitpid2(pid, Process::WNOHANG)
if found
log "found pid #{pid} exited with #{status ? status.exitstatus : 'no status'}"
@pipes[pid].close if @pipes[pid] && !@pipes[pid].closed?
@pipes.delete(pid)
@pids.delete(job)
end
end
end
def cry(msg)
if @to_master
if @to_master.closed?
log "master isn't listening, pipe's closed? re: '#{msg}'"
else
begin
@to_master.puts msg
rescue Errno::EPIPE
log "failed to talk to master, broken pipe? re: '#{msg}'"
end
end
else
log msg
end
end
def run
@master_pid = $$
@pids = {}
@pipes = {}
%w[TERM QUIT INT].each do |sig|
Signal.trap(sig) do
cry("caught #{sig} signal")
shutdown 3
end
end
%w[a b c].each do |job|
from_child,to_master = IO.pipe
if pid = fork
@pids[job] = pid
@pipes[pid] = from_child
to_master.close
log "launching pid #{pid} for #{job}"
else
begin
@to_master = to_master
from_child.close
cry("Hi!!")
3.times do
sleep rand(5)
cry("*YAWN* I'm hungry...")
end
shutdown
rescue SystemExit
# we're outta here..
rescue Interrupt,SignalException
cry("Abort requested, exiting (#{$!.class}:#{$!})")
shutdown 2
rescue Object => err
cry("OUCH: #{err.class}:#{err}")
shutdown 1
end
end
end
log "Entering monitor_pids loop"
loop do
monitor_pids
break if @pids.empty?
sleep 1
end
log "All done!"
end
def shutdown(code=0)
if $$ == @master_pid
@pids ||= {}
@pipes ||= {}
until @pids.empty?
monitor_pids
sleep 1
end
else
cry("Shutting down..")
exit! code
end
end
if __FILE__ == $0
run
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment