コード例 #1
0
class MultiprocessQueueTransport(asyncTransportBase, wakeupTransportBase):
    """A transport designed to use a multiprocess.Queue instance to send
       and receive messages with other multiprocess Process actors.
       There is one instance of this object in each Actor.  This
       object maintains a single input queue (used by its parent an
       any children it creates) and a table of all known sub-Actor
       addresses and their queues (being the most immediate child
       Actor queue that moves the message closer to the child target,
       or the Parent actor queue to which the message should be
       forwarded if no child is identified).
    """

    def __init__(self, initType, *args):
        super(MultiprocessQueueTransport, self).__init__()
        if isinstance(initType, ExternalInterfaceTransportInit):
            # External process that's going to talk "in".  There is no
            # parent, and the child is the systemAdmin.
            capabilities, logDefs, self._concontext = args
            self._parentQ         = None
            NewQ = self._concontext.Queue if self._concontext else Queue
            self._adminQ          = NewQ(MAX_ADMIN_QUEUESIZE)
            self._adminAddr       = self.getAdminAddr(capabilities)
            self._myQAddress      = ActorAddress(QueueActorAddress('~'))
            self._myInputQ        = NewQ(MAX_ACTOR_QUEUESIZE)
        elif isinstance(initType, MpQTEndpoint):
            _addrInst, myAddr, myQueue, parentQ, adminQ, adminAddr, ccon = initType.args
            self._concontext = ccon
            self._parentQ    = parentQ
            self._adminQ     = adminQ
            self._adminAddr  = adminAddr
            self._myQAddress = myAddr
            self._myInputQ   = myQueue
        else:
            thesplog('MultiprocessQueueTransport init of type %s unsupported!', str(initType),
                     level=logging.ERROR)

        # _queues is a map of direct child ActorAddresses to Queue instance.  Note
        # that there will be multiple keys mapping to the same Queue
        # instance because routing is only either to the Parent or to
        # an immediate Child.
        self._queues = AssocList()  # addr -> queue

        # _fwdvia represents routing for other than immediate parent
        # or child (there may be multiple target addresses mapping to
        # the same forward address.
        self._fwdvia = AssocList()  # targetAddress -> fwdViaAddress

        self._nextSubInstance = 0


    def protectedFileNumList(self):
        return foldl(lambda a, b: a+[b._reader.fileno(), b._writer.fileno()],
                     [self._myInputQ, self._parentQ, self._adminQ] +
                     list(self._queues.values()), [])

    def childResetFileNumList(self):
        return foldl(lambda a, b: a+[b._reader.fileno(), b._writer.fileno()],
                     [self._parentQ] +
                     list(self._queues.values()), [])


    @property
    def myAddress(self): return self._myQAddress


    @staticmethod
    def getAddressFromString(addrspec):
        # addrspec is assumed to be a valid address string
        return ActorAddress(QueueActorAddress(addrspec))

    @staticmethod
    def getAdminAddr(capabilities):
        return MultiprocessQueueTransport.getAddressFromString(
            capabilities.get('Admin Address', 'ThespianQ'))


    @staticmethod
    def probeAdmin(addr):
        """Called to see if there might be an admin running already at the
           specified addr.  This is called from the systemBase, so
           simple blocking operations are fine.  This only needs to
           check for a responder; higher level logic will verify that
           it's actually an ActorAdmin suitable for use.
        """
        # never reconnectable; Queue objects are only available from
        # the constructor and cannot be synthesized or passed.
        return False


    def _updateStatusResponse(self, resp):
        "Called to update a Thespian_SystemStatus or Thespian_ActorStatus with common information"
        asyncTransportBase._updateStatusResponse(self, resp)
        wakeupTransportBase._updateStatusResponse(self, resp)


    def _nextSubAddress(self):
        subAddrStr = self._myQAddress.addressDetails.subAddr(self._nextSubInstance)
        self._nextSubInstance = self._nextSubInstance + 1
        return ActorAddress(QueueActorAddress(subAddrStr))


    def prepEndpoint(self, assignedLocalAddr, capabilities):
        """In the parent, prepare to establish a new communications endpoint
           with a new Child Actor.  The result of this call will be
           passed to a created child process to use when initializing
           the Transport object for that class; the result of this
           call will also be kept by the parent to finalize the
           communications after creation of the Child by calling
           connectEndpoint() with this returned object.
        """
        NewQ = self._concontext.Queue if self._concontext else Queue
        if isinstance(assignedLocalAddr.addressDetails, ActorLocalAddress):
            return MpQTEndpoint(assignedLocalAddr.addressDetails.addressInstanceNum,
                                self._nextSubAddress(),
                                NewQ(MAX_ACTOR_QUEUESIZE),
                                self._myInputQ, self._adminQ, self._adminAddr,
                                self._concontext)
        return MpQTEndpoint(None,
                            assignedLocalAddr,
                            self._adminQ, self._myInputQ, self._adminQ,
                            self._adminAddr,
                            self._concontext)

    def connectEndpoint(self, endPoint):
        """Called by the Parent after creating the Child to fully connect the
           endpoint to the Child for ongoing communications."""
        (_addrInst, childAddr, childQueue, _myQ,
         _adminQ, _adminAddr, _concurrency_context) = endPoint.args
        self._queues.add(childAddr, childQueue)


    def deadAddress(self, addressManager, childAddr):
        # Can no longer send to this Queue object.  Delete the
        # entry; this will cause forwarding of messages, although
        # the addressManager is also aware of the dead address and
        # will cause DeadEnvelope forwarding.  Deleting here
        # prevents hanging on queue full to dead children.
        self._queues.rmv(childAddr)
        deadfwd, okfwd = ([],[]) if False else \
                         partition(lambda i: i[0] == childAddr or i[1] == childAddr,
                                   self._fwdvia.items())
        if deadfwd:
            self._fwdvia = AssocList()
            for A,AQ in okfwd:
                self._fwdvia.add(A,AQ)
        super(MultiprocessQueueTransport, self).deadAddress(addressManager, childAddr)


    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


    def abort_run(self, drain=False):
        # Queue transmits immediately, so no draining needed
        self._aborting_run = True


    def serializer(self, intent):
        wrappedMsg = self._myQAddress, intent.targetAddr, intent.message
        # For multiprocess Queues, the serialization (pickling) of the
        # outbound message happens in a separate process.  This is
        # unfortunate because if the message is not pickle-able, the
        # exception is thrown (and not handled) in the other process,
        # and this process has no indication of the issue.  The
        # unfortunate solution is that pickling must be tried in the
        # current process first to detect these errors (unfortunate
        # because that means each message gets pickled twice,
        # impacting performance).
        discard = pickle.dumps(wrappedMsg)
        return wrappedMsg


    def interrupt_wait(self):
        self._myInputQ.put_nowait('BuMP')


    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)
