Open drozzy opened 10 years ago
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.
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:
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 #55. 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.
- Before calling
on_receive()
, setself.sender = envelope.sender
please publish a release with this feature, to verify its usability in practice
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.
any updates?
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:
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