def _scheduleTransmitActual(self, transmitIntent): try: if transmitIntent.targetAddr == self.myAddress: if transmitIntent.message: self._myInputQ.put( (self._myQAddress, transmitIntent.serMsg), True, timePeriodSeconds(transmitIntent.delay())) else: tgtQ = self._queues.find(transmitIntent.targetAddr) if tgtQ: tgtQ.put((self._myQAddress, transmitIntent.serMsg), True, timePeriodSeconds(transmitIntent.delay())) else: # None means sent by parent, so don't send BACK to parent if unknown topOrFromBelow = self._myQAddress if self._parentQ else None (self._parentQ or self._adminQ).put( (topOrFromBelow, transmitIntent.serMsg), True, timePeriodSeconds(transmitIntent.delay())) transmitIntent.tx_done(SendStatus.Sent) return except Q.Full: pass transmitIntent.tx_done( SendStatus.DeadTarget if not isinstance(transmitIntent._message, ( ChildActorExited, ActorExitRequest)) else SendStatus.Failed) thesplog('Q.Full %s to %s result %s', transmitIntent._message, transmitIntent.targetAddr, transmitIntent.result)
def _next_XMIT_1(self, intent): intent.socket = socket.socket( *intent.targetAddr.addressDetails.socketArgs) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) intent.socket.setblocking(0) # Disable Nagle to transmit headers and acks asap; our sends are usually small intent.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) intent.socket.settimeout(timePeriodSeconds(intent.delay())) try: intent.socket.connect( *intent.targetAddr.addressDetails.connectArgs) except socket.error as err: # EINPROGRESS means non-blocking socket connect is in progress... if err.errno != errno.EINPROGRESS: thesplog('Socket connect failure %s to %s (returning %s)', err, intent.targetAddr, intent.completionCallback, level=logging.WARNING) return self._finishIntent(intent, SendStatus.DeadTarget \ if err.errno == errno.ECONNREFUSED \ else SendStatus.Failed) intent.backoffPause(True) except Exception as ex: thesplog('Unexpected TCP socket connect exception: %s', ex, level=logging.ERROR) return self._finishIntent(intent, SendStatus.BadPacket) intent.stage = self._XMITStepSendData # When connect completes intent.amtSent = 0 return True
def _next_XMIT_1(self, intent): intent.socket = socket.socket(*intent.targetAddr.addressDetails.socketArgs) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) intent.socket.setblocking(0) # Disable Nagle to transmit headers and acks asap; our sends are usually small intent.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) intent.socket.settimeout(timePeriodSeconds(intent.delay())) try: intent.socket.connect(*intent.targetAddr.addressDetails.connectArgs) except socket.error as err: # EINPROGRESS means non-blocking socket connect is in progress... if err.errno != errno.EINPROGRESS: thesplog('Socket connect failure %s to %s (returning %s)', err, intent.targetAddr, intent.completionCallback, level=logging.WARNING) return self._finishIntent(intent, SendStatus.DeadTarget \ if err.errno == errno.ECONNREFUSED \ else SendStatus.Failed) intent.backoffPause(True) except Exception as ex: thesplog('Unexpected TCP socket connect exception: %s', ex, level=logging.ERROR) return self._finishIntent(intent, SendStatus.BadPacket) intent.stage = self._XMITStepSendData # When connect completes intent.amtSent = 0 return True
def testOverMax__expected_duration_is_21_seconds(self): # The rate limiter allows bursts.. the bigger cnt is the # closer to the actual limit the result will be, but the # longer the test will take. cnt = 300 tt = send_at_rate(10, 100, cnt) print('send_at_rate(10, 100, %s) --> %s'%(cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(15, actRate) # add a little buffer for overage... just a little
def testOverMax__expected_duration_is_21_seconds(self): # The rate limiter allows bursts.. the bigger cnt is the # closer to the actual limit the result will be, but the # longer the test will take. cnt = 300 tt = send_at_rate(10, 100, cnt) print('send_at_rate(10, 100, %s) --> %s' % (cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) assert 15 > actRate # add a little buffer for overage... just a little
def testTransmitIntentRetryTiming(self): maxPeriod = timedelta(milliseconds=90) period = timedelta(milliseconds=30) ti = TransmitIntent('addr', 'msg', maxPeriod=maxPeriod, retryPeriod=period) self.assertFalse(ti.timeToRetry()) sleep(timePeriodSeconds(period)) self.assertFalse(ti.timeToRetry()) self.assertTrue(ti.retry()) self.assertFalse(ti.timeToRetry()) sleep(timePeriodSeconds(period)) self.assertTrue(ti.timeToRetry()) self.assertTrue(ti.retry()) self.assertFalse(ti.timeToRetry()) sleep(timePeriodSeconds(period)) self.assertFalse(ti.timeToRetry()) # Each retry increases sleep(timePeriodSeconds(period)) self.assertTrue(ti.timeToRetry()) self.assertFalse(ti.retry()) # Exceeds maximum time
def testTransmitIntentRetryTimingExceedsLimit(self): maxPeriod = timedelta(seconds=90) period = timedelta(microseconds=1) ti = TransmitIntent('addr', 'msg', maxPeriod=maxPeriod, retryPeriod=period) self.assertFalse(ti.timeToRetry()) for N in range(MAX_TRANSMIT_RETRIES+1): self.assertTrue(ti.retry()) for x in range(90): if ti.timeToRetry(): break sleep(timePeriodSeconds(period)) self.assertTrue(ti.timeToRetry()) self.assertFalse(ti.retry())
def testTransmitIntentRetryTimingExceedsLimit(self): maxPeriod = timedelta(seconds=90) period = timedelta(microseconds=1) ti = TransmitIntent('addr', 'msg', maxPeriod=maxPeriod, retryPeriod=period) self.assertFalse(ti.timeToRetry()) for N in range(MAX_TRANSMIT_RETRIES + 1): self.assertTrue(ti.retry()) for x in range(90): if ti.timeToRetry(): break sleep(timePeriodSeconds(period)) self.assertTrue(ti.timeToRetry()) self.assertFalse(ti.retry())
def testTransmitIntentRetryTimingExceedsLimit(self): maxPeriod = timedelta(seconds=90) period = timedelta(microseconds=1) ti = TransmitIntent('addr', 'msg', maxPeriod=maxPeriod, retryPeriod=period) self.assertFalse(ti.timeToRetry()) for N in range(MAX_TRANSMIT_RETRIES+1): # Indicate "failure" and the need to retry self.assertTrue(ti.retry()) # Wait for the indication that it is time to retry time_to_retry = False for x in range(90): # Only call timeToRetry once, because it auto-resets time_to_retry = ti.timeToRetry() if time_to_retry: break sleep(timePeriodSeconds(period) * 1.5) self.assertTrue(time_to_retry) self.assertFalse(ti.retry())
def eventRatePause(self): """This is the main method that should be called each time an event is to occur. It will block internally until it is time for the next event to occur. """ if not hasattr(self, '_curRate'): self._runningCount += 1 # runningCount is the total # since the last time it was # zeroed, so it's not just wthin the last second, but it does # provide a threshold above which the actual rate should be # observed and possibly throttled. This will begin to take an # interest in the actual rate when the runningCount reaches an # arbitrary 70% of the maximum rate. if self._runningCount < (self._maxRate * 0.70): return self._curRate = 0 self._timeMark = datetime.now() self._goodMarks = 0 return self._curRate += 1 newT = datetime.now() deltaT = newT - self._timeMark if deltaT < timedelta(seconds=1): return rate = self._curRate / timePeriodSeconds(deltaT) if rate > self._maxRate: # Slow down a little delay(0.1) self._goodMarks = 0 return self._goodMarks += 1 if self._goodMarks > self._maxRate: delattr(self, '_curRate') self._runningCount = 0 return
def _runSends(self, timeout=None): numsends = 0 endtime = ((datetime.now() + toTimeDeltaOrNone(timeout)) if timeout else None) while not endtime or datetime.now() < endtime: while self._pendingSends: numsends += 1 if self.procLimit and numsends > self.procLimit: raise RuntimeError('Too many sends') self._realizeWakeups() self._runSingleSend(self._pendingSends.pop(0)) if not endtime: return now = datetime.now() valid_wakeups = [(W-now) for W in self._wakeUps if W <= endtime] if not valid_wakeups: return import time time.sleep(max(0, timePeriodSeconds(min(valid_wakeups)))) self._realizeWakeups()
def _runSends(self, timeout=None): numsends = 0 endtime = ((datetime.now() + toTimeDeltaOrNone(timeout)) if timeout else None) while not endtime or datetime.now() < endtime: while self._pendingSends: numsends += 1 if self.procLimit and numsends > self.procLimit: raise RuntimeError('Too many sends') self._realizeWakeups() self._runSingleSend(self._pendingSends.pop(0)) if not endtime: return now = datetime.now() valid_wakeups = [(W - now) for W in self._wakeUps if W <= endtime] if not valid_wakeups: return import time time.sleep(max(0, timePeriodSeconds(min(valid_wakeups)))) self._realizeWakeups()
def testSlow__expected_duration_is_20_seconds(self): cnt = 10 tt = send_at_rate(10, 0.5, cnt) print('send_at_rate(10, 0.5, %s) --> %s' % (cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) assert 10 > actRate
systems (which should not be an issue under normal operations). """ import pytest from pytest import raises from thespian.test import * import time from thespian.actors import * from datetime import timedelta from thespian.system.utilis import timePeriodSeconds MAX_ASK_WAIT_PERIOD = timedelta(seconds=7) UPDATE_WAIT_PERIOD = timedelta(milliseconds=300) EXIT_WAIT_PERIOD = timedelta(milliseconds=500) update_wait = lambda: time.sleep(timePeriodSeconds(UPDATE_WAIT_PERIOD)) exit_wait = lambda: time.sleep(timePeriodSeconds(EXIT_WAIT_PERIOD)) colors = ['Red', 'Blue', 'Green', 'Yellow'] class SetCap(object): def __init__(self, capName, capValue): self.capName = capName self.capValue = capValue class ColorActorBase(Actor): """This actor has a particular color (identified by self.color), and requires that color to be a capability of the ActorSystem it runs in.
def testNearMax__expected_duration_is_11_seconds(self): cnt = 100 tt = send_at_rate(10, 9, cnt) print('send_at_rate(10, 9, %s) --> %s' % (cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) assert 10 > actRate
assert 10 > actRate def testOverMax__expected_duration_is_21_seconds(self): # The rate limiter allows bursts.. the bigger cnt is the # closer to the actual limit the result will be, but the # longer the test will take. cnt = 300 tt = send_at_rate(10, 100, cnt) print('send_at_rate(10, 100, %s) --> %s' % (cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) assert 15 > actRate # add a little buffer for overage... just a little def testSlow__expected_duration_is_20_seconds(self): cnt = 10 tt = send_at_rate(10, 0.5, cnt) print('send_at_rate(10, 0.5, %s) --> %s' % (cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) assert 10 > actRate if __name__ == "__main__": for ar in [5, 9, 0.2, 100]: #cnt = 10 * ar # this way all return 10 s unless rate limited cnt = 1000 if ar > 10 else ( 50 if ar >= 1 else 5 ) # this way see time taken for const # of inputs tt = send_at_rate(10, ar, cnt) actRate = cnt / timePeriodSeconds(tt) print('send_at_rate(10, %s, %s) --> %s, rate=%s' % (str(ar), str(cnt), str(tt), actRate))
def _runWithExpiry(self, incomingHandler): xmitOnly = incomingHandler == TransmitOnly or \ isinstance(incomingHandler, TransmitOnly) if hasattr(self, '_aborting_run'): delattr(self, '_aborting_run') while not self.run_time.expired() and \ (not hasattr(self, '_aborting_run') or (self._aborting_run and self._transmitIntents)): if xmitOnly: if not self._transmitIntents: return 0 else: while self._incomingEnvelopes: rEnv = self._incomingEnvelopes.pop(0) if incomingHandler is None: return rEnv if not incomingHandler(rEnv): return None wsend, wrecv = fmap(TCPTransport._socketFile, partition(TCPTransport._waitForSendable, filter(lambda T: not T.backoffPause(), self._transmitIntents))) wrecv = list(filter(None, wrecv)) wsend = list(filter(None, wsend)) wrecv.extend(list(filter(None, [TCPTransport._socketFile(I) for I in self._incomingSockets if not I.backoffPause()]))) delays = list([R for R in [self.run_time.remaining()] + [T.delay() for T in self._transmitIntents] + [T.delay() for T in self._incomingSockets] if R is not None]) delay = timePeriodSeconds(min(delays)) if delays else None if not hasattr(self, '_aborting_run') and not xmitOnly: wrecv.extend([self.socket.fileno()]) # rrecv, rsend, _ign2 = select.select(wrecv, wsend, [], delay) try: rrecv, rsend, _ign2 = select.select(wrecv, wsend, set(wsend+wrecv), delay) except ValueError as ex: thesplog('ValueError on select(#%d: %s, #%d: %s, #%d: %s, %s)', len(wrecv), wrecv, len(wsend), wsend, len(set(wsend + wrecv)), set(wsend + wrecv), delay, level=logging.ERROR) raise except select.error as ex: if ex.args[0] in (errno.EINVAL, # probably a change in descriptors errno.EINTR, ): thesplog('select retry on %s', ex, level=logging.ERROR) continue raise if _ign2: thesplog('WHOA... something else to do for sockets: %s', _ign2, level=logging.WARNING) origPendingSends = len(self._transmitIntents) # Handle newly sendable data for eachs in rsend: self._transmitIntents = [I for I in self._transmitIntents if self._nextTransmitStepCheck(I, eachs)] # Handle newly receivable data for each in rrecv: if each == self.socket.fileno(): self._acceptNewIncoming() continue self._incomingSockets = [S for S in self._incomingSockets if self._handlePossibleIncoming(S, each)] self._transmitIntents = [I for I in self._transmitIntents if self._nextTransmitStepCheck(I, each)] # Handle timeouts self._transmitIntents = [I for I in self._transmitIntents if self._nextTransmitStepCheck(I, -1)] self._incomingSockets = [S for S in self._incomingSockets if self._handlePossibleIncoming(S, -1)] # Check if it's time to quit if [] == rrecv and [] == rsend: if [] == _ign2 and self.run_time.expired(): # Timeout, give up return None continue if xmitOnly: remXmits = len(self._transmitIntents) if origPendingSends > remXmits or remXmits == 0: return remXmits # Handle queued internal "received" data if not xmitOnly: while self._incomingEnvelopes: rEnv = self._incomingEnvelopes.pop(0) if incomingHandler is None: return rEnv if not incomingHandler(rEnv): return None return None
# 5. This parent is the external port for asys1. # 6. If asys1 is shutdown first, then asys2 must time out # on the transmit attempt (usually 5 minutes) before # it can exit. # 7. If the test is re-run within this 5 minute period, it will fail # because the old asys2 is still existing but in shutdown state # (and will therefore rightfully refuse new actions). # By shutting down asys2 first, the parent notification can be # performed and subsequent runs don't encounter the lingering # asys2. request.addfinalizer(lambda asys=asys2: asys2.shutdown()) return (asys, asys2) def unstable_test(asys, *unstable_bases): if asys.base_name in unstable_bases and \ not pytest.config.getoption('unstable', default=False): pytest.skip("Test unstable for %s system base" % asys.base_name) def actor_system_unsupported(asys, *unsupported_bases): if asys.base_name in unsupported_bases: pytest.skip("Functionality not supported for %s system base" % asys.base_name) from thespian.system.utilis import timePeriodSeconds import time inTestDelay = lambda period: time.sleep(timePeriodSeconds(period))
def testModerate__expected_duration_is_20_seconds(self): cnt = 100 tt = send_at_rate(10, 5, cnt) print('send_at_rate(10, 5, %s) --> %s' % (cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(10, actRate)
def testSlow__expected_duration_is_20_seconds(self): cnt = 10 tt = send_at_rate(10, 0.5, cnt) print('send_at_rate(10, 0.5, %s) --> %s'%(cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(10, actRate)
def _runWithExpiry(self, incomingHandler): xmitOnly = incomingHandler == TransmitOnly or \ isinstance(incomingHandler, TransmitOnly) if hasattr(self, '_aborting_run'): delattr(self, '_aborting_run') while not self.run_time.expired() and \ (not hasattr(self, '_aborting_run') or (self._aborting_run and self._transmitIntents)): if xmitOnly: if not self._transmitIntents: return 0 else: while self._incomingEnvelopes: rEnv = self._incomingEnvelopes.pop(0) if incomingHandler is None: return rEnv if not incomingHandler(rEnv): return None wsend, wrecv = fmap( TCPTransport._socketFile, partition( TCPTransport._waitForSendable, filter(lambda T: not T.backoffPause(), self._transmitIntents))) wrecv = list(filter(None, wrecv)) wsend = list(filter(None, wsend)) wrecv.extend( list( filter(None, [ TCPTransport._socketFile(I) for I in self._incomingSockets if not I.backoffPause() ]))) delays = list([ R for R in [self.run_time.remaining()] + [T.delay() for T in self._transmitIntents] + [T.delay() for T in self._incomingSockets] if R is not None ]) delay = timePeriodSeconds(min(delays)) if delays else None if not hasattr(self, '_aborting_run') and not xmitOnly: wrecv.extend([self.socket.fileno()]) # rrecv, rsend, _ign2 = select.select(wrecv, wsend, [], delay) try: rrecv, rsend, _ign2 = select.select(wrecv, wsend, set(wsend + wrecv), delay) except ValueError as ex: thesplog('ValueError on select(#%d: %s, #%d: %s, #%d: %s, %s)', len(wrecv), wrecv, len(wsend), wsend, len(set(wsend + wrecv)), set(wsend + wrecv), delay, level=logging.ERROR) raise except select.error as ex: if ex.args[0] in ( errno.EINVAL, # probably a change in descriptors errno.EINTR, ): thesplog('select retry on %s', ex, level=logging.ERROR) continue raise if _ign2: thesplog('WHOA... something else to do for sockets: %s', _ign2, level=logging.WARNING) origPendingSends = len(self._transmitIntents) # Handle newly sendable data for eachs in rsend: self._transmitIntents = [ I for I in self._transmitIntents if self._nextTransmitStepCheck(I, eachs) ] # Handle newly receivable data for each in rrecv: if each == self.socket.fileno(): self._acceptNewIncoming() continue self._incomingSockets = [ S for S in self._incomingSockets if self._handlePossibleIncoming(S, each) ] self._transmitIntents = [ I for I in self._transmitIntents if self._nextTransmitStepCheck(I, each) ] # Handle timeouts self._transmitIntents = [ I for I in self._transmitIntents if self._nextTransmitStepCheck(I, -1) ] self._incomingSockets = [ S for S in self._incomingSockets if self._handlePossibleIncoming(S, -1) ] # Check if it's time to quit if [] == rrecv and [] == rsend: if [] == _ign2 and self.run_time.expired(): # Timeout, give up return None continue if xmitOnly: remXmits = len(self._transmitIntents) if origPendingSends > remXmits or remXmits == 0: return remXmits # Handle queued internal "received" data if not xmitOnly: while self._incomingEnvelopes: rEnv = self._incomingEnvelopes.pop(0) if incomingHandler is None: return rEnv if not incomingHandler(rEnv): return None return None
def report(what, sysBase, elapsed, nMessages, nActors): print('%5d Actors, %30s %22s -- %s -- %5.2f/sec overall -- %7.2f msg/sec'%( nActors, what, sysBase, elapsed, nMessages / timePeriodSeconds(elapsed), nMessages * nActors / timePeriodSeconds(elapsed)))
print('send_at_rate(10, 9, %s) --> %s'%(cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(10, actRate) def testOverMax__expected_duration_is_21_seconds(self): # The rate limiter allows bursts.. the bigger cnt is the # closer to the actual limit the result will be, but the # longer the test will take. cnt = 300 tt = send_at_rate(10, 100, cnt) print('send_at_rate(10, 100, %s) --> %s'%(cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(15, actRate) # add a little buffer for overage... just a little def testSlow__expected_duration_is_20_seconds(self): cnt = 10 tt = send_at_rate(10, 0.5, cnt) print('send_at_rate(10, 0.5, %s) --> %s'%(cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(10, actRate) if __name__ == "__main__": for ar in [5, 9, 0.2, 100]: #cnt = 10 * ar # this way all return 10 s unless rate limited cnt = 1000 if ar > 10 else (50 if ar >= 1 else 5) # this way see time taken for const # of inputs tt = send_at_rate(10, ar, cnt) actRate = cnt / timePeriodSeconds(tt) print('send_at_rate(10, %s, %s) --> %s, rate=%s'%(str(ar), str(cnt), str(tt), actRate))
def testNearMax__expected_duration_is_11_seconds(self): cnt = 100 tt = send_at_rate(10, 9, cnt) print('send_at_rate(10, 9, %s) --> %s'%(cnt, str(tt))) actRate = cnt / timePeriodSeconds(tt) self.assertGreater(10, actRate)
def report(what, sysBase, elapsed, nMessages, nActors): print('%5d Actors, %30s %22s -- %s -- %5.2f/sec overall -- %7.2f msg/sec' % (nActors, what, sysBase, elapsed, nMessages / timePeriodSeconds(elapsed), nMessages * nActors / timePeriodSeconds(elapsed)))
def _runWithExpiry(self, incomingHandler): """Core scheduling method; called by the current Actor process when idle to await new messages (or to do background processing). """ if incomingHandler == TransmitOnly or \ isinstance(incomingHandler, TransmitOnly): # transmits are not queued/multistage in this transport, no waiting return 0 self._aborting_run = False while not self.run_time.expired() and not self._aborting_run: try: rcvd = self._myInputQ.get(True, self.run_time.remainingSeconds()) except Q.Empty: # Probably a timeout, but let the while loop decide for sure continue if rcvd == 'BuMP': return Thespian__UpdateWork() relayAddr, (sendAddr, destAddr, msg) = rcvd if not self._queues.find(sendAddr): # We don't directly know about this sender, so # remember what path this arrived on to know where to # direct future messages for this sender. if relayAddr and self._queues.find(relayAddr) and \ not self._fwdvia.find(sendAddr): # relayAddr might be None if it's our parent, which is OK because # the default message forwarding is to the parent. If it's not # none, it should be in self._queues though! self._fwdvia.add(sendAddr, relayAddr) if hasattr(self, '_addressMgr'): destAddr, msg = self._addressMgr.prepMessageSend(destAddr, msg) if destAddr is None: thesplog('Unexpected target inaccessibility for %s', msg, level=logging.WARNING) raise CannotPickleAddress(destAddr) if msg is SendStatus.DeadTarget: thesplog( 'Faking message "sent" because target is dead and recursion avoided.' ) continue if destAddr == self._myQAddress: if incomingHandler is None: return ReceiveEnvelope(sendAddr, msg) if not incomingHandler(ReceiveEnvelope(sendAddr, msg)): return # handler returned False, indicating run() should exit else: # Note: the following code has implicit knowledge of serialize() and xmit putQValue = lambda relayer: (relayer, (sendAddr, destAddr, msg)) deadQValue = lambda relayer: (relayer, (sendAddr, self._adminAddr, DeadEnvelope(destAddr, msg))) # Must forward this packet via a known forwarder or our parent. tgtQ = self._queues.find(destAddr) if tgtQ: sendArgs = putQValue(self.myAddress), True if not tgtQ: tgtA = self._fwdvia.find(destAddr) if tgtA: tgtQ = self._queues.find(tgtA) sendArgs = putQValue(None), if tgtQ: try: tgtQ.put(*sendArgs, timeout=timePeriodSeconds( MAX_QUEUE_TRANSMIT_PERIOD)) except Q.Full: thesplog( 'Unable to send msg %s to dest %s; dead lettering', msg, destAddr) try: (self._parentQ or self._adminQ).put( deadQValue( self.myAddress if self._parentQ else None), True, timePeriodSeconds(MAX_QUEUE_TRANSMIT_PERIOD)) except Q.Full: thesplog( 'Unable to send deadmsg %s to %s or admin; discarding', msg, destAddr) else: # Not sure how to route this message yet. It # could be a heretofore silent child of one of our # children, it could be our parent (whose address # we don't know), or it could be elsewhere in the # tree. # # Try sending it to the parent first. If the # parent can't determine the routing, it will be # sent back down (relayAddr will be None in that # case) and it must be sprayed out to all children # in case the target lives somewhere beneath us. # Note that _parentQ will be None for top-level # actors, which send up to the Admin instead. # # As a special case, the external system is the # parent of the admin, but the admin is the # penultimate parent of all others, so this code # must keep the admin and the parent from playing # ping-pong with the message. But... the message # might be directed to the external system, which # is the parent of the Admin, so we need to check # with it first. # parentQ == None but adminQ good --> external # parentQ and adminQ and myAddress == adminAddr --> Admin # parentQ and adminQ and myAddress != adminADdr --> other Actor if relayAddr: # Send message up to the parent to see if the # parent knows how to forward it try: (self._parentQ or self._adminQ).put( putQValue( self.myAddress if self._parentQ else None), True, timePeriodSeconds(MAX_QUEUE_TRANSMIT_PERIOD)) except Q.Full: thesplog( 'Unable to send dead msg %s to %s or admin; discarding', msg, destAddr) else: # Sent by parent or we are an external, so this # may be some grandchild not currently known. # Do the worst case and just send this message # to ALL immediate children, hoping it will # get there via some path. for A, AQ in self._queues.items(): if A not in [ self._adminAddr, str(self._adminAddr) ]: # None means sent by Parent, so don't # send BACK to parent if unknown try: AQ.put( putQValue(None), True, timePeriodSeconds( MAX_QUEUE_TRANSMIT_PERIOD)) except Q.Full: pass return None