Exemple #1
0
    def __init__(self):
        MultiService.__init__(self)

        # Dispatcher
        self.dispatcher = InheritedSocketDispatcher(self)

        # Child Processes
        self.log.info("Setting up master/child spawning service...")
        self.spawningService = ChildSpawningService(self.dispatcher)
        self.spawningService.setServiceParent(self)
Exemple #2
0
 def __init__(self, maxAccepts, maxRequests):
     """
     Create a L{ConnectionLimiter} with an associated dispatcher and
     list of factories.
     """
     MultiService.__init__(self)
     self.factories = []
     # XXX dispatcher needs to be a service, so that it can shut down its
     # sub-sockets.
     self.dispatcher = InheritedSocketDispatcher(self)
     self.maxAccepts = maxAccepts
     self.maxRequests = maxRequests
     self.overloaded = False
 def test_nonBlocking(self):
     """
     Creating a L{_SubprocessSocket} via
     L{InheritedSocketDispatcher.addSocket} results in a non-blocking
     L{socket.socket} object being assigned to its C{skt} attribute, as well
     as a non-blocking L{socket.socket} object being returned.
     """
     dispatcher = InheritedSocketDispatcher(None)
     dispatcher.startDispatching()
     reactor = ReaderAdder()
     dispatcher.reactor = reactor
     inputSocket = dispatcher.addSocket()
     outputSocket = reactor.readers[-1]
     self.assertTrue(isNonBlocking(inputSocket), "Input is blocking.")
     self.assertTrue(isNonBlocking(outputSocket), "Output is blocking.")
Exemple #4
0
 def __init__(self, maxAccepts, maxRequests):
     """
     Create a L{ConnectionLimiter} with an associated dispatcher and
     list of factories.
     """
     MultiService.__init__(self)
     self.factories = []
     # XXX dispatcher needs to be a service, so that it can shut down its
     # sub-sockets.
     self.dispatcher = InheritedSocketDispatcher(self)
     self.maxAccepts = maxAccepts
     self.maxRequests = maxRequests
Exemple #5
0
 def test_addAfterStart(self):
     """
     Adding a socket to an L{InheritedSocketDispatcher} after it has already
     been started results in it immediately starting reading.
     """
     reactor = ReaderAdder()
     dispatcher = InheritedSocketDispatcher(None)
     dispatcher.reactor = reactor
     dispatcher.startDispatching()
     dispatcher.addSocket()
     self.assertEquals(reactor.getReaders(), dispatcher._subprocessSockets)
Exemple #6
0
class ConnectionLimiter(MultiService, object):
    """
    Connection limiter for use with L{InheritedSocketDispatcher}.

    This depends on statuses being reported by L{ReportingHTTPFactory}
    """

    _outstandingRequests = 0

    def __init__(self, maxAccepts, maxRequests):
        """
        Create a L{ConnectionLimiter} with an associated dispatcher and
        list of factories.
        """
        MultiService.__init__(self)
        self.factories = []
        # XXX dispatcher needs to be a service, so that it can shut down its
        # sub-sockets.
        self.dispatcher = InheritedSocketDispatcher(self)
        self.maxAccepts = maxAccepts
        self.maxRequests = maxRequests


    def startService(self):
        """
        Start up multiservice, then start up the dispatcher.
        """
        super(ConnectionLimiter, self).startService()
        self.dispatcher.startDispatching()


    def addPortService(self, description, port, interface, backlog,
                       serverServiceMaker=MaxAcceptTCPServer):
        """
        Add a L{MaxAcceptTCPServer} to bind a TCP port to a socket description.
        """
        lipf = LimitingInheritingProtocolFactory(self, description)
        self.factories.append(lipf)
        serverServiceMaker(
            port, lipf,
            interface=interface,
            backlog=backlog
        ).setServiceParent(self)


    # IStatusWatcher

    def initialStatus(self):
        """
        The status of a new worker added to the pool.
        """
        return WorkerStatus()


    def statusFromMessage(self, previousStatus, message):
        """
        Determine a subprocess socket's status from its previous status and a
        status message.
        """
        if message == '-':
            # A connection has gone away in a subprocess; we should start
            # accepting connections again if we paused (see
            # newConnectionStatus)
            return previousStatus - WorkerStatus(acknowledged=1)
        elif message == '0':
            # A new process just started accepting new connections.  It might
            # still have some unacknowledged connections, but any connections
            # that it acknowledged working on are now completed.  (We have no
            # way of knowing whether the acknowledged connections were acted
            # upon or dropped, so we have to treat that number with a healthy
            # amount of skepticism.)
            return previousStatus.restarted()
        else:
            # '+' acknowledges that the subprocess has taken on the work.
            return previousStatus + WorkerStatus(acknowledged=1,
                                                 unacknowledged=-1,
                                                 unclosed=1)


    def closeCountFromStatus(self, status):
        """
        Determine the number of sockets to close from the current status.
        """
        toClose = status.unclosed
        return (toClose, status - WorkerStatus(unclosed=toClose))


    def newConnectionStatus(self, previousStatus):
        """
        Determine the effect of a new connection being sent on a subprocess
        socket.
        """
        return previousStatus + WorkerStatus(unacknowledged=1)


    def statusesChanged(self, statuses):
        """
        The L{InheritedSocketDispatcher} is reporting that the list of
        connection-statuses have changed.

        (The argument to this function is currently duplicated by the
        C{self.dispatcher.statuses} attribute, which is what
        C{self.outstandingRequests} uses to compute it.)
        """
        current = sum(status.effective()
                      for status in self.dispatcher.statuses)
        self._outstandingRequests = current # preserve for or= field in log
        maximum = self.maxRequests
        overloaded = (current >= maximum)
        for f in self.factories:
            if overloaded:
                f.loadAboveMaximum()
            else:
                f.loadNominal()


    @property # make read-only
    def outstandingRequests(self):
        return self._outstandingRequests
