Ticket #5248: port-from-fd-5248-1.patch

File port-from-fd-5248-1.patch, 12.1 KB (added by rwall, 5 years ago)

Here is my first attempt...don't think Exarkun's going to like it :)

  • fake_inetd.py

    === added file 'fake_inetd.py'
     
     1#!/usr/bin/env python
     2
     3if __name__ == '__main__':
     4    import fake_inetd
     5    raise SystemExit(fake_inetd.main())
     6
     7from select import select
     8from socket import socket, SOL_SOCKET, SO_REUSEADDR
     9from subprocess import Popen
     10
     11def main():
     12    s = socket()
     13    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
     14    s.bind(('127.0.0.1', 1080))
     15    s.listen(0)
     16    while True:
     17        r, w, e = select([s], [], [])
     18        if r:
     19            p = Popen(['python', 'line_reflector.py'], stdin=s.fileno())
     20            p.wait()
  • inetd.conf

    === added file 'inetd.conf'
     
     11080 stream  tcp     wait  richard      /home/richard/Projects/Twisted/branches/port-from-fd-5248/line_reflector.py
  • line_reflector.py

    === added file 'line_reflector.py'
     
     1#!/usr/bin/env python
     2
     3if __name__ == '__main__':
     4    import line_reflector
     5    raise SystemExit(line_reflector.main())
     6
     7"""
     8
     9"""
     10
     11from twisted.internet import reactor
     12from twisted.internet.tcp import SharedPort
     13from twisted.internet.protocol import ServerFactory
     14from twisted.protocols.basic import LineReceiver
     15
     16
     17class LineReflector(LineReceiver):
     18    def lineReceived(self, line):
     19        msg = 'LINE: %r' % (line,)
     20        print msg
     21        self.sendLine(msg)
     22        if line == 'QUIT':
     23            self.transport.loseConnection()
     24        if line == 'SHUTDOWN':
     25            reactor.stop()
     26
     27
     28def main():
     29    f = ServerFactory()
     30    f.protocol = LineReflector
     31    p = SharedPort(0, f, reactor=reactor)
     32    p.startListening()
     33
     34    reactor.run()
  • twisted/internet/base.py

    === modified file 'twisted/internet/base.py'
     
    10821082    addressFamily = None
    10831083    socketType = None
    10841084
    1085     def createInternetSocket(self):
    1086         s = socket.socket(self.addressFamily, self.socketType)
     1085    def createInternetSocket(self, fd=None):
     1086        """
     1087        @param fd: Optional filedescriptor number of an existing socket fd.
     1088        """
     1089        if fd is None:
     1090            s = socket.socket(self.addressFamily, self.socketType)
     1091            fdesc._setCloseOnExec(s.fileno())
     1092        else:
     1093            s = socket.fromfd(fd, self.addressFamily, self.socketType)
     1094
    10871095        s.setblocking(0)
    1088         fdesc._setCloseOnExec(s.fileno())
     1096
    10891097        return s
    10901098
    10911099
  • twisted/internet/tcp.py

    === modified file 'twisted/internet/tcp.py'
     
    489489        return address.IPv4Address('TCP', *self.client)
    490490
    491491
    492 
    493 class Port(base.BasePort, _SocketCloser):
     492class BaseTcpPort(base.BasePort):
    494493    """
    495494    A TCP server port, listening for connections.
    496495
     
    530529    # value when we are actually listening.
    531530    _realPortNumber = None
    532531
    533     def __init__(self, port, factory, backlog=50, interface='', reactor=None):
    534         """Initialize with a numeric port to listen on.
    535         """
    536         base.BasePort.__init__(self, reactor=reactor)
    537         self.port = port
    538         self.factory = factory
    539         self.backlog = backlog
    540         self.interface = interface
    541 
    542532    def __repr__(self):
    543533        if self._realPortNumber is not None:
    544534            return "<%s of %s on %s>" % (self.__class__, self.factory.__class__,
     
    546536        else:
    547537            return "<%s of %s (not listening)>" % (self.__class__, self.factory.__class__)
    548538
    549     def createInternetSocket(self):
    550         s = base.BasePort.createInternetSocket(self)
    551         if platformType == "posix" and sys.platform != "cygwin":
    552             s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    553         return s
    554 
    555539
    556540    def startListening(self):
    557541        """Create and bind my socket, and begin listening on it.
     
    559543        This is called on unserialization, and must be called after creating a
    560544        server to begin listening on the specified port.
    561545        """
    562         try:
    563             skt = self.createInternetSocket()
    564             skt.bind((self.interface, self.port))
    565         except socket.error, le:
    566             raise CannotListenError, (self.interface, self.port, le)
     546        skt = self.createInternetSocket()
    567547
    568548        # Make sure that if we listened on port 0, we update that to
    569549        # reflect what the OS actually assigned us.
     
    658638            # and return, so handling it here works just as well.
    659639            log.deferr()
    660640
     641
    661642    def loseConnection(self, connDone=failure.Failure(main.CONNECTION_DONE)):
    662643        """
    663644        Stop accepting connections on this port.
     
    707688        """
    708689        return reflect.qual(self.factory.__class__)
    709690
     691
    710692    def getHost(self):
    711693        """Returns an IPv4Address.
    712694
     
    714696        """
    715697        return address.IPv4Address('TCP', *self.socket.getsockname())
    716698
     699
     700class Port(BaseTcpPort, _SocketCloser):
     701    """
     702    A TCP server port, listening for connections.
     703
     704    When a connection is accepted, this will call a factory's buildProtocol
     705    with the incoming address as an argument, according to the specification
     706    described in L{twisted.internet.interfaces.IProtocolFactory}.
     707
     708    If you wish to change the sort of transport that will be used, the
     709    C{transport} attribute will be called with the signature expected for
     710    C{Server.__init__}, so it can be replaced.
     711
     712    @ivar deferred: a deferred created when L{stopListening} is called, and
     713        that will fire when connection is lost. This is not to be used it
     714        directly: prefer the deferred returned by L{stopListening} instead.
     715    @type deferred: L{defer.Deferred}
     716
     717    @ivar disconnecting: flag indicating that the L{stopListening} method has
     718        been called and that no connections should be accepted anymore.
     719    @type disconnecting: C{bool}
     720
     721    @ivar connected: flag set once the listen has successfully been called on
     722        the socket.
     723    @type connected: C{bool}
     724    """
     725
     726    def __init__(self, port, factory, backlog=50, interface='', reactor=None):
     727        """Initialize with a numeric port to listen on.
     728        """
     729        BaseTcpPort.__init__(self, reactor=reactor)
     730        self.port = port
     731        self.factory = factory
     732        self.backlog = backlog
     733        self.interface = interface
     734
     735
     736    def createInternetSocket(self):
     737        try:
     738            s = BaseTcpPort.createInternetSocket(self)
     739            if platformType == "posix" and sys.platform != "cygwin":
     740                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     741            s.bind((self.interface, self.port))
     742            return s
     743        except socket.error, le:
     744            raise CannotListenError, (self.interface, self.port, le)
     745
     746
     747class SharedPort(BaseTcpPort):
     748    """
     749    A TCP server port for which a listening socket already exists. eg
     750    a socket may have been setup by a parent process such as inetd or
     751    systemd.
     752
     753    The socket will not be destroyed when this Port stops listening or
     754    is destroyed.
     755    """
     756
     757    def __init__(self, fd, factory, backlog=50, reactor=None):
     758        """
     759        Initialize with the file descriptor number of an existing
     760        listening socket.
     761        """
     762        BaseTcpPort.__init__(self, reactor=reactor)
     763        self.fd = fd
     764        self.factory = factory
     765        self.backlog = backlog
     766
     767
     768    def createInternetSocket(self):
     769        try:
     770            s = BaseTcpPort.createInternetSocket(self, fd=self.fd)
     771            if platformType == "posix" and sys.platform != "cygwin":
     772                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     773            return s
     774        except socket.error, le:
     775            raise CannotListenError, (self.interface, self.fd, le)
     776
     777
     778    def _closeSocket(self):
     779        pass
     780
     781
    717782class Connector(base.BaseConnector):
    718783    def __init__(self, host, port, factory, timeout, bindAddress, reactor=None):
    719784        self.host = host
  • twisted/internet/test/test_unix.py

    === modified file 'twisted/internet/test/test_unix.py'
     
    159159        """
    160160        Get the expected connection lost message for a UNIX port
    161161        """
    162         return "(UNIX Port %s Closed)" % (repr(port.port),)
     162        return "(UNIX Port %s Closed)" % (repr(port.fileName),)
    163163
    164164
    165165
  • twisted/internet/unix.py

    === modified file 'twisted/internet/unix.py'
     
    6767
    6868
    6969
    70 class Port(_UNIXPort, tcp.Port):
     70class Port(_UNIXPort, tcp.BaseTcpPort):
    7171    addressFamily = socket.AF_UNIX
    7272    socketType = socket.SOCK_STREAM
    7373
     
    7575    lockFile = None
    7676
    7777    def __init__(self, fileName, factory, backlog=50, mode=0666, reactor=None, wantPID = 0):
    78         tcp.Port.__init__(self, fileName, factory, backlog, reactor=reactor)
     78        tcp.BaseTcpPort.__init__(self, reactor=reactor)
     79        self.fileName = fileName
     80        self.factory = factory
     81        self.backlog = backlog
     82
    7983        self.mode = mode
    8084        self.wantPID = wantPID
    8185
    8286    def __repr__(self):
    8387        factoryName = reflect.qual(self.factory.__class__)
    8488        if hasattr(self, 'socket'):
    85             return '<%s on %r>' % (factoryName, self.port)
     89            return '<%s on %r>' % (factoryName, self.fileName)
    8690        else:
    8791            return '<%s (not listening)>' % (factoryName,)
    8892
     
    96100        This is called on unserialization, and must be called after creating a
    97101        server to begin listening on the specified port.
    98102        """
    99         log.msg("%s starting on %r" % (self.factory.__class__, repr(self.port)))
     103        log.msg("%s starting on %r" % (self.factory.__class__, repr(self.fileName)))
    100104        if self.wantPID:
    101             self.lockFile = lockfile.FilesystemLock(self.port + ".lock")
     105            self.lockFile = lockfile.FilesystemLock(self.fileName + ".lock")
    102106            if not self.lockFile.lock():
    103                 raise CannotListenError, (None, self.port, "Cannot acquire lock")
     107                raise CannotListenError, (None, self.fileName, "Cannot acquire lock")
    104108            else:
    105109                if not self.lockFile.clean:
    106110                    try:
     
    109113                        # If it fails, there's not much else we can
    110114                        # do.  The bind() below will fail with an
    111115                        # exception that actually propagates.
    112                         if stat.S_ISSOCK(os.stat(self.port).st_mode):
    113                             os.remove(self.port)
     116                        if stat.S_ISSOCK(os.stat(self.fileName).st_mode):
     117                            os.remove(self.fileName)
    114118                    except:
    115119                        pass
    116120
    117121        self.factory.doStart()
    118122        try:
    119123            skt = self.createInternetSocket()
    120             skt.bind(self.port)
     124            skt.bind(self.fileName)
    121125        except socket.error, le:
    122             raise CannotListenError, (None, self.port, le)
     126            raise CannotListenError, (None, self.fileName, le)
    123127        else:
    124             if _inFilesystemNamespace(self.port):
     128            if _inFilesystemNamespace(self.fileName):
    125129                # Make the socket readable and writable to the world.
    126                 os.chmod(self.port, self.mode)
     130                os.chmod(self.fileName, self.mode)
    127131            skt.listen(self.backlog)
    128132            self.connected = True
    129133            self.socket = skt
     
    136140        """
    137141        Log message for closing socket
    138142        """
    139         log.msg('(UNIX Port %s Closed)' % (repr(self.port),))
    140 
    141 
    142     def connectionLost(self, reason):
    143         if _inFilesystemNamespace(self.port):
    144             os.unlink(self.port)
     143        log.msg('(UNIX Port %s Closed)' % (repr(self.fileName),))
     144
     145
     146    def _closeSocket(self):
     147        if _inFilesystemNamespace(self.fileName):
     148            os.unlink(self.fileName)
    145149        if self.lockFile is not None:
    146150            self.lockFile.unlock()
    147         tcp.Port.connectionLost(self, reason)
    148151
    149152
    150153