Readit News logoReadit News
tel · 10 months ago
I'm always most curious with these frameworks how they're considering supervision. That's the real superpower of OTP, especially over Rust.

To me, Rust has adequate concurrency tooling to make ad hoc actor designs roughly on par with more developed ones for many tasks, but supervision is both highly valuable and non-trivial. Briefly, I'd say Rust is top-notch for lower-level concurrency primitives but lacks architectural guidance. Supervisor trees are a great choice here for many applications.

I've tried implementing supervision a few times and the design is both subtle and easy to get wrong. Even emulating OTP, if you go that route, requires exploring lots of quiet corner cases that they handle. Reifying this all into a typed language is an additional challenge.

I've found myself tending toward one_for_all strategies. A reusable Fn that takes some kind of supervisor context and builds a set of children, potentially recursively building the supervision tree beneath, tends to be the best design for typed channels. It forces one_for_all however as it's a monolithic restart function. You can achieve limited (batch-y) rest_for_one by having the last thing you boot in your one_for_all be another one_for_all supervisor, but this feels a little hacky and painful and pushes back against more granular rest_for_one designs.

You then probably want a specialized supervisor for one_for_one, similar to Elixir's DynamicSupervisor.

pton_xd · 10 months ago
> That's the real superpower of OTP, especially over Rust.

That, and preemptive scheduling. And being able to inspect / debug / modify a live system. Man, these actor frameworks just make me appreciate how cool Erlang is.

0x457 · 10 months ago
tel · 10 months ago
It's clear that there's some beginning of this in place. They reference it as initial in the docs there and it's missing quite a bit. I'm not a huge fan of what I'm seeing here where each actor is implicitly itself and a supervisor (i.e., ActorRef makes reference to the actor's children). I think I saw that first in Akka and while it makes sense in theory, I don't like the practice.

To me, your supervision tree should be dedicated to that purpose and forms a superstructure relating entirely to spawning, resource provisioning, restarting, shutdown. Part of what makes it nice in Erlang is that it's consistent and thoughtless, just part of designing your system instead of being behavior you have to write or worry about much.

Here with Ractor they've built a special monitoring channel and callbacks into Actor (`handle_supervisor_evt`). This implies at some point one might write a nice supervisor in their framework that hopefully has some of those properties.

ninkendo · 10 months ago
At $dayjob I've taken to just implementing my own actor-like model when I need interior mutability across tasks. Something like:

    struct State { counter: usize, control: mpsc::Receiver<Msg> }
    struct StateActor { addr: mpsc::Sender<Msg> }
    enum Msg {
        Increment { reply: oneshot::Sender<()> }
    }

    impl StateActor {
        pub async fn increment(&self) {
            let (tx, rx) = oneshot::channel();
            let msg = Msg::Increment { reply: tx };
            self.addr.send(msg).await.unwrap();
            rx.await.unwrap();
        }
    }

    impl State {
        fn start(self) {
            tokio::spawn(async move {
                /* ... tokio::select from self.control in a loop, handle messages, self is mutable */
                /* e.g. self.counter +1 1; msg.reply.send(()) */
            })
        }
    }

    // in main
    some_state_actor.increment().await // doesn't return until the message is processed

A StateActor can be cheaply cloned and used in multiple threads at once, and methods on it are sent as messages to the actual State object which loops waiting for messages. Shutdown can be sent as an in-band message or via a separate channel, etc.

To me it's simpler than bringing in an entire actor framework, and it's especially useful if you already have control loops in your program (say, for periodic work), and want an easy system for sending messages to/from them. That is to say, if I used an existing actor framework, it solves the message sending/isolation part, but if I want to do my own explicit work inside the tokio::select loop that's not strictly actor message processing, I already have a natural place to do it.

slau · 10 months ago
I’m not saying that you can’t use another project’s name, but there already exists an actor framework named Ractor, but this one written in Ruby.

https://docs.ruby-lang.org/en/master/Ractor.html

Alifatisk · 10 months ago
I was about to mention this, Ractor is already a name used to describe the Ruby Actor class. But I guess this is only known within the Ruby Community? It would’ve taken one search to find out about this. Either way, since both names are bounded to their languages, the context should make it clear what is referred to. And to be fair, the name Ractor makes sense for both languages.
snowboarder63 · 10 months ago
Honest truth? I didn't Google it first lol. I just checked if there was a crate with the same name and carried on. I only found out about the Ruby ones after I posted on Reddit for the first time. By then it was too late since I had already reserved the crate name and it was being downloaded
hit8run · 10 months ago
Well someone got inspired by Rubys Ractors hence the naming?

https://docs.ruby-lang.org/en/master/ractor_md.html

mtndew4brkfst · 10 months ago
Unlikely, the contributors don't seem to have meaningful Ruby backgrounds. Isn't it a simpler supposition that the naming intent was merely (R)ust Actors?
zem · 10 months ago
well, it's "r" + "actor" - just a coincidence that both "ruby" and "rust" started with an R!
pshc · 10 months ago
I have to wonder if Ractor was inspired from The Diamond Age.
rubyfan · 10 months ago
Yeah bad choice of naming here. Oddly this project shows up first on DDG for “ractor” despite prevalent Ruby ractor documentation.

Dead Comment

jatins · 10 months ago
That looks interesting! What's the distributed story of Ractor? Would you need a central store like Redis to serve as Actor registry?

One of the promises of Elixir/Erlang is that you can call a process/Actor on different machine just like you can one on same once you put together a bunch of machines in a cluster

stefanka · 10 months ago
Seems to be supported

> Additionally ractor has a companion library, ractor_cluster which is needed for ractor to be deployed in a distributed (cluster-like) scenario. ractor_cluster shouldn’t be considered production ready, but it is relatively stable and we’d love your feedback!

kemiller · 10 months ago
Elixir/erlang/gleam also have the advantage that many libraries are written as servers and so automatically gain the benefits and resilience. Something all these actor frameworks can’t give you.
brunoqc · 10 months ago
With gleam, how do you call an actor on another machine when you have a cluster? Do you need something like redis?
doawoo · 10 months ago
The Erlang runtime and EPMD takes care of this for you. Every node in the cluster gets a name, so you can address it directly, and, every PID is unique across nodes on a cluster, so you can send messages to processes (actors) no matter where they are in the cluster.

OTP is super powerful out of the box :)

webkike · 10 months ago
I personally find actors in rust to be too much of a head ache. You might as well use Erlang (or elixir) and do your heaving lifting in rust with somethign like https://github.com/rusterlium/rustler
qwertox · 10 months ago
Related from one month ago: Kameo - https://github.com/tqwewe/kameo

Show HN: Kameo – Fault-tolerant async actors built on Tokio - https://news.ycombinator.com/item?id=41723569 - October 2024 (58 comments)

Sytten · 10 months ago
I personnaly don't like the single enum model for messages. I prefer the generic Handler trait model.

Also another square that I have not circled with async actor other than actix is that all of them use mutable self and they dont have a great way to have long running tasks. Sometimes you would want to call a remote server without blocking the actor, in actix this is easy to do and you unamed child actors to do that. All those newer frameworks don't have the primitives.

derefr · 10 months ago
> all of them use mutable self

As demonstrated in the linked tutorial, Ractor passes its handlers `&self` with `&mut State`.

echelon · 10 months ago
It looks like the message type can be anything:

    type Msg = MyFirstActorMessage;