Exemple #7
0
class ConnectionLimiter(MultiService, object):
    """
    Connection limiter for use with L{InheritedSocketDispatcher}.

    This depends on statuses being reported by L{ReportingHTTPFactory}
    """

    _outstandingRequests = 0
    _maxOutstandingRequests = 0

    def __init__(self, maxAccepts, maxRequests):
        """
        Create a L{ConnectionLimiter} with an associated dispatcher and
        list of factories.
        """
        MultiService.__init__(self)
        self.factories = []
        # XXX dispatcher needs to be a service, so that it can shut down its
        # sub-sockets.
        self.dispatcher = InheritedSocketDispatcher(self)
        self.maxAccepts = maxAccepts
        self.maxRequests = maxRequests
        self.overloaded = False

    def startService(self):
        """
        Start up multiservice, then start up the dispatcher.
        """
        super(ConnectionLimiter, self).startService()
        self.dispatcher.startDispatching()

    def addPortService(self,
                       description,
                       port,
                       interface,
                       backlog,
                       serverServiceMaker=MaxAcceptTCPServer):
        """
        Add a L{MaxAcceptTCPServer} to bind a TCP port to a socket description.
        """
        lipf = LimitingInheritingProtocolFactory(self, description)
        self.factories.append(lipf)
        serverServiceMaker(port, lipf, interface=interface,
                           backlog=backlog).setServiceParent(self)

    def addSocketFileService(self, description, address, backlog=None):
        lipf = LimitingInheritingProtocolFactory(self, description)
        self.factories.append(lipf)
        MaxAcceptSocketFileServer(lipf, address,
                                  backlog=backlog).setServiceParent(self)

    # IStatusWatcher

    def initialStatus(self):
        """
        The status of a new worker added to the pool.
        """
        return WorkerStatus()

    def statusFromMessage(self, previousStatus, message):
        """
        Determine a subprocess socket's status from its previous status and a
        status message.
        """
        if message == '-':
            # A connection has gone away in a subprocess; we should start
            # accepting connections again if we paused (see
            # newConnectionStatus)
            return previousStatus.adjust(acknowledged=-1)

        elif message == '0':
            # A new process just started accepting new connections.
            return previousStatus.restarted()

        else:
            # '+' acknowledges that the subprocess has taken on the work.
            return previousStatus.adjust(
                acknowledged=1,
                unacknowledged=-1,
                total=1,
                unclosed=1,
            )

    def closeCountFromStatus(self, status):
        """
        Determine the number of sockets to close from the current status.
        """
        toClose = status.unclosed
        return (toClose, status.adjust(unclosed=-toClose))

    def newConnectionStatus(self, previousStatus):
        """
        A connection was just sent to the process, but not yet acknowledged.
        """
        return previousStatus.adjust(unacknowledged=1)

    def statusesChanged(self, statuses):
        """
        The L{InheritedSocketDispatcher} is reporting that the list of
        connection-statuses have changed. Check to see if we are overloaded
        or if there are no active processes left. If so, stop the protocol
        factory from processing more requests until capacity is back.

        (The argument to this function is currently duplicated by the
        C{self.dispatcher.statuses} attribute, which is what
        C{self.outstandingRequests} uses to compute it.)
        """
        current = sum(status.effective()
                      for status in self.dispatcher.statuses)
        self._outstandingRequests = current  # preserve for or= field in log
        self._maxOutstandingRequests = max(self._maxOutstandingRequests,
                                           self._outstandingRequests)
        maximum = self.maxRequests
        overloaded = (current >= maximum)
        available = len(filter(lambda x: x.active(), self.dispatcher.statuses))
        self.overloaded = (overloaded or available == 0)
        for f in self.factories:
            if self.overloaded:
                f.loadAboveMaximum()
            else:
                f.loadNominal()

    @property  # make read-only
    def outstandingRequests(self):
        return self._outstandingRequests

    @property  # make read-only
    def maxOutstandingRequests(self):
        """
        Reset the max value to the current outstanding value every time the max is read. It
        is up to the caller to track the overall max value.
        """
        temp = self._maxOutstandingRequests
        self._maxOutstandingRequests = self._outstandingRequests
        return temp
