Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sender attribute? #40

Open
drozzy opened this issue Jul 21, 2014 · 4 comments
Open

Sender attribute? #40

drozzy opened this issue Jul 21, 2014 · 4 comments

Comments

@drozzy
Copy link

drozzy commented Jul 21, 2014

Is there an equivalent of a sender attribute on the actor?
This is to allow me to send messages to the "sender" --- and is drastically different from the "ask" semantics.

Consider, for example, a child actor keeping track of "failures" and sending a message to parent saying "Failure Limit Reached".

In this case the semantics of the parent would be something like this:

  1. Spawn Child actor.
  2. Listen to "Failure Limit Reached" messages and restart or replace child actor with a new one.

This cannot be accomplished with an "ask" --- since once the child is spawned, the parent cares not for it and carries on with its duties.

P.S.: As a side note, documentation seems to favor the "ask" way of doing things (see "replying to messages" in pykka docs), which is not how the general actor system usually functions. The preferred way is to use "tell" and only resort to "ask" in select cases. Akka also prefers tell:
http://doc.akka.io/docs/akka/snapshot/scala/actors.html

@drozzy
Copy link
Author

drozzy commented Mar 4, 2019

This is me from the future - I suggest you guys take a look at Erlang instead of Akka for inspiration. This is what I ended up using instead.

@jodal
Copy link
Owner

jodal commented Mar 4, 2019

Thanks for the tip!

We now have the Envelope wrapper around messages, so adding extra metadata like a sender attribute is now straight forward. The open question is how to best expose it to on_receive(). I see two alternatives:

  • Before calling on_receive(), set self.sender = envelope.sender where self is the receiving actor, and then unset it once on_receive() completes. I feel this is a bit implicit way of passing objects to on_receive(), but is how it was solved it in Sender functionality #55.
  • Pass the sender as an argument to on_receive(). To avoid having to update all existing on_receive() implementations, this would need to use introspection of the on_receive() signature and only pass the argument if it is expected by the method.

I'm open to other suggestions and to hear what expectations you have for this from other actor systems.

@Andrei-Pozolotin
Copy link

  • Before calling on_receive(), set self.sender = envelope.sender

please publish a release with this feature, to verify its usability in practice

@akatashev
Copy link

akatashev commented May 20, 2020

In Akka it works the following way:

ActorCell class has a property:

var currentMessage: Envelope = _

Envelope case class has a message and a sender:

final case class Envelope private (val message: Any, val sender: ActorRef)`

sender method looks like this:

  final def sender(): ActorRef = currentMessage match {
    case null                      => system.deadLetters
    case msg if msg.sender ne null => msg.sender
    case _                         => system.deadLetters
  }

Following this pattern, in Pykka it could be something like:
In _envelope.py:

class Envelope:
    """
    Envelope to add metadata to a message.

    This is an internal type and is not part of the public API.

    :param message: the message to send
    :type message: any
    :param reply_to: the future to reply to if there is a response
    :type reply_to: :class:`pykka.Future`
    """

    # Using slots speeds up envelope creation with ~20%
    __slots__ = ["message", "sender", "reply_to"]

    def __init__(self, message, sender=None, reply_to=None):
        self.message = message
        self.reply_to = reply_to
        self.sender = sender
...

In _actor.py:

    def _actor_loop(self):
        """
        The actor's event loop.

        This is the method that will be executed by the thread or greenlet.
        """
        try:
            self.on_start()
        except Exception:
            self._handle_failure(*sys.exc_info())

        while not self.actor_stopped.is_set():
            envelope = self.actor_inbox.get()
            self.current_message = envelope
...

and

def _sender(self):
    sender = self.current_message.sender
    if not sender:
        sender = DeadLetters(self)
    return sender

where

class DeadLetters(ActorRef):
    def __repr__(self):
        return f"<DeadLetters of {self}>"

    def tell(self, message):
        logger.warning("%s: Received message %s via 'tell'.", self.__repr__(), message)

    def ask(self, message, block=True, timeout=None):
        logger.warning("%s: Received message %s via 'ask'.", self.__repr__(), message)

or something like this. Just to cover a situation when an actor tries to send something to a sender and this sender is not an actor.
This DeadLetters pseudo-ActorRef could be pre-created, but I don't expect lots of messages to end there, so pre-creating it for every actor could be a waste of memory.

I don't mean, that's a great solution, but that's an example of how it's implemented in Akka Classic.

Surely, it would be necessary to modify tell and ask methods a bit. If we forget about Akka ! method and take a look at its tell method, it looks like this:

  final def tell(msg: Any, sender: ActorRef): Unit = this.!(msg)(sender)

So, to have something similar, we would need to do something like:

    def tell(self, message, sender=None):
        """
        Send message to actor without waiting for any response.

        Will generally not block, but if the underlying queue is full it will
        block until a free slot is available.

        :param message: message to send
        :param sender: ActorRef of a sender
        :type message: any

        :raise: :exc:`pykka.ActorDeadError` if actor is not available
        :return: nothing
        """
        if hasattr(sender, "actor_ref"):
            sender = sender.actor_ref

        if not self.is_alive():
            raise ActorDeadError(f"{self} not found")
        self.actor_inbox.put(Envelope(message, sender=sender))

And then use it from an actor like:

def on_receive(self, message):
    if message == "Hello!":
        self._sender().tell("Why, hello, good sir!", self)

That's not pretty, but since we don't have implicit variables right now I don't see how to make it prettier.
As an unexpected plus it would give us a chance to send a message "on behalf" of some other actor. This can be useful sometimes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants