runtime: write Registry#261
Conversation
Signed-off-by: Nathan Perry <[email protected]> Change-Id: I1da3320594c3f42f0b7350f137e92b2d6a6a6964
Signed-off-by: Nathan Perry <[email protected]> Change-Id: I3674e34c01786579d90d26acd040cec26a6a6964
Signed-off-by: Nathan Perry <[email protected]> Change-Id: I3674e34c01786579d90d26acd040cec26a6a6964
Signed-off-by: Nathan Perry <[email protected]> Change-Id: I0f032c94121fee97b846a4585d89fbee6a6a6964
nrc
left a comment
There was a problem hiding this comment.
LGTM, only significant comments are a couple of possible simplifications.
| Ok(()) | ||
| } | ||
|
|
||
| /// Register an actor in the registry as the canonical actor of its type. |
There was a problem hiding this comment.
If name is specified, is this comment still correct or should it only be unnamed actors which are type-canonical?
| where | ||
| A: kameo::Actor + Any, | ||
| { | ||
| let aref = self |
There was a problem hiding this comment.
nit: could this call lookup_opt to avoid the code dup?
| self.resolve_aref(aref) | ||
| } | ||
|
|
||
| /// Send an ask to an actor in the registry. |
There was a problem hiding this comment.
Could you document the meaning of the wait arg please? (Unless it is a Kameo convention)
| #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
| struct Id { | ||
| actor_ty: TypeId, | ||
| name: SmolStr, |
There was a problem hiding this comment.
Is it worth using a SmolStr here rather than a plain String?
| /// names are permitted to overlap between actors of different types, as there can't be a collision | ||
| /// between them. | ||
| /// | ||
| /// The conventional structure of names in this registry includes the idea of a "canonical" actor |
There was a problem hiding this comment.
I wonder if the added complexity of canonical actors is worth it. Presumably, users could always just a pick a name or use a conventional name? I suppose it's not a huge amount of complexity, but it does complicate the API a little.
| where | ||
| A: kameo::Actor + Any, | ||
| { | ||
| /// Construct a register request for the canonical actor of type `A`. |
There was a problem hiding this comment.
| /// Construct a register request for the canonical actor of type `A`. | |
| /// Construct a register request for a named or canonical actor. |
| { | ||
| pub fn new(name: Option<SmolStr>) -> Self { | ||
| Self { | ||
| id: if let Some(name) = name { |
Stacked on #253.
Write a
Registryactor in roughly the Erlang model allowing actors to be registered and looked up by name. TheRegistrycan also handle forwarding messages for registered actors, which can be useful for message-ordering purposes (the lookup and send are atomic from the perspective of the forwarding actor).Notably, each name is registered in the scope of a single actor type. This is a consequence of kameo's design trying to enforce strong message typing: the
ActorReftype is a pid augmented with the actor typeA, meaning that a collection ofActorRefs simply can't be held generically asActorRefs (which is the only type you can send a message to because theAcan be used to prove thatAcan handle the message you're trying to send it). To erase the actor type, you need to holdBox<dyn Any>and downcast later. But it's not possible to recover the actor typeAfrom a generic incoming message (which you might want to forward): the sender must specify the type of the actor they're trying to communicate with. Hence, names overlapping between actors doesn't make sense andRegistryseparates them into what are essentially namespaces byTypeId::of::<A>().This is in the interest of supervision; as in Erlang, when an actor process dies and a supervisor restarts the process, its old pid (
ActorRef) doesn't address it anymore. So you functionally need a naming service to resolve the semantic, symbolic name ("the control plane actor") to the actual pid (ActorRef) it currently has at the time you're trying to send a message.kameo's built-in registry
Kameo has a built-in actor registry which provides
ActorRef::register(name). We don't use this for a few reasons:ActorRef::registeris global, which would break the ability to run more than one runtime in the same process without adding namespacing logic, which would require the runtimes to be aware of each other somehow (probably through more global state). I wanted to avoid that.kameo::Registry::new, but the implementation is just a wrapper around a hashmap. It doesn't run as an actor. That's not ideal for observability or message-ordering — you'd need to stick it behind a mutex to run it like this, which is what the global one does. This also makes it difficult to wait for another actor to register itself, which this implementation provides