Exemple #8
0
 def setUp(self):
     self.dispatcher = InheritedSocketDispatcher(ConnectionLimiter(2, 20))
     self.dispatcher.reactor = ReaderAdder()
Exemple #9
0
class InheritedSocketDispatcherTests(TestCase):
    """
    Inherited socket dispatcher tests.
    """
    def setUp(self):
        self.dispatcher = InheritedSocketDispatcher(ConnectionLimiter(2, 20))
        self.dispatcher.reactor = ReaderAdder()

    def test_closeSomeSockets(self):
        """
        L{InheritedSocketDispatcher} determines how many sockets to close from
        L{IStatusWatcher.closeCountFromStatus}.
        """
        self.dispatcher.statusWatcher = Watcher([])

        class SocketForClosing(object):
            blocking = True
            closed = False

            def setblocking(self, b):
                self.blocking = b

            def fileno(self):
                return object()

            def close(self):
                self.closed = True

        one = SocketForClosing()
        two = SocketForClosing()
        three = SocketForClosing()

        skt = self.dispatcher.addSocket(
            lambda: (SocketForClosing(), SocketForClosing()))
        skt.restarted()

        self.dispatcher.sendFileDescriptor(one, "one")
        self.dispatcher.sendFileDescriptor(two, "two")
        self.dispatcher.sendFileDescriptor(three, "three")

        def sendfd(unixSocket, tcpSocket, description):
            pass

        # Put something into the socket-close queue.
        self.dispatcher._subprocessSockets[0].doWrite(sendfd)
        # Nothing closed yet.
        self.assertEquals(one.closed, False)
        self.assertEquals(two.closed, False)
        self.assertEquals(three.closed, False)

        def recvmsg(fileno):
            return 'data', 0, 0

        self.dispatcher._subprocessSockets[0].doRead(recvmsg)
        # One socket closed.
        self.assertEquals(one.closed, True)
        self.assertEquals(two.closed, False)
        self.assertEquals(three.closed, False)

    def test_nonBlocking(self):
        """
        Creating a L{_SubprocessSocket} via
        L{InheritedSocketDispatcher.addSocket} results in a non-blocking
        L{socket.socket} object being assigned to its C{skt} attribute, as well
        as a non-blocking L{socket.socket} object being returned.
        """
        dispatcher = self.dispatcher
        dispatcher.startDispatching()
        inputSocket = dispatcher.addSocket()
        outputSocket = self.dispatcher.reactor.readers[-1]
        self.assertTrue(isNonBlocking(inputSocket), "Input is blocking.")
        self.assertTrue(isNonBlocking(outputSocket), "Output is blocking.")

    def test_addAfterStart(self):
        """
        Adding a socket to an L{InheritedSocketDispatcher} after it has already
        been started results in it immediately starting reading.
        """
        dispatcher = self.dispatcher
        dispatcher.startDispatching()
        dispatcher.addSocket()
        self.assertEquals(dispatcher.reactor.getReaders(),
                          dispatcher._subprocessSockets)

    def test_statusesChangedOnNewConnection(self):
        """
        L{InheritedSocketDispatcher.sendFileDescriptor} will update its
        C{statusWatcher} via C{statusesChanged}.
        """
        q = []
        dispatcher = self.dispatcher
        dispatcher.statusWatcher = Watcher(q)
        description = "whatever"
        # Need to have a socket that will accept the descriptors.
        skt = dispatcher.addSocket()
        skt.restarted()
        dispatcher.sendFileDescriptor(object(), description)
        dispatcher.sendFileDescriptor(object(), description)
        self.assertEquals(q, [[(0, True)], [(1, True)], [(2, True)]])

    def test_statusesChangedOnStatusMessage(self):
        """
        L{InheritedSocketDispatcher.sendFileDescriptor} will update its
        C{statusWatcher} will update its C{statusWatcher} via
        C{statusesChanged}.
        """
        q = []
        dispatcher = self.dispatcher
        dispatcher.statusWatcher = Watcher(q)
        message = "whatever"
        # Need to have a socket that will accept the descriptors.
        dispatcher.addSocket()
        subskt = dispatcher._subprocessSockets[0]
        dispatcher.statusMessage(subskt, message)
        dispatcher.statusMessage(subskt, message)
        self.assertEquals(q, [[(-1, False)], [(-2, False)]])

    def test_statusesChangedOnStartRestartStop(self):
        """
        L{_SubprocessSocket} will update its C{status} when state change.
        """
        q = []
        dispatcher = self.dispatcher
        dispatcher.statusWatcher = Watcher(q)
        message = "whatever"
        # Need to have a socket that will accept the descriptors.
        subskt = dispatcher.addSocket()
        subskt.start()
        subskt.restarted()
        dispatcher.sendFileDescriptor(subskt, message)
        subskt.stop()
        subskt.start()
        subskt.restarted()
        self.assertEquals(q, [
            [(0, False)],
            [(0, True)],
            [(1, True)],
            [(0, False)],
            [(0, False)],
            [(0, True)],
        ])
