module ChildProcess class AbstractProcess POLL_INTERVAL = 0.1 attr_reader :exit_code # # Set this to true if you do not care about when or if the process quits. # attr_accessor :detach # # Set this to true if you want to write to the process' stdin (process.io.stdin) # attr_accessor :duplex # # Modify the child's environment variables # attr_reader :environment # # Set the child's current working directory. # attr_accessor :cwd # # Set this to true to make the child process the leader of a new process group # # This can be used to make sure that all grandchildren are killed # when the child process dies. # attr_accessor :leader # # Create a new process with the given args. # # @api private # @see ChildProcess.build # def initialize(args) unless args.all? { |e| e.kind_of?(String) } raise ArgumentError, "all arguments must be String: #{args.inspect}" end @args = args @started = false @exit_code = nil @io = nil @cwd = nil @detach = false @duplex = false @leader = false @environment = {} end # # Returns a ChildProcess::AbstractIO subclass to configure the child's IO streams. # def io raise SubclassResponsibility, "io" end # # @return [Integer] the pid of the process after it has started # def pid raise SubclassResponsibility, "pid" end # # Launch the child process # # @return [AbstractProcess] self # def start launch_process @started = true self end # # Forcibly terminate the process, using increasingly harsher methods if possible. # # @param [Integer] timeout (3) Seconds to wait before trying the next method. # def stop(timeout = 3) raise SubclassResponsibility, "stop" end # # Block until the process has been terminated. # # @return [Integer] The exit status of the process # def wait raise SubclassResponsibility, "wait" end # # Did the process exit? # # @return [Boolean] # def exited? raise SubclassResponsibility, "exited?" end # # Has the process started? # # @return [Boolean] # def started? @started end # # Is this process running? # # @return [Boolean] # def alive? started? && !exited? end # # Returns true if the process has exited and the exit code was not 0. # # @return [Boolean] # def crashed? exited? && @exit_code != 0 end # # Wait for the process to exit, raising a ChildProcess::TimeoutError if # the timeout expires. # def poll_for_exit(timeout) log "polling #{timeout} seconds for exit" end_time = Time.now + timeout until (ok = exited?) || Time.now > end_time sleep POLL_INTERVAL end unless ok raise TimeoutError, "process still alive after #{timeout} seconds" end end private def launch_process raise SubclassResponsibility, "launch_process" end def detach? @detach end def duplex? @duplex end def leader? @leader end def log(*args) ChildProcess.logger.debug "#{self.inspect} : #{args.inspect}" end def assert_started raise Error, "process not started" unless started? end end # AbstractProcess end # ChildProcess