Example #1
0
File: popen.py Project: clones/kaa
    def start(self, args = None):
        """
        Starts the process.  If args is not None, it can be either a list or
        string, as with the constructor, and is appended to the command line
        specified in the constructor.
        """
        if not self.__dead:
            raise SystemError, "Process is already running."
        if self.stopping:
            raise SystemError, "Process isn't done stopping yet."

        cmd = self._cmd + self._normalize_cmd(args)
        self.__kill_timer = None
        self.__dead = False
        self.binary = cmd[0]

        self.child = popen2.Popen3( cmd, True, 100 )

        flags = fcntl.fcntl(self.child.tochild.fileno(), fcntl.F_GETFL)
        fcntl.fcntl( self.child.tochild.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK )
        self._wmon = IOMonitor(self._handle_write)
        if self._write_buffer:
            self._wmon.register(self.child.tochild, IO_WRITE)

        log.info('running %s (pid=%s)' % ( self.binary, self.child.pid ) )

        # IO_Handler for stdout
        self.stdout = IO_Handler( 'stdout', self.child.fromchild,
                                  self.signals["stdout"], self.signals['raw-stdout'], self._debugname )
        # IO_Handler for stderr
        self.stderr = IO_Handler( 'stderr', self.child.childerr,
                                  self.signals["stderr"], self.signals['raw-stderr'], self._debugname )

        # add child to watcher
        if not is_mainthread():
            MainThreadCallback(proclist.append)(self, self.__child_died)
        else:
            proclist.append( self, self.__child_died )
        self.in_progress = InProgress()
        return self.in_progress
Example #2
0
File: popen.py Project: clones/kaa
class Process(object):
    """
    Base class for started child processes
    """
    def __init__( self, cmd, debugname = None ):
        """
        Init the child process 'cmd'. This can either be a string or a list
        of arguments (similar to popen2). If debugname is given, the stdout
        and stderr will also be written.
        """

        # Setup signal handlers for the process; allows the class to be
        # useful without subclassing.
        self.signals = {
            "stderr": Signal(),
            "stdout": Signal(),
            "raw-stderr": Signal(),
            "raw-stdout": Signal(),
            "completed": Signal(),
        }

        self._cmd = self._normalize_cmd(cmd)
        self._stop_cmd = None
        self._debugname = debugname
        self.__dead = True
        self.stopping = False
        self.__kill_timer = None
        self.child = None
        self.in_progress = None
        self._write_buffer = []
        self._wmon = None
        self._close_stdin = False


    def __inprogress__(self):
        return self.in_progress


    def _normalize_cmd(self, cmd):
        """
        Converts a command string into a list while honoring quoting, or
        removes empty strings if the cmd is a list.
        """
        if cmd == None:
            return []
        if isinstance(cmd, tuple):
            cmd = list(cmd)
        if isinstance(cmd, list):
            # Remove empty strings from argument list.
            while '' in cmd:
                cmd.remove('')
            return cmd

        assert(isinstance(cmd, str))

        # This might be how you'd do it in C. :)
        cmdlist = []
        curarg = ""
        waiting = None
        last = None
        for c in cmd:
            if (c == ' ' and not waiting) or c == waiting:
                if curarg:
                    cmdlist.append(curarg)
                    curarg = ""
                waiting = None
            elif c in ("'", '"') and not waiting and last != '\\':
                waiting = c
            else:
                curarg += c
            last = c

        if curarg:
            cmdlist.append(curarg)

        return cmdlist


    def start(self, args = None):
        """
        Starts the process.  If args is not None, it can be either a list or
        string, as with the constructor, and is appended to the command line
        specified in the constructor.
        """
        if not self.__dead:
            raise SystemError, "Process is already running."
        if self.stopping:
            raise SystemError, "Process isn't done stopping yet."

        cmd = self._cmd + self._normalize_cmd(args)
        self.__kill_timer = None
        self.__dead = False
        self.binary = cmd[0]

        self.child = popen2.Popen3( cmd, True, 100 )

        flags = fcntl.fcntl(self.child.tochild.fileno(), fcntl.F_GETFL)
        fcntl.fcntl( self.child.tochild.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK )
        self._wmon = IOMonitor(self._handle_write)
        if self._write_buffer:
            self._wmon.register(self.child.tochild, IO_WRITE)

        log.info('running %s (pid=%s)' % ( self.binary, self.child.pid ) )

        # IO_Handler for stdout
        self.stdout = IO_Handler( 'stdout', self.child.fromchild,
                                  self.signals["stdout"], self.signals['raw-stdout'], self._debugname )
        # IO_Handler for stderr
        self.stderr = IO_Handler( 'stderr', self.child.childerr,
                                  self.signals["stderr"], self.signals['raw-stderr'], self._debugname )

        # add child to watcher
        if not is_mainthread():
            MainThreadCallback(proclist.append)(self, self.__child_died)
        else:
            proclist.append( self, self.__child_died )
        self.in_progress = InProgress()
        return self.in_progress


    def get_pid(self):
        """
        Returns the pid of the child process if it has been spawned.  Otherwise
        returns None
        """
        if self.child:
            return self.child.pid


    def close_stdin(self):
        """
        Closes stdin either now, or once the write buffer has been flushed.
        This might be necessary when, for example, we know we have nothing
        more to send to the child, but the child is waiting for either more
        data or a closed fd before outputting data we're interested in.
        """
        if not self.child:
            return

        if self._wmon and self._wmon.active:
            self._close_stdin = True
        else:
            self.child.tochild.close()


    def write(self, data):
        """
        Queue data for writing when the child is ready to receive it.
        """
        if self.child.tochild.closed:
            raise ValueError("Can't write when stdin has been closed")

        self._write_buffer.append(data)
        if self.child and self._wmon and not self._wmon.active:
            self._wmon.register(self.child.tochild, IO_WRITE)


    def _handle_write(self):
        if not self.child:
            # The child process might have died before we had a chance to
            # flush the write buffer.
            del self._write_buffer[:]
            return False

        try:
            while self._write_buffer:
                data = self._write_buffer[0]
                sent = os.write(self.child.tochild.fileno(), data)
                # If we got here, no exception was raised, so we can pop the
                # data from the buffer.
                self._write_buffer.pop(0)
                if sent != len(data):
                    # Not all data was sent, so push the remaining bytes back and
                    # abort the loop.
                    self._write_buffer.insert(0, data[sent:])
                    break
        except IOError, (errno, msg):
            if errno == 11:
                # Resource temporarily unavailable -- trying to write too
                # much data.
                return
        except OSError, (errno, msg):
            if errno == 32 and self.stopping:
                # Broken pipe.  Child is dead while we are trying to
                # issue stop command.  Safe to ignore.
                pass
            else:
                # Reraise exception.
                raise