Exemple #10
0
class MasterService(MultiService, object):
    """
    Service for master processes.
    """

    log = Logger()

    def __init__(self):
        MultiService.__init__(self)

        # Dispatcher
        self.dispatcher = InheritedSocketDispatcher(self)

        # Child Processes
        self.log.info("Setting up master/child spawning service...")
        self.spawningService = ChildSpawningService(self.dispatcher)
        self.spawningService.setServiceParent(self)

    def addProtocol(self, protocol, port):
        self.log.info(
            "Setting service for protocol {protocol!r} on port {port}...",
            protocol=protocol, port=port,
        )

        # TCP Service
        tcpFactory = SpawningInheritingProtocolFactory(
            self.dispatcher, self.spawningService, protocol
        )
        tcpService = TCPServer(port, tcpFactory)

        tcpService.setServiceParent(self)

    def startService(self):
        """
        Start up multiservice, then start up the dispatcher.
        """
        super(MasterService, self).startService()
        self.dispatcher.startDispatching()

    # IStatusWatcher

    @staticmethod
    def initialStatus():
        return ChildStatus()

    @staticmethod
    def newConnectionStatus(previousStatus):
        """
        A connection was just sent to the process, but not yet acknowledged.
        """
        return previousStatus.adjust(unacknowledged=1)

    @staticmethod
    def statusFromMessage(previousStatus, message):
        if message == "-":
            # A connection has gone away in a subprocess; we should start
            # accepting connections again if we paused (see
            # newConnectionStatus)
            return previousStatus.adjust(acknowledged=-1)

        elif message == "0":
            # A new process just started accepting new connections.
            return previousStatus.restarted()

        elif message == "+":
            # Acknowledges that the subprocess has taken on the work.
            return previousStatus.adjust(
                acknowledged=1,
                unacknowledged=-1,
                total=1,
                unclosed=1,
            )

        else:
            raise AssertionError("Unknown message: {0}".format(message))

    @staticmethod
    def closeCountFromStatus(previousStatus):
        toClose = previousStatus.unclosed
        return (toClose, previousStatus.adjust(unclosed=-toClose))

    def statusesChanged(self, statuses):
        # FIXME: This isn't in IStatusWatcher, but is called by
        # InheritedSocketDispatcher.

        self.log.info("Status changed: {0}".format(tuple(statuses)))
    def test_sendFileDescriptorSorting(self):
        """
        Make sure InheritedSocketDispatcher.sendFileDescriptor sorts sockets with status None
        higher than those with int status values.
        """

        self.patch(_SubprocessSocket, 'sendSocketToPeer', lambda x, y, z:None)
        dispatcher = InheritedSocketDispatcher(ConnectionLimiter(2, 20))
        dispatcher.addSocket()
        dispatcher.addSocket()
        dispatcher.addSocket()

        sockets = dispatcher._subprocessSockets[:]

        # Check that 0 is preferred over None
        sockets[0].status = 0
        sockets[1].status = 1
        sockets[2].status = None

        dispatcher.sendFileDescriptor(None, "")

        self.assertEqual(sockets[0].status, 1)
        self.assertEqual(sockets[1].status, 1)
        self.assertEqual(sockets[2].status, None)

        dispatcher.sendFileDescriptor(None, "")

        self.assertEqual(sockets[0].status, 1)
        self.assertEqual(sockets[1].status, 1)
        self.assertEqual(sockets[2].status, 1)

        # Check that after going to 1 and back to 0 that is still preferred over None
        sockets[0].status = 0
        sockets[1].status = 1
        sockets[2].status = None

        dispatcher.sendFileDescriptor(None, "")

        self.assertEqual(sockets[0].status, 1)
        self.assertEqual(sockets[1].status, 1)
        self.assertEqual(sockets[2].status, None)

        sockets[1].status = 0

        dispatcher.sendFileDescriptor(None, "")

        self.assertEqual(sockets[0].status, 1)
        self.assertEqual(sockets[1].status, 1)
        self.assertEqual(sockets[2].status, None)
 def setUp(self):
     self.dispatcher = InheritedSocketDispatcher(ConnectionLimiter(2, 20))
     self.dispatcher.reactor = ReaderAdder()
