Closed rkuhn closed 3 years ago
@BathTor Since the S: Spawner
member is publicly accessible, this might open up the possibility of writing an actor that requires access to Kompact features, like interacting with its component lifecycle. Does any of this look useful to you?
@Bathtor Since the
S: Spawner
member is publicly accessible, this might open up the possibility of writing an actor that requires access to Kompact features, like interacting with its component lifecycle. Does any of this look useful to you?
Sorry for the late response, I still haven't really had time to look into this in sufficient detail to judge if/how it might work. The code is just too tricky for me to just look at it and see if it makes sense. I gotta find some time to actually play around with it, try integrating it with Kompact and seeing where stuff breaks, etc. But I'm a bit pressed for time at the moment. Just wanted to let you know that's not fallen under table completely. I'll get around to it eventually. Just...not very soon, sorry.
Thanks for the update! And I agree, the types are way too complicated as it stands — unfortunately I haven’t yet found a simpler solution, apart from punting on the executor abstraction and forcing actor authors to explicitly name the executor themselves whenever they create other actors.
Good news everyone: thinking from the assembly backwards, and considering the power-law code explosion for runtimes vs. mailboxes vs. message types vs. future types, I think I found a better way. Executor-agnostic usage looks like this:
async fn actor(mut ctx: Context<(String, ActorRef<String>)>) -> Result<()> {
loop {
let (name, sender) = ctx.receive().await?;
let (responder, handle) = actor!(Mailbox, |ctx| {
let m = ctx.receive().await?;
sender.tell(format!("Hello {}!", m));
});
responder.tell(name);
let _ = handle.await;
}
}
and it is spawned like this:
let (aref, _join_handle) = actor!(Mailbox, Spawner, fn actor(ctx));
// or with inline definition:
let (aref, join_handle) = actor!(Mailbox, Spawner, |ctx| {
msg = ctx.receive().await?;
_ = tx.send(msg);
});
The main trick was to split off the polymorphic mailbox creation into its own Mailbox
trait, leaving the context quite simple:
pub struct Context<M> {
recv: Box<dyn Receiver<M>>,
aref: ActorRef<M>,
spawner: Arc<dyn Spawner>,
}
Boxing is preferable here because otherwise there would be one implementation per message type per mailbox type per runtime type. This brings it down to one per message type, which is probably needed for performance-sensitive mailbox usage. The full implementation of a Tokio-based executor is about a dozen meaningful lines (60 lines with mostly boiler plate).
As a positive side-effect, the creator of an actor gets to choose the mailbox implementation to use for it. The Mailbox
in the code above is the Tokio unbounded mpsc channel implementation.
This is a first attempt at describing what an actor is in Rust without tying it to an execution strategy or mailbox type. The resulting code is workable but somewhat clunky, and the involved types are definitely too complex, but this is how it looks:
The issues are:
Spawner
cannot be made trait-safe because it is by its definition a code template