Building Fault-Tolerant Actors with gleam/otp
Learn how to implement type-safe concurrent actors in Gleam using gleam/otp, including state management, supervision strategies, and BEAM VM integration.
15 Oct 2026, 21:33 UTC

Managing State and Concurrency with Type-Safe Actors
The primary challenge in concurrent systems is preventing state corruption and managing process failures without crashing the entire application. In Gleam, the gleam/otp library solves this by providing a type-safe wrapper around Erlang's gen_server. The key takeaway is that gleam/otp transforms the traditionally dynamic Erlang actor model into a statically verified system where the compiler ensures every possible message sent to a process is handled.
The Actor Mechanism
An actor in Gleam is a process that maintains a private state and communicates via asynchronous casts or synchronous calls. Unlike raw Erlang processes, a Gleam actor defines its message types using a custom type, forcing the developer to handle every case in the message loop. This eliminates the common "unexpected message" crashes found in BEAM applications.
Implementation Example: A Concurrent Counter
To implement an actor, you must define the state, the messages it accepts, and the handler functions. This example demonstrates a simple counter that supports incrementing and retrieving the current value.
import gleam/otp/actor
import gleam/erlang/process
// 1. Define the message types the actor will handle
pub type CounterMsg {
Increment
GetCount(process.Subject(Int))
}
// 2. Define the initial state
fn init(_) -> actor.Actor(Int, CounterMsg) {
actor.Actor(
initial_state: 0,
loop: handle_message,
)
}
// 3. Implement the message handler
fn handle_message(msg: CounterMsg, state: Int) -> actor.Next( actor.Actor(Int, CounterMsg), Int) {
case msg {
Increment -> {
actor.continue(state + 1)
}
GetCount(client) -> {
process.send(client, state)
actor.continue(state)
}
}
}
pub fn main() {
// Start the actor and get its Subject (address)
let assert Ok(subject) = actor.start(init, None)
// Send an increment message
process.send(subject, Increment)
// Request the current count
let reply_subject = process.new_subject()
process.send(subject, GetCount(reply_subject))
let count = process.receive(reply_subject, 100)
// Result: Some(1)
}
Integrating with Supervision Trees
Actors should rarely be started in isolation. To achieve fault tolerance, actors are placed under a Supervisor. A supervisor monitors its children and restarts them based on a predefined strategy if they crash. In gleam/otp, this is configured via child specifications.
| Strategy | Behavior | Use Case |
|---|---|---|
:one_for_one |
Only the crashed process is restarted. | Independent workers. |
:one_for_all |
All children are restarted if one crashes. | Tightly coupled dependencies. |
:rest_for_one |
Crashed process and all processes started after it are restarted. | Linear dependency chains. |
Limitations and Engineering Trade-offs
- Static Supervision: Supervision trees are defined at compile time. If your application requires adding children dynamically based on runtime configuration, you must use the
gleam/erlangFFI to callsupervisor:start_childdirectly. - State Migration: Gleam does not automatically handle state migration during hot code reloading. If you update the state type of a running actor, you must manually implement the
code_changelogic via Erlang interoperability. - FFI Boundaries: While Gleam ensures type safety within its own processes, messages coming from external Erlang processes are untyped. You must use
gleam/erlangboundary types to validate data entering your Gleam actor from the BEAM VM.
Verification and Diagnostics
To verify that your actor is running and correctly supervised, use the following steps:
- Run your application using
gleam run. - Open an Erlang shell (
erl) and connect to the running node. - Run
observer:start()to open the Erlang Observer GUI. This allows you to visually inspect the process tree, check the memory usage of your actors, and verify that the supervisor is correctly monitoring the children. - To test fault tolerance, manually kill a child process via the observer and verify that the supervisor restarts it immediately.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.