class InheritedSocketDispatcherTests(TestCase):
    """
    Inherited socket dispatcher tests.
    """
    def setUp(self):
        self.dispatcher = InheritedSocketDispatcher(ConnectionLimiter(2, 20))
        self.dispatcher.reactor = ReaderAdder()


    def test_closeSomeSockets(self):
        """
        L{InheritedSocketDispatcher} determines how many sockets to close from
        L{IStatusWatcher.closeCountFromStatus}.
        """
        self.dispatcher.statusWatcher = Watcher([])
        class SocketForClosing(object):
            blocking = True
            closed = False
            def setblocking(self, b):
                self.blocking = b
            def fileno(self):
                return object()
            def close(self):
                self.closed = True

        one = SocketForClosing()
        two = SocketForClosing()
        three = SocketForClosing()

        skt = self.dispatcher.addSocket(
            lambda: (SocketForClosing(), SocketForClosing())
        )
        skt.restarted()

        self.dispatcher.sendFileDescriptor(one, "one")
        self.dispatcher.sendFileDescriptor(two, "two")
        self.dispatcher.sendFileDescriptor(three, "three")
        def sendfd(unixSocket, tcpSocket, description):
            pass
        # Put something into the socket-close queue.
        self.dispatcher._subprocessSockets[0].doWrite(sendfd)
        # Nothing closed yet.
        self.assertEquals(one.closed, False)
        self.assertEquals(two.closed, False)
        self.assertEquals(three.closed, False)

        def recvmsg(fileno):
            return 'data', 0, 0
        self.dispatcher._subprocessSockets[0].doRead(recvmsg)
        # One socket closed.
        self.assertEquals(one.closed, True)
        self.assertEquals(two.closed, False)
        self.assertEquals(three.closed, False)


    def test_nonBlocking(self):
        """
        Creating a L{_SubprocessSocket} via
        L{InheritedSocketDispatcher.addSocket} results in a non-blocking
        L{socket.socket} object being assigned to its C{skt} attribute, as well
        as a non-blocking L{socket.socket} object being returned.
        """
        dispatcher = self.dispatcher
        dispatcher.startDispatching()
        inputSocket = dispatcher.addSocket()
        outputSocket = self.dispatcher.reactor.readers[-1]
        self.assertTrue(isNonBlocking(inputSocket), "Input is blocking.")
        self.assertTrue(isNonBlocking(outputSocket), "Output is blocking.")


    def test_addAfterStart(self):
        """
        Adding a socket to an L{InheritedSocketDispatcher} after it has already
        been started results in it immediately starting reading.
        """
        dispatcher = self.dispatcher
        dispatcher.startDispatching()
        dispatcher.addSocket()
        self.assertEquals(dispatcher.reactor.getReaders(),
                          dispatcher._subprocessSockets)


    def test_statusesChangedOnNewConnection(self):
        """
        L{InheritedSocketDispatcher.sendFileDescriptor} will update its
        C{statusWatcher} via C{statusesChanged}.
        """
        q = []
        dispatcher = self.dispatcher
        dispatcher.statusWatcher = Watcher(q)
        description = "whatever"
        # Need to have a socket that will accept the descriptors.
        skt = dispatcher.addSocket()
        skt.restarted()
        dispatcher.sendFileDescriptor(object(), description)
        dispatcher.sendFileDescriptor(object(), description)
        self.assertEquals(q, [[(0, True)], [(1, True)], [(2, True)]])


    def test_statusesChangedOnStatusMessage(self):
        """
        L{InheritedSocketDispatcher.sendFileDescriptor} will update its
        C{statusWatcher} will update its C{statusWatcher} via
        C{statusesChanged}.
        """
        q = []
        dispatcher = self.dispatcher
        dispatcher.statusWatcher = Watcher(q)
        message = "whatever"
        # Need to have a socket that will accept the descriptors.
        dispatcher.addSocket()
        subskt = dispatcher._subprocessSockets[0]
        dispatcher.statusMessage(subskt, message)
        dispatcher.statusMessage(subskt, message)
        self.assertEquals(q, [[(-1, False)], [(-2, False)]])


    def test_statusesChangedOnStartRestartStop(self):
        """
        L{_SubprocessSocket} will update its C{status} when state change.
        """
        q = []
        dispatcher = self.dispatcher
        dispatcher.statusWatcher = Watcher(q)
        message = "whatever"
        # Need to have a socket that will accept the descriptors.
        subskt = dispatcher.addSocket()
        subskt.start()
        subskt.restarted()
        dispatcher.sendFileDescriptor(subskt, message)
        subskt.stop()
        subskt.start()
        subskt.restarted()
        self.assertEquals(
            q,
            [
                [(0, False)],
                [(0, True)],
                [(1, True)],
                [(0, False)],
                [(0, False)],
                [(0, True)],
            ]
        )
