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
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