7 ms·
Show HN: Ractor – a Rust-based actor framework with clusters and supervisors
SOME HIGHLIGHTS.
* We have a full supervision tree so actors can monitor other actors for exits and unhandled panic!s (at least the ones that can be caught)
* The actor lifecycle is handled for you in a simple single-threaded, message handler primitive
* You have a mutable state with each message handling call, so you have an easy way to create stateful actors and update that state as messages are processed
* Actors talk to other actors by message passing, but there are remote-procedure-calls (RPCs) so actors can "ask a question" to another actor and wait on the reply.
* A lot of the concurrency primitives are handled by the framework, such as cancellation/termination of actors (both graceful and forceful)
* A Factory primitive in order to formulate distributed processing pools with multiple job routing options
* Early but stable support for a distributed epmd-like cluster environment, where you can talk to actors over a network link. It's an additional crate (ractor_cluster) that builds on ractor to facilitate the inter-connection between nodes and support remote casts and calls to actors on a remote node.
We're openly seeking feedback, so please feel free to utilize the library and let us know if there's anything you find missing or doesn't work as expected!
- qualudeheart 4y agoWhat would you use this thing for?
- sidlls 4y ago"[W]ith "NO unsafe code...."* * except all the unsafe code in the dependencies (e.g. https://github.com/tokio-rs/tokio/search?q=unsafe https://github.com/tokio-rs/tokio/search?q=unsafe)
- deleted 4y ago[deleted]
- vore 4y agoClearly nothing can be truly safe then if you ever use the standard library ;-)
- pkolaczk 4y agoAll safe language runtimes use unsafe code by this logic.
- Alifatisk 4y agoDoes that also apply to GC-languages?
- linguistics__ 4y agoYes, if you dare to look into a GC implementation, you might see some truly unsafe code indeed
- alpaca128 4y agoYou can't have 100% safety all the way down. Garbage collection doesn't help you if the VM itself has a memory leak or some driver crashes.
- phaylon 4y agoYou know, people outside the Rust community keep bringing this up as some kind of argument against it. But to me, it's one of the biggest points for it; because the important thing about `unsafe` encapsulation isn't what's inside of it, but what's possible on the outside.
- Traubenfuchs 4y agoIs there any resource that can help me properly understand the actor model and make an informed decision on whether it's something everyone should have in their toolbelt or just another technology like blockchain, in search of non-scam usecases with mainstream appeal?
- Yoric 4y agoActors have been used since the 90s for distributed programming, in particular at telcos. To a large extent, microservices are a poor man's reimplementation of actors, with all the drawbacks and not many of the benefits.
- samuell 4y agoThis classic book contains a chapter (chapter 5) on Actors: https://www.amazon.com/Seven-Concurrency-Models-Weeks-Programmers/dp/1937785653 https://www.amazon.com/Seven-Concurrency-Models-Weeks-Progra...
- dividedbyzero 4y agoThe actor pattern is used quite a bit in the functional world (Akka is pretty popular in the Scala ecosystem). It's just a design pattern that lends itself well to distributed applications with complex communication structure like some microservice systems. Some people use it to represent graph-like data structures with nodes that update themselves based on their neighborhood, though that's kind of gimmicky. There's no connection at all (that I'm aware of) to crypto or fintech scams, and I guess it doesn't hurt to have a look at the actor pattern if you're in a functional language; always good to know what tools exist.
- latenightcoding 4y agoDesigning Data-Intensive Applications is a good start.
- klabb3 4y agoNot scam or even fad. It’s legit. However, details vary so I agree with the sibling that “design pattern” is the best classification for it. I think it’s fair to say that the actor model is analogous to FP, but for distributed (and to a lesser extent multithreaded) computing. Just like FP has friction against imperative programming, there is a similar friction when interoping with traditional systems. Likewise, benefits pile up once you have some critical mass of subsystems that all use the actor model, where certain problems just go away. I’m personally a big believer, it fits my mental model as I like to think about data flows first. That said, almost all systems today are request-response, so you have to either accept a certain amount of friction or embrace the much smaller ecosystem of message-based systems. Just like regular programming is going more and more multi-paradigm, incorporating FP and others into mainstream langs, it seems like the distributed world is moving slowly in a multi-paradigm direction as well. For instance, databases and file systems are increasingly using message/event streams and sometimes exposing them directly to users.
- yodon 4y agoHow would you compare this to Orleans? (beyond the obvious Rust vs C# bit)
- snowboarder63 4y agoActually I was not familiar with Orleans until now. I think there's probably similar primitives you could build but from my quick 2s read they're solving a higher order problem. Additionally having written an actor lib in the past in .net, I can say they can't match rust for speed but they do have some nice syntax stuff with reflection we can't do. So it's a tradeoff on stuff like syntactic sugar and speed imo
- pjmlp 4y agoOrleans was born to support Halo's server backend and went from there, they know their stuff regarding performance. > Orleans is used by Microsoft in Azure, Xbox, Skype, Halo, PlayFab, Gears of War, and many other internal services. Orleans has many features that make it easy to use for a variety of applications. https://learn.microsoft.com/en-us/dotnet/orleans/overview https://learn.microsoft.com/en-us/dotnet/orleans/overview Until Ractor proves itself in similar workloads, it is a tiny bit more than syntax sugar difference.
- deleted 4y ago[deleted]
- pjmlp 4y agoFair enough, then it is proven that it can handle the load. Then there is the question about the features themselves, besides what any actor framework should provide as baseline. Orleans is already on version 7.
- pull_my_finger 4y agoNever a bad time to plug Pony lang[1] - a safety-oriented actor-model language. In addition to the numerous safety guarantees, you also get a beautiful syntax and automatic memory management. Really a great language that often gets overshadowed by Rust's hype-turfing. [1]: https://www.ponylang.io/ https://www.ponylang.io/
- satvikpendem 4y agoPony is nice but the ecosystem can't compare to Rust or others.
- pull_my_finger 4y agoA bit of a chicken and the egg problem, I'd say, but you're right. You can, however, extend your applications using the C API which opens you up to the entire C ecosystem. Someone has to go and write a bunch of bindings though, and that largely hasn't happened yet, probably because the language is still in flux and writing bindings can be time consuming, and having to maintain them against constant breaking changes would be awful.
- qualudeheart 4y agoLanguage models solve that. AI translation from Rust into Pony.
- deleted 4y ago[deleted]
- pvg 4y agoNever a bad time to plug Somebody else's Show HN is not really a great time or venue to tangent off into some language war.
- LoganDark 4y agoI don't think it's unreasonable to link something relevant in the comments of a Show HN.
- faitswulff 4y agoAny connection to Ruby's Ractor class? https://ruby-doc.org/core/Ractor.html https://ruby-doc.org/core/Ractor.html
- snowboarder63 4y agoActually no, I came up with the name independently and only later discovered their lib. I think it's safe to say we diverge a lot in practice though lol
- JKCalhoun 4y agoMy mind went to the Artificially Insane RACTER: https://en.wikipedia.org/wiki/Racter https://en.wikipedia.org/wiki/Racter
- jtwebman 4y agoThe no VM thing I think is a downside. Sure raw performance is nice but not having a process eat all system cou resources is the true beauty of Erlang and Elixir to allow you to self heal. Are you handling that with this library?
- deleted 4y ago[deleted]
- Fiahil 4y agoDocker and kubernetes are here for that
- paulgb 4y agoThey’ll give you ways to limit the CPU use of the OS process, but not the individual actor “processes” (Erlang overloads the term), which are opaque to k8s/linux/docker.
- snowboarder63 4y agoCan you give an example of what you're referring to? I don't know of anything limiting memory / cpu / etc in Erlang at least of any individual gen_server. We have the Factory processes which can gracefully loadshed, but that doesn't stop you from having a memory leak. At least Rust doesn't have a garbage collector, so when the actor is stopped + dropped, it'll cleanup not only it's state but also it's message queue's flushing them so that all memory is released at the time of drop.
- Yoric 4y agoLooks very nice! How does it compare to lunatic https://github.com/lunatic-solutions/lunatic https://github.com/lunatic-solutions/lunatic ? (besides wasm vs. native) Also, what's the deployment story?
- snowboarder63 4y agoI mean lunatic runs within Wasm, which isn't the end of the world, but we compile down to native. Lunatic generally seems to want to be the end-all of concurrency but we fit more into an existing Tokio-based environment. As far as deployment, do you mean like how we came to build this? Or like how you'd actually deploy it...? Because that's really up to the program being built imo
- Yoric 4y ago> As far as deployment, do you mean like how we came to build this? Or like how you'd actually deploy it...? Because that's really up to the program being built imo I mean that deployment of Erlang code on a BEAM cluster is pretty simple, iirc. I wonder if you have a solution that handles that part as conveniently as Erlang/Elixir/BEAM.
- snowboarder63 4y agoAh yeah we don't have hot-updates like Erlang does (we may never, it depends on how Rust would let us do it). That being said we aren't running on a runtime, so Rust code is native-compiled. You could take down and upgrade a node in a cluster as long as the network protocol doesn't change (or is at least backwards compat), but you wouldn't be able to like upgrade a single actor on a node.
- Yoric 4y agoI suspect that it would be possible to somehow implement hot-updates with native, if you're willing to use some container as your "runtime". I haven't investigated this seriously, though, so I may be wrong.
- idiomaticrust 4y agoLooks interesting. I'm sure I'll try it one day.
- asymmetric 4y agoIs this in use at WhatsApp (main dev works there). Asking cause I know WhatsApp was built on Erlang, and was wondering if they still are, or have (partially) moved to Rust.
- cmoski 4y agoPretty sure they're still on erlang.
- deleted 4y ago[deleted]
- asymmetric 4y agoAt the rewrites happening for performance reasons? Anything you’ve published/will publish about this?
- deleted 4y ago[deleted]
- dmix 4y agoNeat. How big is the dev team working on this Erlang & Erlang-adjacent stuff?
- snowboarder63 4y agoNot sure what's public here, so let's just say "big", as in 100's+.
- yodon 4y agoWait. This package is in use in production at WhatsApp and the only mention of that is many levels deep in a comment thread on HN? That's seriously burying the lede.
- vegabook 4y agoDoes this have soft real-time preemption capabilities like Erlang? IE: Do I get the BEAM's latency ~"guarantees"?
- snowboarder63 4y agoAh so presently we're using the Tokio scheduler, which does cooperative scheduling not preemptive. We've abstracted pretty much all of our core concurrency primitives to a single module however so the plan is to support future schedulers (perhaps custom) in the future, but that's not the main focus at the moment. However if you have a blocking task, that will utilize a lot of sequential CPU time, the general guidance is to tokio::spawn_blocking(..) in your actor then await the result, so a new dedicated i/o thread will be used and your actor will yield the main scheduler for other workers.
- fulmicoton 4y agoThe trouble with spawn_blocking is that it runs on the tokio thread pool... By default the size of that thread pool is gigantic because it is meant to run blocking IO. For computation, you would want to have a number close to the number of threads of the CPU. ... And you can't have both.
- zelphirkalt 4y agoHere is another one in Rust: https://docs.rs/bastion/latest/bastion/ https://docs.rs/bastion/latest/bastion/ How does Ractor compare to Bastion?
- rapnie 4y agoMy first observation is that Bastion looks abandoned and last commit was more than a year ago. Maybe it is feature-complete and stable, but..
- wongarsu 4y agoThis looks interesting. I don't think the single-threaded design fits my use cases, I would prefer a thread per actor. Both are valid tradeoffs though, and it's always good to see more projects in the area
- cmoski 4y agoAre you going to be able to pattern match as nicely as one can on the BEAM? In practice, is the handle function going to match on each message and then call a handler for that specific message which may then match on the state?
- snowboarder63 4y agoThat's really up to you, Rust has great pattern matching imho, rivaling Erlang's for sure, but with some nice syntax upgrades. Additionally in Ractor, RPC's are strong-typed to the reply type, so a `call` in Erlang might result in the wrong type coming back which then you have to handle in your reply pattern matching or crash. This isn't possible in Rust w/ Ractor, since you _know_ the strong type in advance and can't send an incorrect value.
- jkbbwr 4y agoI still think the design in the beam world is as close as we can get to perfect actors. Its very very elegant in how you can easily resume crashed actors, upgrade them, migrate them. `handle_call(pattern, from, state)` Return your reply and your new state and boom. It just works.
- snowboarder63 4y agoYeah hot upgrades is something we'd love to get to, but probably won't be able to swing in a compiled language. We had worked out something ugly back in the day in a .net style environment (different library at a different job), but it still didn't compare to Erlang's hot upgrade flow.
- jkbbwr 4y agoSo a problem I noticed, is you pass mutable state to the actor. What happens if the actor mutates the state then fails? Do you just dirty the state and throw it away or do you retry at all?
- snowboarder63 4y agoIf the actor fails, it could have failed mid-mutation so we can't trust the "state" object. This would be the same if we passed in an owned state (rather than a mutable reference) and the handler didn't reply with a new state object. If a failure happens, the state is dropped there too (in Rust). I know it's not exactly how Erlang handles it, but it's a tradeoff we've accepted in this case. We're open to suggestions however!
- Yoric 4y agoIt looks like lunatic has a solution? Not very familiar with the project, but maybe worth checking out.
- uaksom 4y agoThey don't, at least not yet. https://github.com/lunatic-solutions/lunatic/issues/150 https://github.com/lunatic-solutions/lunatic/issues/150 It is WebAssembly based though, so it should be easier than trying to deal with dynamic libraries and Rust's ABI.
- farnerup 4y agoIs the "let it crash" philosophy a part of this or will a panic bring down the whole process?
- snowboarder63 4y agoPanic's (which can be captured) are captured and will be propagated to the supervisors. That's one of the biggest goals of building this, panics in tasks which have been spawned is a nightmare to deal with
- buybackoff 4y agoWhen I was looking for a name for my now-archived distributed actors system based on Redis ( https://github.com/buybackoff/Ractor https://github.com/buybackoff/Ractor), the choice for Redis+Actor was between Reactor, Redactor and Ractor. Then I found a reference to The Diamond Age book (https://en.m.wikipedia.org/wiki/The_Diamond_Age https://en.m.wikipedia.org/wiki/The_Diamond_Age) where a ractor is a virtual actor in interactive games and movies working from a computer-provided script. I found it somewhat fitting to the actors abstraction. And the most cyberpunky.
- deleted 4y ago[deleted]
- Sytten 4y agoVery glad to see this emerge, we have been using actix in production for a year now and it's a bit annoying the state of it. The dominant sentiment in the Rust community right now seems to be to build your own framework, but I really don't agree with this sentiment. Regarding the implementation, I would have preferred to have different handler implementations for the various message types instead of a big enum. It's not rare to have 4-5 messages for a single actor and the current approach will make it messy very fast. One big thing of actix is the ability to control the concurrency of message processing in the async case (sequential or parallel). How does Ractor does it?
- snowboarder63 4y agoSo to your two parts. 1. If you would want independent message handlers, normally we'd just add functions off the state which would be called strait from the message handler's match statement. I.e. something like self.handle_msg_1(message, state, ...). We looked at having multiple handlers but you kind of either get into generic's hell or having to do some kind of ugly proc macro. We figured this left it up to the implementor on how you want to handle it 2. Sequential processing would really be a factory. That would do parallel work of jobs using a basic actor implementation while handling concurrent work limits, job routing, etc. Otherwise you could always spawn inside your actor and like a wait on multiple handles if you wanted, but that gets messy and kind of diverges from the true intent. There is a factory implementation in the base ractor lib, feel free to take a look!
- nazka 4y agoAwesome to see a project like! One question: what happens for panics that won’t be able to be catched by std::panic::catch_unwind?
- snowboarder63 4y agoThat's essentially a process-abort level panic, which then your whole environment would die. Unfortunately there's not much we can do about that. This will catch all unwindable panics however, so that's quite a lot but yeah there's an edge-case. https://doc.rust-lang.org/std/panic/fn.catch_unwind.html https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
- nazka 4y agoOk thank you for your answer.
- mavam 4y agoBecause it's related and a powerful alternative for those stuck in C++ land: The C++ Actor Framework (CAF) implements a combination of Akka and Erlang semantics. Especially the type-safe actor abstractions saved us many bugs, e.g., sending an uint32 but trying to receive a uint64. https://actor-framework.readthedocs.io/ https://actor-framework.readthedocs.io/