class ConnectionLimiter(MultiService, object):
    """
    Connection limiter for use with L{InheritedSocketDispatcher}.

    This depends on statuses being reported by L{ReportingHTTPFactory}
    """

    def __init__(self, maxAccepts, maxRequests):
        """
        Create a L{ConnectionLimiter} with an associated dispatcher and
        list of factories.
        """
        MultiService.__init__(self)
        self.factories = []
        # XXX dispatcher needs to be a service, so that it can shut down its
        # sub-sockets.
        self.dispatcher = InheritedSocketDispatcher(self)
        self.maxAccepts = maxAccepts
        self.maxRequests = maxRequests


    def startService(self):
        """
        Start up multiservice, then start up the dispatcher.
        """
        super(ConnectionLimiter, self).startService()
        self.dispatcher.startDispatching()


    def addPortService(self, description, port, interface, backlog):
        """
        Add a L{MaxAcceptTCPServer} to bind a TCP port to a socket description.
        """
        lipf = LimitingInheritingProtocolFactory(self, description)
        self.factories.append(lipf)
        MaxAcceptTCPServer(
            port, lipf,
            interface=interface,
            backlog=backlog
        ).setServiceParent(self)


    # implementation of implicit statusWatcher interface required by
    # InheritedSocketDispatcher

    def statusFromMessage(self, previousStatus, message):
        """
        Determine a subprocess socket's status from its previous status and a
        status message.
        """
        if message in ('-', '0'):
            if message == '-':
                # A connection has gone away in a subprocess; we should start
                # accepting connections again if we paused (see
                # newConnectionStatus)
                result = self.intWithNoneAsZero(previousStatus) - 1
            else:
                # A new process just started accepting new connections; zero
                # out its expected load.
                result = 0
            # If load has indeed decreased (i.e. in any case except 'a new,
            # idle process replaced an old, idle process'), then start
            # listening again.
            if result < previousStatus:
                for f in self.factories:
                    f.myServer.myPort.startReading()
        else:
            # '+' is just an acknowledgement of newConnectionStatus, so we can
            # ignore it.
            result = self.intWithNoneAsZero(previousStatus)
        return result


    def newConnectionStatus(self, previousStatus):
        """
        Determine the effect of a new connection being sent on a subprocess
        socket.
        """
        current = self.outstandingRequests + 1
        maximum = self.maxRequests
        overloaded = (current >= maximum)
        if overloaded:
            for f in self.factories:
                f.myServer.myPort.stopReading()

        result = self.intWithNoneAsZero(previousStatus) + 1
        return result


    def intWithNoneAsZero(self, x):
        """
        Convert 'x' to an C{int}, unless x is C{None}, in which case return 0.
        """
        if x is None:
            return 0
        else:
            return int(x)


    @property
    def outstandingRequests(self):
        outstanding = 0
        for status in self.dispatcher.statuses:
            outstanding += self.intWithNoneAsZero(status)
        return outstanding
