lib/child_labor.rb
module ChildLabor def self.subprocess(cmd) t = Task.new(cmd) t.launch if block_given? begin yield t ensure t.wait t.close end end return t end class Task attr_reader :cmd, :stdout, :stderr, :stdin def initialize(cmd) @cmd = cmd @pid = nil @exit_status = nil @terminated = false end def launch create_pipes @pid = Process.fork # child unless @pid @stdout.close STDOUT.reopen @stdout_child @stdin.close STDIN.reopen @stdin_child @stderr.close STDERR.reopen @stderr_child Process.exec cmd end @stdout_child.close @stdin_child.close @stderr_child.close true end def launched? @pid end def running? poll_status(Process::WNOHANG) end def terminated? launched? && !running? end def exit_status poll_status @exit_status end def wait exit_status end def suspend signal('STOP') end def resume signal('CONT') end def terminate(signal = 'TERM') signal(signal) end def read(length = nil, buffer = nil) @stdout.read(length, buffer) end def readline @stdout.readline end def read_stderr(length = nil, buffer = nil) @stderr.read(length, buffer) end def write(str) @stdin.write(str) end def signal(signal) Process.kill(signal, @pid) end def close_read @stdout.close if @stdout @stdout = nil end def close_write @stdin.close if @stdin @stdin = nil end def close_stderr @stderr.close if @stderr @stderr = nil end def close close_read close_write close_stderr end private # Handles the state of the Task in a single call to # Process.wait2. # Returns true if the process is currently running def poll_status(flags = 0) return false unless @pid return false if @terminated pid, status = Process.wait2(@pid, flags) return true unless pid @terminated = true @exit_status = status false rescue Errno::ECHILD false end def create_pipes @stdout, @stdout_child = IO.pipe @stdin_child, @stdin = IO.pipe @stderr, @stderr_child = IO.pipe end end end