43 years of the actor model

The actor model of computation was first proposed in the 70's as a nicer way to deal with concurrency. Nowadays it's built into our everyday tools and we don't think about it as much. Understanding how it works helps you write less bugs.

43 years of actors: A taxonomy of actor models and their key properties is a 2016 paper by Koster et al that provides a readable overview of the actor model and why it's good. You can read the paper with my annotations, here.

The paper identifies common concepts, classifies implementations into 4 types, and defines the Isolated Turn Principle as a key component. The authors stress that specifics are implementation dependent.

Meaning that the actor model of computation is more what you'd call guidelines than actual rules.

The basic idea

The basic idea of an actor model is that you have actors doing work based on a combination of internal state and commands/messages/events from outside. The actors do not directly share state. This makes them concurrency safe.

Common concepts

Your tools will have different names for these concepts, but they all use the same conceptual building blocks.

Your React components are actors that communicate by passing events through a shared event mechanism. In a distributed backend system this maps nicely to message queues and lambda functions or servers that process events.

4 types of implementations

The paper classifies implementations into 4 types and finds no difference in the power of these different implementations as long as the Isolated Turn Principle is observed. Use whatever works for you.

  1. The classic actor model uses create, send, and become primitives to implement the model in a functional paradigm. To change state, actors become a new actor with different behavior.
  2. The active objects approach uses objects that hold internal state and use a single entry point to process events
  3. The processes approach leverages processes to process an event from start to finish based on a single entry point. The process is killed when its turn finishes.
  4. The communicating event-loops approach uses promises to wait for processing to be ready. It's the only paradigm that supports multiple entry/exit points for an actor.

You see active objects a lot in object oriented languages, I don't have much direct experience.

Processes are common in distributed backend systems where a new Lambda boots up to process each event on a queue. Apache of ye olde LAMP stack uses this approach to start a new PHP process for every HTTP request.

A note on why event-loops are good

Event-loops are the core primitive in JavaScript. Every async function is an actor. This makes JavaScript uniquely well-suited for IO-bound code because the single-threaded promise-based approach makes it easy to coordinate lots of waiting around for input/output.

But event-loops are bad for compute-bound code because of their single-threaded nature. If you lock up the CPU with your actor (function), it blocks all other in-progress actors from proceeding.

The paper does not talk about this and doesn't mention JavaScript. I assume for two reasons:

The Isolated Turn Principle

The isolated turn principle is key to making the actor model useful. Best when your language can guarantee this, but you can use it as a set of principles for your own code. Follow these rules and you'll rarely have bugs due to concurrency.

A single turn must be processed as 1 isolated step

To satisfy this rule you need:

  1. Safety meaning that an actor's state is fully isolated and cannot be modified by another actor or another instance of the same actor during processing
  2. Liveness meaning that an actor must process a message start to finish without blocking operations

In JavaScript terms this translates to keeping your functions idempotent and avoiding unmanaged mutable state.

Cheers,
~Swizec