Ticket #1939: twisted-process-improvements.patch

File twisted-process-improvements.patch, 16.4 KB (added by naked, 16 years ago)

first version of the improvement patch

  • twisted/internet/posixbase.py

    ==== Patch <twisted-process-improvements> level 1
    Source: 4ed7e550-c718-0410-ac9a-b82090e61d08:/local/twisted-process-idea-dev:11405
    Target: bbbe8e31-12d6-0310-92fd-ac37d47ddeeb:/trunk:17576
            (svn://svn.twistedmatrix.com/svn/Twisted/trunk)
    Log:
     r11396@taby:  naked | 2006-07-19 10:48:04 +0300
     Branched twisted-trunk to for developing the process module.
     r11399@taby:  naked | 2006-07-19 20:56:44 +0300
     Added first helpers for process childFDs.
     r11400@taby:  naked | 2006-07-19 21:23:05 +0300
     First version of rework of childFDs handling. We should be in working state
     again, so let me try testing.
     r11401@taby:  naked | 2006-07-19 21:56:49 +0300
     Implemented ProcessPTY as the reader/writer for PTY processes.
     r11402@taby:  naked | 2006-07-19 22:16:56 +0300
     Bugfixes to PTY process handling.
     r11403@taby:  naked | 2006-07-19 22:27:38 +0300
     Added defaultWriter hack to maybe make Process behave a bit like PTYProces if
     wanted.
     r11404@taby:  naked | 2006-07-19 22:34:55 +0300
     Bugfix.
     r11405@taby:  naked | 2006-07-19 22:36:24 +0300
     Modify posixbase to use the new Process PTY interface.
    
    === twisted/internet/posixbase.py
    ==================================================================
     
    275275            if usePTY:
    276276                if childFDs is not None:
    277277                    raise ValueError("Using childFDs is not supported with usePTY=True.")
    278                 return process.PTYProcess(self, executable, args, env, path,
    279                                           processProtocol, uid, gid, usePTY)
     278                return process.Process(self, executable, args, env, path,
     279                                       processProtocol, uid, gid, {'_defaultWriter': 1,
     280                                                                   1: (process.PTY(), (0, 1, 2,))})
    280281            else:
    281282                return process.Process(self, executable, args, env, path,
    282283                                       processProtocol, uid, gid, childFDs)
  • twisted/internet/process.py

    === twisted/internet/process.py
    ==================================================================
     
    233233        self.proc.childConnectionLost(self.name, reason)
    234234
    235235
     236class ProcessPTY(abstract.FileDescriptor):
     237    connected = 1
     238
     239    def __init__(self, reactor, proc, name, fileno):
     240        abstract.FileDescriptor.__init__(self, reactor)
     241        fdesc.setNonBlocking(fileno)
     242        self.proc = proc
     243        self.name = name
     244        self.fd = fileno
     245        self.startReading()
     246
     247    def fileno(self):
     248        return self.fd
     249
     250    def writeSomeData(self, data):
     251        try:
     252            return os.write(self.fd, data)
     253        except IOError,io:
     254            if io.args[0] == errno.EAGAIN:
     255                return 0
     256            return CONNECTION_LOST
     257        except OSError, ose:
     258            if ose.errno == errno.EAGAIN: # MacOS-X does this # FIXME: really needed?
     259                return 0
     260            raise
     261
     262    def doRead(self):
     263        try:
     264            return fdesc.readFromFD(self.fd, self.dataReceived)
     265        except OSError: # FIXME: really needed?
     266            return CONNECTION_LOST
     267
     268    def dataReceived(self, data):
     269        self.proc.childDataReceived(self.name, data)
     270
     271    def connectionLost(self, reason):
     272        abstract.FileDescriptor.connectionLost(self, reason)
     273        self.proc.childConnectionLost(self.name, reason)
     274
     275
     276class ProcessHelper:
     277    def setupPrefork(self):
     278        raise NotImplementedError("%s does not implement setup" %
     279                                  reflect.qual(self.__class__))
     280
     281    def setupChild(self):
     282        raise NotImplementedError("%s does not implement setup" %
     283                                  reflect.qual(self.__class__))
     284
     285    def setupParent(self):
     286        raise NotImplementedError("%s does not implement setup" %
     287                                  reflect.qual(self.__class__))
     288
     289
     290class PassthroughFD(ProcessHelper):
     291    def __init__(self, fd):
     292        self.fd = fd
     293
     294    def setupPrefork(self):
     295        pass
     296
     297    def setupChild(self):
     298        return self.fd
     299
     300    def setupParent(self, reactor, proc, name):
     301        return None
     302
     303
     304class ReadPipe(ProcessHelper):
     305    def setupPrefork(self):
     306        readFD, writeFD = os.pipe()
     307        self.parentFD = readFD
     308        self.childFD = writeFD
     309
     310    def setupChild(self):
     311        os.close(self.parentFD)
     312        return self.childFD
     313
     314    def setupParent(self, reactor, proc, name):
     315        os.close(self.childFD)
     316        reader = ProcessReader(reactor, proc, name, self.parentFD)
     317        return reader
     318
     319
     320class WritePipe(ProcessHelper):
     321    def setupPrefork(self):
     322        readFD, writeFD = os.pipe()
     323        self.parentFD = writeFD
     324        self.childFD = readFD
     325
     326    def setupChild(self):
     327        os.close(self.parentFD)
     328        return self.childFD
     329
     330    def setupParent(self, reactor, proc, name):
     331        os.close(self.childFD)
     332        reader = ProcessWriter(reactor, proc, name, self.parentFD)
     333        return reader
     334
     335
     336class PTY(ProcessHelper):
     337    def __init__(self, usePTY=None):
     338        if not pty and type(usePTY) not in (types.ListType, types.TupleType):
     339            # no pty module and we didn't get a pty to use
     340            raise NotImplementedError, "cannot use PTYProcess on platforms without the pty module."
     341        self.usePTY = usePTY
     342
     343    def setupPrefork(self):
     344        if type(self.usePTY) in (types.ListType, types.TupleType):
     345            masterFD, slaveFD, ttyname = self.usePTY
     346        else:
     347            masterFD, slaveFD = pty.openpty()
     348            ttyname = os.ttyname(slaveFD)
     349        self.parentFD = masterFD
     350        self.childFD = slaveFD
     351        self.ttyname = ttyname
     352
     353    def setupChild(self):
     354        os.close(self.parentFD)
     355        if hasattr(termios, 'TIOCNOTTY'):
     356            try:
     357                fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
     358            except OSError:
     359                pass
     360            else:
     361                try:
     362                    fcntl.ioctl(fd, termios.TIOCNOTTY, '')
     363                except:
     364                    pass
     365                os.close(fd)
     366                   
     367        os.setsid()
     368               
     369        if hasattr(termios, 'TIOCSCTTY'):
     370            fcntl.ioctl(self.childFD, termios.TIOCSCTTY, '')
     371
     372        return self.childFD
     373
     374    def setupParent(self, reactor, proc, name):
     375        os.close(self.childFD)
     376        processpty = ProcessPTY(reactor, proc, name, self.parentFD)
     377        return processpty
     378
     379
     380class SocketPair(ProcessHelper):
     381    pass
     382
     383
    236384class Process(styles.Ephemeral):
    237385    """An operating-system Process.
    238386
     
    252400    status = -1
    253401    pid = None
    254402
     403    defaultWriter = 0 # FIXME: hack
     404
    255405    def __init__(self,
    256406                 reactor, command, args, environment, path, proto,
    257407                 uid=None, gid=None, childFDs=None):
     
    266416        nuances of setXXuid on UNIX: it will assume that either your effective
    267417        or real UID is 0.)
    268418        """
    269         if not proto:
    270             assert 'r' not in childFDs.values()
    271             assert 'w' not in childFDs.values()
     419        # FIXME: check for just integers?
     420        # if not proto:
     421        #     assert 'r' not in childFDs.values()
     422        #     assert 'w' not in childFDs.values()
    272423        if not signal.getsignal(signal.SIGCHLD):
    273424            log.msg("spawnProcess called, but the SIGCHLD handler is not "
    274425                    "installed. This probably means you have not yet "
     
    313464        debug = self.debug
    314465        if debug: print "childFDs", childFDs
    315466
    316         # fdmap.keys() are filenos of pipes that are used by the child.
    317         fdmap = {} # maps childFD to parentFD
    318467        for childFD, target in childFDs.items():
    319468            if debug: print "[%d]" % childFD, target
    320             if target == "r":
    321                 # we need a pipe that the parent can read from
    322                 readFD, writeFD = os.pipe()
    323                 if debug: print "readFD=%d, writeFD%d" % (readFD, writeFD)
    324                 fdmap[childFD] = writeFD     # child writes to this
    325                 helpers[childFD] = readFD    # parent reads from this
     469            if childFD == '_defaultWriter': # FIXME: hack
     470                self.defaultWriter = target
     471            elif target == "r":
     472                helpers[childFD] = (ReadPipe(), (childFD,))
    326473            elif target == "w":
    327                 # we need a pipe that the parent can write to
    328                 readFD, writeFD = os.pipe()
    329                 if debug: print "readFD=%d, writeFD=%d" % (readFD, writeFD)
    330                 fdmap[childFD] = readFD      # child reads from this
    331                 helpers[childFD] = writeFD   # parent writes to this
     474                helpers[childFD] = (WritePipe(), (childFD,))
     475            elif type(target) == int: # FIXME: isinstance??
     476                helpers[childFD] = (PassthroughFD(target), (childFD,))
     477            elif isinstance(target, ProcessHelper):
     478                helpers[childFD] = (target, (childFD,))
     479            elif type(target) in (types.ListType, types.TupleType): # FIXME: isinstance??
     480                helpers[childFD] = target # FIXME: more checks?
    332481            else:
    333                 assert type(target) == int, '%r should be an int' % (target,)
    334                 fdmap[childFD] = target      # parent ignores this
    335         if debug: print "fdmap", fdmap
     482                raise ValueError('%r is of unknown type' % (target,))
    336483        if debug: print "helpers", helpers
    337         # the child only cares about fdmap.values()
    338484
     485        # FIXME: check for duplicates in childFD lists?
     486
     487        for helper, _ in helpers.values():
     488            helper.setupPrefork()
     489
    339490        self.pid = os.fork()
    340491        if self.pid == 0: # pid is 0 in the child process
    341492
     
    351502            try:
    352503                # stop debugging, if I am!  I don't care anymore!
    353504                sys.settrace(None)
    354                 # close all parent-side pipes
     505                # setup child FDs
     506                fdmap = {}
     507                for helper, childFDlist in helpers.values():
     508                    childFD = helper.setupChild()
     509                    for fd in childFDlist:
     510                        fdmap[fd] = childFD
     511                # map all FDs to where they belong
    355512                self._setupChild(fdmap)
    356513                self._execChild(path, settingUID, uid, gid,
    357514                                command, args, environment)
     
    385542
    386543        self.proto = proto
    387544       
    388         # arrange for the parent-side pipes to be read and written
    389         for childFD, parentFD in helpers.items():
    390             os.close(fdmap[childFD])
     545        # setup the parent side helpers
     546        for childFD, (helper, _) in helpers.items():
     547            trans = helper.setupParent(reactor, self, childFD)
     548            if trans is not None:
     549                self.pipes[childFD] = trans
    391550
    392             if childFDs[childFD] == "r":
    393                 reader = ProcessReader(reactor, self, childFD, parentFD)
    394                 self.pipes[childFD] = reader
    395 
    396             if childFDs[childFD] == "w":
    397                 writer = ProcessWriter(reactor, self, childFD, parentFD, forceReadHack=True)
    398                 self.pipes[childFD] = writer
    399 
    400551        try:
    401552            # the 'transport' is used for some compatibility methods
    402553            if self.proto is not None:
     
    580731       
    581732        NOTE: This will silently lose data if there is no standard input.
    582733        """
    583         if self.pipes.has_key(0):
    584             self.pipes[0].write(data)
     734        if self.pipes.has_key(self.defaultWriter):
     735            self.pipes[self.defaultWriter].write(data)
    585736
    586737    def registerProducer(self, producer, streaming):
    587738        """Call this to register producer for standard input.
     
    589740        If there is no standard input producer.stopProducing() will
    590741        be called immediately.
    591742        """
    592         if self.pipes.has_key(0):
    593             self.pipes[0].registerProducer(producer, streaming)
     743        if self.pipes.has_key(self.defaultWriter):
     744            self.pipes[self.defaultWriter].registerProducer(producer, streaming)
    594745        else:
    595746            producer.stopProducing()
    596747
    597748    def unregisterProducer(self):
    598749        """Call this to unregister producer for standard input."""
    599         if self.pipes.has_key(0):
    600             self.pipes[0].unregisterProducer()
     750        if self.pipes.has_key(self.defaultWriter):
     751            self.pipes[self.defaultWriter].unregisterProducer()
    601752   
    602753    def writeSequence(self, seq):
    603754        """Call this to write to standard input on this process.
    604755
    605756        NOTE: This will silently lose data if there is no standard input.
    606757        """
    607         if self.pipes.has_key(0):
    608             self.pipes[0].writeSequence(seq)
     758        if self.pipes.has_key(self.defaultWriter):
     759            self.pipes[self.defaultWriter].writeSequence(seq)
    609760
    610761    def childDataReceived(self, name, data):
    611762        self.proto.childDataReceived(name, data)