Please change the title to the original, "Actors: A Model Of Concurrent Computation In Distributed Systems".
I'm not normally a stickler for HN's rule about title preservation, but in this case the "in distributed systems" part is crucial, because IMO the urge to use both the actor model (and its relative, CSP) in non-distributed systems solely in order to achieve concurrency has been a massive boondoggle and a huge dead end. Which is to say, if you're within a single process, what you want is structured concurrency ( https://vorpus.org/blog/notes-on-structured-concurrency-or-g... ), not the unstructured concurrency that is inherent to a distributed system.
I'm working on an rest API server backed by a git repo. Having an actor responsible for all git operations saved me from a lot of trouble as having all git operations serialised freed me from having to prevent concurrent git operations.
Using actors also simplified greatly other parts of the app.
Isn’t that just serializing through a queue, aka. the producer consumer pattern? Which I think the correct solution for most common concurrency problems.
For something to be an actor, it should be able to:
- Send and receive messages
- Create other actors
- Change how the next message is handled (becomes in Erlang)
I think the last one is what makes it different it from simple message passing, and what makes it genius: state machines consuming queues.
It is endemic to the JVM world that people try various forms of snake oil concurrency inside an address space like actors and the original synchronized model when java.util.concurrent and Executors are "all you need."
It was a theme in part of my career to pick up something written in Scala that used actors that (1) didn't always get the same answer and (2) didn't use all the CPU cores and struggling for days to get it working right with actors then taking 20 minutes to rewrite it using Executors and getting it to work the first time and always work thereafter.
Nurseries sound similar to run-till-completion schedulers [0].
> IMO the urge to use both the actor model (and its relative, CSP) in non-distributed systems solely in order to achieve concurrency has been a massive boondoggle
Can't you model any concurrent non-distributed system as a concurrent distributed system?
> Can't you model any concurrent non-distributed system as a concurrent distributed system?
Yes, in the same way that you can give up `for` loops and `while` loops and `if` statements and `switch` statements and instead write them all with `goto`, but you don't do this, and anyone advising you to do this would be written off as insane. The entire thrust of this thread is that you can have a more reliable system that is easier to reason about if you use specific constructs that each have less power, and non-distributed systems have the option to do this. Unstructured concurrency should be reserved exclusively for contexts where structured concurrency is impossible, which is what the actor model is for.
I don't know if I'd apply your blanket prescription, but at some level I agree... here's where I see Actors often going wrong and substantially agree with you: state propagation / replication:
I've been on more than one team that has broken their (in-process, single machine) process up into multiple "actors" (or "components" or "services") through communicating threads (usually over Rust channels) and then had a situation where they replicate some piece of state through messaging because they're been told that their system must not have global (mutable or immutable) state.
But now they've just created a whole pile of inefficient boiler plate (propagating copies of effectively the same piece of global state through different services) and created a new way of having race conditions and/or just plain old stale or inconsistent data. For what are essentially ideological reasons.
Every new feature in this model ends up being mostly plumbing of state replication between what are supposed to be isolated component models.
The answer to me is just to establish a discipline where a given piece of data is owned for writes by one task or component, but can be freely read by any.
If you truly have a stateless system or extremely clear data ownership boundaries, I can see the value of a CSP/actor approach. And in the context of Rust's borrow checker this model is fairly convenient. But it quickly becomes prone to cargo-culting and becomes a recipe for hairy, hard to maintain code.
I am convinced most teams blanket applying actors would be far better suited to more tuplespaces/blackboard/Linda type model for concurrent coordination. A way of working that never caught on, but has always been attractive to me.
Note that Rust supports a form of structured programming (though only via thread-based concurrency, not via async), provided by the `std::thread::scope` API: https://doc.rust-lang.org/std/thread/fn.scope.html . The blog post above calls out an older version of this API as inspiration.
I’m currently engineering a system that uses an actor framework to describe graphs of concurrent processing. We’re going to a lot of trouble to set up a system that can inflate a description into a running pipeline, along with nesting subgraphs inside a given node.
It’s all in-process though, so my ears are perking up at your comment. Would you relax your statement for cases where flexibility is important? E.g. we don’t want to write one particular arrangement of concurrent operations, but rather want to create a meta system that lets us string together arbitrary ones. Would you agree that the actor abstraction becomes useful again for such cases?
Data flow graphs could arguably be called structured concurrency (granted, of nodes that resemble actors).
FWIW, this has become a perfectly cromulent pattern over the decades.
It allows highly concurrent computation limited only by the size and shape of the graph while allowing all the payloads to be implemented in simple single-threaded code.
The flow graph pattern can also be extended to become a distributed system by having certain nodes have side-effects to transfer data to other systems running in other contexts. This extension does not need any particularly advanced design changes and most importantly, they are limited to just the "entrance" and "exit" nodes that communicate between contexts.
I am curious to learn more about your system. In particular, what language or mechanism you use for the description of the graph.
> we don’t want to write one particular arrangement of concurrent operations, but rather want to create a meta system that lets us string together arbitrary ones. Would you agree that the actor abstraction becomes useful again for such cases?
Actors are still just too general and uncontrolled, unless you absolutely can't express the thing you want to any other way. Based on your description, have you looked at iterate-style abstractions and/or something like Haskell's Conduit? In my experience those are powerful enough to express anything you want to (including, critically, being able to write a "middle piece of a pipeline" as a reusable value), but still controlled and safe in a way that actor-based systems aren't.
> both the actor model (and its relative, CSP) in non-distributed systems solely in order to achieve concurrency has been a massive boondoggle and a huge dead end.
I've written a non-distributed app that uses the Actor model and it's been very successful. It concurrently collects data from hundreds of REST endpoints, a typical run may make 500,000 REST requests, with 250 actors making simultaneous requests - I've tested with 1,000 but that tends to pound the REST servers into the ground. Any failed requests are re-queued. The requests aren't independent, request type C may depend on request types A & B being completed first as it requires data from them, so there's a declarative dependency graph mechanism that does the scheduling.
I started off using Akka but then the license changed and Pekko wasn't a thing yet, so I wrote my own single-process minimalist Actor framework - I only needed message queues, actor pools & supervision to handle scheduling and request failures, so that's all I wrote. It can easily handle 1m messages a second.
I have no idea why that's a "huge dead end", Actors are a model that's a very close fit to my use case, why on earth wouldn't I use it? That "nurseries" link is way TL;DR but it appears to be rubbishing other options in order to promote its particular model. The level of concurrency it provides seems to be very limited and some of it is just plain wrong - "in most concurrency systems, unhandled errors in background tasks are simply discarded". Err, no.
Big Rule 0: No Dogmas: Use The Right Tool For The Job.
Is Pony still an actively developed language? I remember watching several talks while they brought the language up to release, and read several of the accompanying papers. However, I thought with the primary corporate sponsor dropping the language it had gone basically EOL. Which was a pretty large bummer as I was very interested to see how the reference capability model of permissions and control worked at large scale for concurrency control and management (as well as its potential application to other domains).
Dunno, now it feels like the "hot" thing is either manual memory languages like Zig, Odin an Rust or languages with novel type systems like Lean, Koka, Idris, etc... GC'd "systems" languages like Nim, Crystal, Pony, Go, etc... all seem kind of old fashioned now.
Orleans is pretty cool! The project has matured nicely over the years (been something like 10 years?) and they have some research papers attached to it if you like reading up on the details. The nuget stats indicate a healthy amount of downloads too, more than one might expect.
One of the single most important things I've done in my career was going down the Actor Model -framework rabbit hole about 8 or 9 years ago, read a bunch of books on the topic, that contained a ton of hidden philosophy, amazing reasoning, conversations about real-time vs eventual consistency, Two-Generals-Problem - just a ton of enriching stuff, ways to think about data flows, the direction of the flow, immutability, event-logged systems and on and on. At the time CQS/CQRS was making heavy waves and everyone tried to implement DDD & Event-based (and/or service busses - tons of nasty queues...) and Actor Model (and F# for that matter) was such clean fresh breath of air from all the Enterprise complexity.
Would highly recommend going this path for anyone with time on their hands, its time well spent. I still call on that knowledge frequently even when doing OOP.
I was disappointed when MS discontinued Axum, which I found pleasant to use and thought the language based approach was nicer than a library based solution like Orleans.
The Axum language had `domain` types, which could contain one or more `agent` and some state. Agents could have multiple functions and could share domain state, but not access state in other domains directly. The programming model was passing messages between agents over a typed `channel` using directional infix operators, which could also be used to build process pipelines. The channels could contain `schema` types and a state-machine like protocol spec for message ordering.
It didn't have "classes", but Axum files could live in the same projects as regular C# files and call into them. The C# compiler that came with it was modified to introduce an `isolated` keyword for classes, which prevented them from accessing `static` fields, which was key to ensuring state didn't escape the domain.
The software and most of the information was scrubbed from MS own website, but you can find an archived copy of the manual[1]. I still have a copy of the software installer somewhere but I doubt it would work on any recent Windows.
Sadly this project was axed before MS had embraced open source. It would've been nice if they had released the source when the decided to discontinue working on it.
Actor model is one of these things that really seduces me on paper, but my only exposure to it was in my consulting career, and that was to help migrate away from it. The use case seemed particularly adapted (integration of a bunch of remote devices with spotty connection), but it was practically a nightmare to debug... which was a problem since it was buggy.
To be fair, the problem was probably that particular implementation, but I'm wondering if there's any successful rollout of that model at any significant scale out there.
I was in a team that built a bigger telco project for machine to machine communication, using akka actors. It was okayish, the only thing that I hated was how the whole pattern spread through the whole code base
> It captures the nondeterminism in the order of delivery of communications. The Subsequent transition captures fairness arising from the guarantee of delivery. We provide a denotational semantics for our minimal actor language in terms of the transition relations.
Juicy paper, not to mention the declassification. It really reminds me of asyncmachine.dev which has actors, relations, transitions, and embraces non-determinism.
> It is generally believed that the next generation of computers will involve
massively parallel architectures.
To this day - we have only taken advantage of parallel architectures in GPUs - a lot of software still runs on single CPU threads. most programming languages- are made optimized for single threads - yeah we might have threads, virtual threads, fibers etc - but how many people are using those on a daily basis?
I was under the impression that parallel and concurrent code was the dominant paradigm for programming tasks currently going in most of the semi-mainstream domains. I am certainly willing to concede that I could just be in a bubble that thinks about and designs for concurrency and parallelism as a first class concern, but it doesn’t seem that way.
I mean one of the large features/touted benefits for Rust is the single mutable XOR multiple immutable semantics explicitly to assist with problems in parallel/concurrent code, all of the OTP languages are built on top of a ridiculously parallel and distributed first ‘VM’. It strikes me as peculiar that these types of languages and ecosystems would be so, apparently, popular if the primary use case of ‘safe’/resilient parallel/concurrent code was not a large concern.
Please change the title to the original, "Actors: A Model Of Concurrent Computation In Distributed Systems".
I'm not normally a stickler for HN's rule about title preservation, but in this case the "in distributed systems" part is crucial, because IMO the urge to use both the actor model (and its relative, CSP) in non-distributed systems solely in order to achieve concurrency has been a massive boondoggle and a huge dead end. Which is to say, if you're within a single process, what you want is structured concurrency ( https://vorpus.org/blog/notes-on-structured-concurrency-or-g... ), not the unstructured concurrency that is inherent to a distributed system.
I'm working on an rest API server backed by a git repo. Having an actor responsible for all git operations saved me from a lot of trouble as having all git operations serialised freed me from having to prevent concurrent git operations.
Using actors also simplified greatly other parts of the app.
Isn’t that just serializing through a queue, aka. the producer consumer pattern? Which I think the correct solution for most common concurrency problems.
For something to be an actor, it should be able to:
- Send and receive messages
- Create other actors
- Change how the next message is handled (becomes in Erlang)
I think the last one is what makes it different it from simple message passing, and what makes it genius: state machines consuming queues.
1 reply →
So you're just using actors to limit concurrency? Why not use a mutex?
10 replies →
It is endemic to the JVM world that people try various forms of snake oil concurrency inside an address space like actors and the original synchronized model when java.util.concurrent and Executors are "all you need."
It was a theme in part of my career to pick up something written in Scala that used actors that (1) didn't always get the same answer and (2) didn't use all the CPU cores and struggling for days to get it working right with actors then taking 20 minutes to rewrite it using Executors and getting it to work the first time and always work thereafter.
Nurseries sound similar to run-till-completion schedulers [0].
> IMO the urge to use both the actor model (and its relative, CSP) in non-distributed systems solely in order to achieve concurrency has been a massive boondoggle
Can't you model any concurrent non-distributed system as a concurrent distributed system?
0. https://en.wikipedia.org/wiki/Run-to-completion_scheduling
> Can't you model any concurrent non-distributed system as a concurrent distributed system?
Yes, in the same way that you can give up `for` loops and `while` loops and `if` statements and `switch` statements and instead write them all with `goto`, but you don't do this, and anyone advising you to do this would be written off as insane. The entire thrust of this thread is that you can have a more reliable system that is easier to reason about if you use specific constructs that each have less power, and non-distributed systems have the option to do this. Unstructured concurrency should be reserved exclusively for contexts where structured concurrency is impossible, which is what the actor model is for.
6 replies →
You can certainly make any simple problem complicated enough to need a complex solution. But that’s just bad engineering.
I don't know if I'd apply your blanket prescription, but at some level I agree... here's where I see Actors often going wrong and substantially agree with you: state propagation / replication:
I've been on more than one team that has broken their (in-process, single machine) process up into multiple "actors" (or "components" or "services") through communicating threads (usually over Rust channels) and then had a situation where they replicate some piece of state through messaging because they're been told that their system must not have global (mutable or immutable) state.
But now they've just created a whole pile of inefficient boiler plate (propagating copies of effectively the same piece of global state through different services) and created a new way of having race conditions and/or just plain old stale or inconsistent data. For what are essentially ideological reasons.
Every new feature in this model ends up being mostly plumbing of state replication between what are supposed to be isolated component models.
The answer to me is just to establish a discipline where a given piece of data is owned for writes by one task or component, but can be freely read by any.
If you truly have a stateless system or extremely clear data ownership boundaries, I can see the value of a CSP/actor approach. And in the context of Rust's borrow checker this model is fairly convenient. But it quickly becomes prone to cargo-culting and becomes a recipe for hairy, hard to maintain code.
I am convinced most teams blanket applying actors would be far better suited to more tuplespaces/blackboard/Linda type model for concurrent coordination. A way of working that never caught on, but has always been attractive to me.
Note that Rust supports a form of structured programming (though only via thread-based concurrency, not via async), provided by the `std::thread::scope` API: https://doc.rust-lang.org/std/thread/fn.scope.html . The blog post above calls out an older version of this API as inspiration.
Hmm, you think?
I’m currently engineering a system that uses an actor framework to describe graphs of concurrent processing. We’re going to a lot of trouble to set up a system that can inflate a description into a running pipeline, along with nesting subgraphs inside a given node.
It’s all in-process though, so my ears are perking up at your comment. Would you relax your statement for cases where flexibility is important? E.g. we don’t want to write one particular arrangement of concurrent operations, but rather want to create a meta system that lets us string together arbitrary ones. Would you agree that the actor abstraction becomes useful again for such cases?
Data flow graphs could arguably be called structured concurrency (granted, of nodes that resemble actors).
FWIW, this has become a perfectly cromulent pattern over the decades.
It allows highly concurrent computation limited only by the size and shape of the graph while allowing all the payloads to be implemented in simple single-threaded code.
The flow graph pattern can also be extended to become a distributed system by having certain nodes have side-effects to transfer data to other systems running in other contexts. This extension does not need any particularly advanced design changes and most importantly, they are limited to just the "entrance" and "exit" nodes that communicate between contexts.
I am curious to learn more about your system. In particular, what language or mechanism you use for the description of the graph.
2 replies →
> we don’t want to write one particular arrangement of concurrent operations, but rather want to create a meta system that lets us string together arbitrary ones. Would you agree that the actor abstraction becomes useful again for such cases?
Actors are still just too general and uncontrolled, unless you absolutely can't express the thing you want to any other way. Based on your description, have you looked at iterate-style abstractions and/or something like Haskell's Conduit? In my experience those are powerful enough to express anything you want to (including, critically, being able to write a "middle piece of a pipeline" as a reusable value), but still controlled and safe in a way that actor-based systems aren't.
CSP in Golang makes concurrency in it look pleasant compared to the async monstrosities I've seen in C#.
I heartily recommend reading the link the parent comment on structured concurrency. The alternatives here are not merely goroutines vs. async.
[dead]
> both the actor model (and its relative, CSP) in non-distributed systems solely in order to achieve concurrency has been a massive boondoggle and a huge dead end.
Why is that so?
Well, lots of people have tried it and spent a lot of money on it and don't seem to have derived any benefit from doing so.
10 replies →
Eh?
I've written a non-distributed app that uses the Actor model and it's been very successful. It concurrently collects data from hundreds of REST endpoints, a typical run may make 500,000 REST requests, with 250 actors making simultaneous requests - I've tested with 1,000 but that tends to pound the REST servers into the ground. Any failed requests are re-queued. The requests aren't independent, request type C may depend on request types A & B being completed first as it requires data from them, so there's a declarative dependency graph mechanism that does the scheduling.
I started off using Akka but then the license changed and Pekko wasn't a thing yet, so I wrote my own single-process minimalist Actor framework - I only needed message queues, actor pools & supervision to handle scheduling and request failures, so that's all I wrote. It can easily handle 1m messages a second.
I have no idea why that's a "huge dead end", Actors are a model that's a very close fit to my use case, why on earth wouldn't I use it? That "nurseries" link is way TL;DR but it appears to be rubbishing other options in order to promote its particular model. The level of concurrency it provides seems to be very limited and some of it is just plain wrong - "in most concurrency systems, unhandled errors in background tasks are simply discarded". Err, no.
Big Rule 0: No Dogmas: Use The Right Tool For The Job.
> That "nurseries" link is way TL;DR
Please read and understand that blog post, I promise it's worth your time.
1 reply →
Title might have been changed for length.
A more legible version: https://dspace.mit.edu/handle/1721.1/6952
https://en.wikipedia.org/wiki/Gul_Agha_(computer_scientist)
The first link returns a 403.
Both seem to be accessible from Germany. Maybe a geoblock?
May be of interest: Pony Language is designed from the ground up to support the Actor model.
https://www.ponylang.io/
Is Pony still an actively developed language? I remember watching several talks while they brought the language up to release, and read several of the accompanying papers. However, I thought with the primary corporate sponsor dropping the language it had gone basically EOL. Which was a pretty large bummer as I was very interested to see how the reference capability model of permissions and control worked at large scale for concurrency control and management (as well as its potential application to other domains).
It is still developed, although it feels a bit like it was mostly "done" 10 years ago.
https://github.com/ponylang/ponyc
Quite a few recent commits and their blog/X account mentioned their LSP now being bundled with the language...
https://www.ponylang.io/blog/2026/02/last-week-in-pony---feb...
Dunno, now it feels like the "hot" thing is either manual memory languages like Zig, Odin an Rust or languages with novel type systems like Lean, Koka, Idris, etc... GC'd "systems" languages like Nim, Crystal, Pony, Go, etc... all seem kind of old fashioned now.
1 reply →
D language is also supporting actor model in the standard library for its concurrent, parallel and distributed programming [1].
[1] D (programming language):
https://en.wikipedia.org/wiki/D_(programming_language)
Mandatory mention of notable actor languages:
I think Microsoft Orleans, Erlang OTP and Scala Play are probably most famous examples in use today.
Orleans is pretty cool! The project has matured nicely over the years (been something like 10 years?) and they have some research papers attached to it if you like reading up on the details. The nuget stats indicate a healthy amount of downloads too, more than one might expect.
One of the single most important things I've done in my career was going down the Actor Model -framework rabbit hole about 8 or 9 years ago, read a bunch of books on the topic, that contained a ton of hidden philosophy, amazing reasoning, conversations about real-time vs eventual consistency, Two-Generals-Problem - just a ton of enriching stuff, ways to think about data flows, the direction of the flow, immutability, event-logged systems and on and on. At the time CQS/CQRS was making heavy waves and everyone tried to implement DDD & Event-based (and/or service busses - tons of nasty queues...) and Actor Model (and F# for that matter) was such clean fresh breath of air from all the Enterprise complexity.
Would highly recommend going this path for anyone with time on their hands, its time well spent. I still call on that knowledge frequently even when doing OOP.
Do any of the books you read on the topic stand out as something you'd recommend?
3 replies →
I was disappointed when MS discontinued Axum, which I found pleasant to use and thought the language based approach was nicer than a library based solution like Orleans.
The Axum language had `domain` types, which could contain one or more `agent` and some state. Agents could have multiple functions and could share domain state, but not access state in other domains directly. The programming model was passing messages between agents over a typed `channel` using directional infix operators, which could also be used to build process pipelines. The channels could contain `schema` types and a state-machine like protocol spec for message ordering.
It didn't have "classes", but Axum files could live in the same projects as regular C# files and call into them. The C# compiler that came with it was modified to introduce an `isolated` keyword for classes, which prevented them from accessing `static` fields, which was key to ensuring state didn't escape the domain.
The software and most of the information was scrubbed from MS own website, but you can find an archived copy of the manual[1]. I still have a copy of the software installer somewhere but I doubt it would work on any recent Windows.
Sadly this project was axed before MS had embraced open source. It would've been nice if they had released the source when the decided to discontinue working on it.
[1]:https://web.archive.org/web/20110629202213/http://download.m...
I would think Akka in Java world is more famous than orleans
Akka's not open source anymore so people tend to look at similar or competing systems like Scala Play.
1 reply →
That's probably what they meant by "Scala Play".
Actor model is one of these things that really seduces me on paper, but my only exposure to it was in my consulting career, and that was to help migrate away from it. The use case seemed particularly adapted (integration of a bunch of remote devices with spotty connection), but it was practically a nightmare to debug... which was a problem since it was buggy.
To be fair, the problem was probably that particular implementation, but I'm wondering if there's any successful rollout of that model at any significant scale out there.
I was in a team that built a bigger telco project for machine to machine communication, using akka actors. It was okayish, the only thing that I hated was how the whole pattern spread through the whole code base
> It captures the nondeterminism in the order of delivery of communications. The Subsequent transition captures fairness arising from the guarantee of delivery. We provide a denotational semantics for our minimal actor language in terms of the transition relations.
Juicy paper, not to mention the declassification. It really reminds me of asyncmachine.dev which has actors, relations, transitions, and embraces non-determinism.
> It is generally believed that the next generation of computers will involve massively parallel architectures.
To this day - we have only taken advantage of parallel architectures in GPUs - a lot of software still runs on single CPU threads. most programming languages- are made optimized for single threads - yeah we might have threads, virtual threads, fibers etc - but how many people are using those on a daily basis?
I will be glad to be wrong about, but…
I was under the impression that parallel and concurrent code was the dominant paradigm for programming tasks currently going in most of the semi-mainstream domains. I am certainly willing to concede that I could just be in a bubble that thinks about and designs for concurrency and parallelism as a first class concern, but it doesn’t seem that way.
I mean one of the large features/touted benefits for Rust is the single mutable XOR multiple immutable semantics explicitly to assist with problems in parallel/concurrent code, all of the OTP languages are built on top of a ridiculously parallel and distributed first ‘VM’. It strikes me as peculiar that these types of languages and ecosystems would be so, apparently, popular if the primary use case of ‘safe’/resilient parallel/concurrent code was not a large concern.
It doesn't feel 1985. Feels very 2015. Really good insights. Remembering the hardware they had back then too, and ~14 years before Google took off.
Missing: (1985)