Exemplo n.º 1
0
    def scheduleTransmit(self, addressManager, transmitIntent):
        """Requests that a transmit be performed.  The message and target
           address must be fully valid at this point; any local
           addresses should throw a CannotPickleAddress exception and
           the caller is responsible for retrying later when those
           addresses are available.

           If addressManager is None then the intent address is
           assumed to be valid but it cannot be updated if it is a
           local address or a dead address.  A value of None is
           normally only used at Admin or Actor startup time when
           confirming the established connection back to the parent,
           at which time the target address should always be valid.

           Any transmit attempts from a thread other than the main
           thread are queued; calls to the underlying transmit layer
           are done only from the context of the main thread.
        """

        if addressManager:
            # Verify the target address is useable
            targetAddr, txmsg = addressManager.prepMessageSend(
                transmitIntent.targetAddr, transmitIntent.message)
            try:
                isDead = txmsg == SendStatus.DeadTarget
            except Exception:
                # txmsg may have an __eq__ that caused an exception
                isDead = False
            if isDead:
                # Address Manager has indicated that these messages
                # should never be attempted because the target is
                # dead.  This is *only* for special messages like
                # DeadEnvelope and ChildActorExited which would
                # endlessly recurse or bounce back and forth.  This
                # code indicates here that the transmit was
                # "successful" to allow normal cleanup but to avoid
                # recursive error generation.
                thesplog('Faking dead target transmit result Sent for %s',
                         transmitIntent,
                         level=logging.WARNING)
                transmitIntent.tx_done(SendStatus.Sent)
                return

            if not targetAddr:
                raise CannotPickleAddress(transmitIntent.targetAddr)

            # In case the prep made some changes...
            transmitIntent.changeTargetAddr(targetAddr)
            transmitIntent.changeMessage(txmsg)

        # Verify that the message can be serialized.  This may throw
        # an exception for local-only ActorAddresses or for attempting
        # to send other invalid elements in the message.  The invalid
        # address will cause the caller to store this intent and retry
        # it at some future point (the code up to and including this
        # serialization should be idempotent).

        transmitIntent.serMsg = self.serializer(transmitIntent)
        self._schedulePreparedIntent(transmitIntent)
Exemplo n.º 2
0
    def scheduleTransmit(self, addressManager, transmitIntent):

        """Requests that a transmit be performed.  The message and target
           address must be fully valid at this point; any local
           addresses should throw a CannotPickleAddress exception and
           the caller is responsible for retrying later when those
           addresses are available.

           If addressManager is None then the intent address is
           assumed to be valid but it cannot be updated if it is a
           local address or a dead address.  A value of None is
           normally only used at Admin or Actor startup time when
           confirming the established connection back to the parent,
           at which time the target address should always be valid.
        """

        if addressManager:
            # Verify the target address is useable
            targetAddr, txmsg = addressManager.prepMessageSend(transmitIntent.targetAddr,
                                                               transmitIntent.message)
            if not targetAddr:
                raise CannotPickleAddress(transmitIntent.targetAddr)

            # In case the prep made some changes...
            transmitIntent.changeTargetAddr(targetAddr)
            transmitIntent.changeMessage(txmsg)

        # Verify that the message can be serialized.  This may throw
        # an exception, which will cause the caller to store this
        # intent and retry it at some future point (the code up to and
        # including this serialization should be idempotent).

        transmitIntent.serMsg = self.serializer(transmitIntent)

        # If there's nothing to send, that's implicit success
        if not transmitIntent.serMsg:
            transmitIntent.result = SendStatus.Sent
            transmitIntent.completionCallback()
            return

        # OK, this can be sent now, so go ahead and get it sent out
        if not self._canSendNow():
            self._aTB_queuedPendingTransmits.insert(0, transmitIntent)
            if len(self._aTB_queuedPendingTransmits) >= MAX_QUEUED_TRANSMITS:
                # Try to drain out local work before accepting more
                # because it looks like we're getting really behind.
                # This is dangerous though, because if other Actors
                # are having the same issue this can create a
                # deadlock.
                thesplog('Entering tx-only mode to drain excessive queue (%s > %s, drain-to %s)',
                         len(self._aTB_queuedPendingTransmits),
                         MAX_QUEUED_TRANSMITS,
                         QUEUE_TRANSMIT_UNBLOCK_THRESHOLD,
                         level = logging.WARNING)
                while len(self._aTB_queuedPendingTransmits) > QUEUE_TRANSMIT_UNBLOCK_THRESHOLD:
                    self.run(TransmitOnly, transmitIntent.delay())
                thesplog('Exited tx-only mode after draining excessive queue (%s)',
                         len(self._aTB_queuedPendingTransmits),
                         level = logging.WARNING)
            return

        self._submitTransmit(transmitIntent)
Exemplo n.º 3
0
    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()