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