🎭 Actor Model

Erlang-style message passing concurrency with supervision trees

Overview

The Actor model is a concurrency paradigm where independent actors communicate exclusively through asynchronous message passing — no shared mutable state. Each actor has a private mailbox, processes messages sequentially, and can spawn child actors, creating supervision hierarchies for fault tolerance.

This implementation is inspired by Erlang/OTP and Akka, featuring typed mailboxes, supervision trees, behavior switching, routers, and a dead letter handler. It includes 38 built-in tests.

Concepts Demonstrated

How It Works

(* Actor references for addressing *)
type actor_ref = {
  id: actor_id;
  name: string option;
}

(* Messages wrapped in envelopes *)
type 'msg envelope = {
  from: actor_ref option;
  to_ref: actor_ref;
  payload: 'msg;
  timestamp: float;
  correlation_id: string option;
}

(* Supervision strategies *)
type restart_strategy =
  | OneForOne    (* restart only the failed child *)
  | OneForAll    (* restart all children *)
  | RestForOne   (* restart failed child + all children started after it *)

(* Define an actor's behavior *)
let counter_behavior ctx msg =
  match msg with
  | Increment -> ctx.state := !(ctx.state) + 1
  | GetCount reply_to ->
      send reply_to (Count !(ctx.state))

(* Spawn and communicate *)
let counter = spawn system "counter" counter_behavior
send counter Increment
send counter Increment
let count = ask counter (GetCount self)  (* → Count 2 *)

Key Components

ComponentDescription
ActorSystemTop-level container that manages all actors, message routing, and dead letters
ActorUnit of computation with a mailbox, behavior function, and optional parent
SupervisorSpecial actor that monitors children and applies a restart strategy on failure
RouterDistributes messages across a pool of actors (round-robin or broadcast)
RegistryNamed actor lookup — find actors by name instead of reference
DeadLetterHandlerCaptures messages sent to stopped or non-existent actors

Supervision Strategies

Design Philosophy: "Let It Crash"

Rather than defensive error handling in every function, actors delegate failure recovery to supervisors. If an actor encounters an unexpected error, it crashes. Its supervisor detects the failure and decides whether to restart it, escalate, or stop. This produces cleaner application code and more robust systems.

Running

# Compile and run with tests
ocamlfind ocamlopt -package unix -linkpkg actor.ml -o actor && ./actor

Further Reading