コード例 #2
0
class MultiprocessQueueTCore_Common(object):
    def __init__(self, myQueue, parentQ, adminQ, adminAddr):
        self._myInputQ   = myQueue
        self._parentQ    = parentQ
        self._adminQ     = adminQ
        self._adminAddr  = adminAddr

        # _queues is a map of direct child ActorAddresses to Queue instance.  Note
        # that there will be multiple keys mapping to the same Queue
        # instance because routing is only either to the Parent or to
        # an immediate Child.
        self._queues = AssocList()  # addr -> queue

        # _fwdvia represents routing for other than immediate parent
        # or child (there may be multiple target addresses mapping to
        # the same forward address.
        self._fwdvia = AssocList()  # targetAddress -> fwdViaAddress

        self._deadaddrs = []

        # Signals can set these to true; they should be checked and
        # reset by the main processing loop.  There is a small window
        # where they could be missed because signals are not queued,
        # but this should handle the majority of situations.  Note
        # that the Queue object is NOT signal-safe, so don't try to
        # queue signals that way.

        self._checkChildren = False
        self._shutdownSignalled = False


    def mainLocalInputQueueEndpoint(self): return self._myInputQ
    def adminQueueEndpoint(self): return self._adminQ
    @property
    def adminAddr(self): return self._adminAddr

    def protectedFileNumList(self):
        return foldl(lambda a, b: a+[b._reader.fileno(), b._writer.fileno()],
                     [self._myInputQ, self._parentQ, self._adminQ] +
                     list(self._queues.values()), [])


    def childResetFileNumList(self):
        return foldl(lambda a, b: a+[b._reader.fileno(), b._writer.fileno()],
                     [self._parentQ] +
                     list(self._queues.values()), [])


    def add_endpoint(self, child_addr, child_queue):
        self._queues.add(child_addr, child_queue)


    def set_address_to_dead(self, child_addr):
        self._queues.rmv(child_addr)
        self._fwdvia.rmv(child_addr)
        self._fwdvia.rmv_value(child_addr)
        self._deadaddrs.append(child_addr)


    def abort_core_run(self):
        self._aborting_run = True


    def core_common_transmit(self, transmit_intent, from_addr):
        try:
            if self.isMyAddress(transmit_intent.targetAddr):
                if transmit_intent.message:
                    self._myInputQ.put( (from_addr, transmit_intent.serMsg),
                                        True,
                                        timePeriodSeconds(transmit_intent.delay()))
            else:
                tgtQ = self._queues.find(transmit_intent.targetAddr)
                if tgtQ:
                    tgtQ.put((from_addr, transmit_intent.serMsg), True,
                             timePeriodSeconds(transmit_intent.delay()))
                else:
                    # None means sent by parent, so don't send BACK to parent if unknown
                    topOrFromBelow = from_addr if self._parentQ else None
                    (self._parentQ or self._adminQ).put(
                        (topOrFromBelow, transmit_intent.serMsg),
                        True,
                        timePeriodSeconds(transmit_intent.delay()))

            transmit_intent.tx_done(SendStatus.Sent)
            return
        except Q.Full:
            pass
        transmit_intent.tx_done(SendStatus.DeadTarget if not isinstance(
            transmit_intent._message,
            (ChildActorExited, ActorExitRequest)) else SendStatus.Failed)


    def core_common_receive(self, incoming_handler, for_local_addr, run_time_f):
        """Core scheduling method; called by the current Actor process when
           idle to await new messages (or to do background
           processing).
        """
        if incoming_handler == TransmitOnly or \
           isinstance(incoming_handler, TransmitOnly):
            # transmits are not queued/multistage in this transport, no waiting
            return 0

        self._aborting_run = False

        while not run_time_f().expired() and not self._aborting_run:
            try:
                # Unfortunately, the Queue object is not signal-safe,
                # so a frequent wakeup is needed to check
                # _checkChildren and _shutdownSignalled.
                rcvd = self._myInputQ.get(True,
                                          min(run_time_f().remainingSeconds() or
                                              QUEUE_CHECK_PERIOD,
                                              QUEUE_CHECK_PERIOD))
            except Q.Empty:
                if not self._checkChildren and not self._shutdownSignalled:
                    # Probably a timeout, but let the while loop decide for sure
                    continue
                rcvd = 'BuMP'
            if rcvd == 'BuMP':
                relayAddr = sendAddr = destAddr = for_local_addr
                if self._checkChildren:
                    self._checkChildren = False
                    msg = ChildMayHaveDied()
                elif self._shutdownSignalled:
                    self._shutdownSignalled = False
                    msg = ActorExitRequest()
                else:
                    return Thespian__UpdateWork()
            else:
                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 self.isMyAddress(destAddr):
                if incoming_handler is None:
                    return ReceiveEnvelope(sendAddr, msg)
                r = incoming_handler(ReceiveEnvelope(sendAddr, msg))
                if not r:
                    return r  # 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.
                send_dead = False
                tgtQ = self._queues.find(destAddr)
                if tgtQ:
                    sendArgs = putQValue(for_local_addr), True
                if not tgtQ:
                    tgtA = self._fwdvia.find(destAddr)
                    if tgtA:
                        tgtQ = self._queues.find(tgtA)
                        sendArgs = putQValue(None),
                    else:
                        for each in self._deadaddrs:
                            if destAddr == each:
                                send_dead = True
                if tgtQ:
                    try:
                        tgtQ.put(*sendArgs,
                                 timeout=timePeriodSeconds(MAX_QUEUE_TRANSMIT_PERIOD))
                        continue
                    except Q.Full:
                        thesplog('Unable to send msg %s to dest %s; dead lettering',
                                 msg, destAddr)
                        send_dead = True
                if send_dead:
                    try:
                        (self._parentQ or self._adminQ).put(
                            deadQValue(for_local_addr 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)
                    continue

                # 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(for_local_addr 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


    def interrupt_run(self, signal_shutdown=False, check_children=False):
        self._shutdownSignalled |= signal_shutdown
        self._checkChildren |= check_children
        # Do not put anything on the Queue if running in the context
        # of a signal handler, because Queues are not signal-context
        # safe.  Instead, those will just have to depend on the short
        # maximum Queue get wait time.
        if not signal_shutdown and not check_children:
            self._myInputQ.put_nowait('BuMP')
コード例 #3
0
class MultiprocessQueueTCore_Common(object):
    def __init__(self, myQueue, parentQ, adminQ, adminAddr):
        self._myInputQ = myQueue
        self._parentQ = parentQ
        self._adminQ = adminQ
        self._adminAddr = adminAddr

        # _queues is a map of direct child ActorAddresses to Queue instance.  Note
        # that there will be multiple keys mapping to the same Queue
        # instance because routing is only either to the Parent or to
        # an immediate Child.
        self._queues = AssocList()  # addr -> queue

        # _fwdvia represents routing for other than immediate parent
        # or child (there may be multiple target addresses mapping to
        # the same forward address.
        self._fwdvia = AssocList()  # targetAddress -> fwdViaAddress

        self._deadaddrs = []

        # Signals can set these to true; they should be checked and
        # reset by the main processing loop.  There is a small window
        # where they could be missed because signals are not queued,
        # but this should handle the majority of situations.  Note
        # that the Queue object is NOT signal-safe, so don't try to
        # queue signals that way.

        self._checkChildren = False
        self._shutdownSignalled = False

    def mainLocalInputQueueEndpoint(self):
        return self._myInputQ

    def adminQueueEndpoint(self):
        return self._adminQ

    @property
    def adminAddr(self):
        return self._adminAddr

    def protectedFileNumList(self):
        return foldl(lambda a, b: a + [b._reader.fileno(
        ), b._writer.fileno()], [self._myInputQ, self._parentQ, self._adminQ] +
                     list(self._queues.values()), [])

    def childResetFileNumList(self):
        return foldl(lambda a, b: a + [b._reader.fileno(),
                                       b._writer.fileno()],
                     [self._parentQ] + list(self._queues.values()), [])

    def add_endpoint(self, child_addr, child_queue):
        self._queues.add(child_addr, child_queue)

    def set_address_to_dead(self, child_addr):
        self._queues.rmv(child_addr)
        self._fwdvia.rmv(child_addr)
        self._fwdvia.rmv_value(child_addr)
        self._deadaddrs.append(child_addr)

    def abort_core_run(self):
        self._aborting_run = Thespian__Run_Terminated()

    def core_common_transmit(self, transmit_intent, from_addr):
        try:
            if self.isMyAddress(transmit_intent.targetAddr):
                if transmit_intent.message:
                    self._myInputQ.put(
                        (from_addr, transmit_intent.serMsg), True,
                        timePeriodSeconds(transmit_intent.delay()))
            else:
                tgtQ = self._queues.find(transmit_intent.targetAddr)
                if tgtQ:
                    tgtQ.put((from_addr, transmit_intent.serMsg), True,
                             timePeriodSeconds(transmit_intent.delay()))
                else:
                    # None means sent by parent, so don't send BACK to parent if unknown
                    topOrFromBelow = from_addr if self._parentQ else None
                    (self._parentQ or self._adminQ).put(
                        (topOrFromBelow, transmit_intent.serMsg), True,
                        timePeriodSeconds(transmit_intent.delay()))

            transmit_intent.tx_done(SendStatus.Sent)
            return
        except Q.Full:
            pass
        transmit_intent.tx_done(
            SendStatus.DeadTarget if not isinstance(transmit_intent._message, (
                ChildActorExited, ActorExitRequest)) else SendStatus.Failed)

    def core_common_receive(self, incoming_handler, local_routing_addr,
                            run_time_f):
        """Core scheduling method; called by the current Actor process when
           idle to await new messages (or to do background
           processing).
        """
        if incoming_handler == TransmitOnly or \
           isinstance(incoming_handler, TransmitOnly):
            # transmits are not queued/multistage in this transport, no waiting
            return local_routing_addr, 0

        self._aborting_run = None

        while self._aborting_run is None:
            ct = currentTime()
            if run_time_f().view(ct).expired():
                break
            try:
                # Unfortunately, the Queue object is not signal-safe,
                # so a frequent wakeup is needed to check
                # _checkChildren and _shutdownSignalled.
                rcvd = self._myInputQ.get(
                    True,
                    min(
                        run_time_f().view(ct).remainingSeconds()
                        or QUEUE_CHECK_PERIOD, QUEUE_CHECK_PERIOD))
            except Q.Empty:
                if not self._checkChildren and not self._shutdownSignalled:
                    # Probably a timeout, but let the while loop decide for sure
                    continue
                rcvd = 'BuMP'
            if rcvd == 'BuMP':
                relayAddr = sendAddr = destAddr = local_routing_addr
                if self._checkChildren:
                    self._checkChildren = False
                    msg = ChildMayHaveDied()
                elif self._shutdownSignalled:
                    self._shutdownSignalled = False
                    msg = ActorExitRequest()
                else:
                    return local_routing_addr, Thespian__UpdateWork()
            else:
                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 self.isMyAddress(destAddr):
                if isinstance(incoming_handler,
                              ReturnTargetAddressWithEnvelope):
                    return destAddr, ReceiveEnvelope(sendAddr, msg)
                if incoming_handler is None:
                    return destAddr, ReceiveEnvelope(sendAddr, msg)
                r = Thespian__Run_HandlerResult(
                    incoming_handler(ReceiveEnvelope(sendAddr, msg)))
                if not r:
                    # handler returned False-ish, indicating run() should exit
                    return destAddr, r
            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.
                send_dead = False
                tgtQ = self._queues.find(destAddr)
                if tgtQ:
                    sendArgs = putQValue(local_routing_addr), True
                if not tgtQ:
                    tgtA = self._fwdvia.find(destAddr)
                    if tgtA:
                        tgtQ = self._queues.find(tgtA)
                        sendArgs = putQValue(None),
                    else:
                        for each in self._deadaddrs:
                            if destAddr == each:
                                send_dead = True
                if tgtQ:
                    try:
                        tgtQ.put(*sendArgs,
                                 timeout=timePeriodSeconds(
                                     MAX_QUEUE_TRANSMIT_PERIOD))
                        continue
                    except Q.Full:
                        thesplog(
                            'Unable to send msg %s to dest %s; dead lettering',
                            msg, destAddr)
                        send_dead = True
                if send_dead:
                    try:
                        (self._parentQ or self._adminQ).put(
                            deadQValue(
                                local_routing_addr 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)
                    continue
                # 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(
                                local_routing_addr 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

        if self._aborting_run is not None:
            return local_routing_addr, self._aborting_run

        return local_routing_addr, Thespian__Run_Expired()

    def interrupt_run(self, signal_shutdown=False, check_children=False):
        self._shutdownSignalled |= signal_shutdown
        self._checkChildren |= check_children
        # Do not put anything on the Queue if running in the context
        # of a signal handler, because Queues are not signal-context
        # safe.  Instead, those will just have to depend on the short
        # maximum Queue get wait time.
        if not signal_shutdown and not check_children:
            try:
                self._myInputQ.put_nowait('BuMP')
            except Q.Full:
                # if the queue is full, it should be reading something
                # off soon which will accomplish the same interrupt
                # effect, so nothing else needs to be done here.
                pass