Exemple #15
0
class ConnectionLimiter(MultiService, object):
    """
    Connection limiter for use with L{InheritedSocketDispatcher}.

    This depends on statuses being reported by L{ReportingHTTPFactory}
    """

    _outstandingRequests = 0

    def __init__(self, maxAccepts, maxRequests):
        """
        Create a L{ConnectionLimiter} with an associated dispatcher and
        list of factories.
        """
        MultiService.__init__(self)
        self.factories = []
        # XXX dispatcher needs to be a service, so that it can shut down its
        # sub-sockets.
        self.dispatcher = InheritedSocketDispatcher(self)
        self.maxAccepts = maxAccepts
        self.maxRequests = maxRequests
        self.overloaded = False


    def startService(self):
        """
        Start up multiservice, then start up the dispatcher.
        """
        super(ConnectionLimiter, self).startService()
        self.dispatcher.startDispatching()


    def addPortService(self, description, port, interface, backlog,
                       serverServiceMaker=MaxAcceptTCPServer):
        """
        Add a L{MaxAcceptTCPServer} to bind a TCP port to a socket description.
        """
        lipf = LimitingInheritingProtocolFactory(self, description)
        self.factories.append(lipf)
        serverServiceMaker(
            port, lipf,
            interface=interface,
            backlog=backlog
        ).setServiceParent(self)


    def addSocketFileService(self, description, address, backlog=None):
        lipf = LimitingInheritingProtocolFactory(self, description)
        self.factories.append(lipf)
        MaxAcceptSocketFileServer(
            lipf, address, backlog=backlog
        ).setServiceParent(self)


    # IStatusWatcher

    def initialStatus(self):
        """
        The status of a new worker added to the pool.
        """
        return WorkerStatus()


    def statusFromMessage(self, previousStatus, message):
        """
        Determine a subprocess socket's status from its previous status and a
        status message.
        """
        if message == '-':
            # A connection has gone away in a subprocess; we should start
            # accepting connections again if we paused (see
            # newConnectionStatus)
            return previousStatus.adjust(acknowledged=-1)

        elif message == '0':
            # A new process just started accepting new connections.
            return previousStatus.restarted()

        else:
            # '+' acknowledges that the subprocess has taken on the work.
            return previousStatus.adjust(
                acknowledged=1,
                unacknowledged=-1,
                total=1,
                unclosed=1,
            )


    def closeCountFromStatus(self, status):
        """
        Determine the number of sockets to close from the current status.
        """
        toClose = status.unclosed
        return (toClose, status.adjust(unclosed=-toClose))


    def newConnectionStatus(self, previousStatus):
        """
        A connection was just sent to the process, but not yet acknowledged.
        """
        return previousStatus.adjust(unacknowledged=1)


    def statusesChanged(self, statuses):
        """
        The L{InheritedSocketDispatcher} is reporting that the list of
        connection-statuses have changed. Check to see if we are overloaded
        or if there are no active processes left. If so, stop the protocol
        factory from processing more requests until capacity is back.

        (The argument to this function is currently duplicated by the
        C{self.dispatcher.statuses} attribute, which is what
        C{self.outstandingRequests} uses to compute it.)
        """
        current = sum(status.effective()
                      for status in self.dispatcher.statuses)
        self._outstandingRequests = current # preserve for or= field in log
        maximum = self.maxRequests
        overloaded = (current >= maximum)
        available = len(filter(lambda x: x.active(), self.dispatcher.statuses))
        self.overloaded = (overloaded or available == 0)
        for f in self.factories:
            if self.overloaded:
                f.loadAboveMaximum()
            else:
                f.loadNominal()


    @property # make read-only
    def outstandingRequests(self):
        return self._outstandingRequests