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 __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.")
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 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)
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
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
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 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)
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
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