Gleam OTP Supervisors

A supervisor watches over child processes and restarts them when they crash. Supervisors are the backbone of fault-tolerant systems on the BEAM. Instead of letting one crash bring down the whole application, supervisors contain failures and recover automatically.

The Supervisor Philosophy


"Let it crash" + Supervisor = Resilient System
──────────────────────────────────────────────────
Without supervisor:
  Worker crashes → whole app crashes

With supervisor:
  Worker crashes → supervisor restarts it → app keeps running

Erlang systems achieve high uptime not by preventing all crashes, but by recovering from them automatically. A supervisor makes that recovery happen without human intervention.

Adding the Dependency

gleam add gleam_otp

Supervisor Tree Concept


Application Supervisor Tree
──────────────────────────────────────────────────
         App Supervisor
         /      |      \
    WebSup   CacheSup  DbSup
    /    \       |       |
Router Worker  Cache   Pool

Supervisors form a tree. Each node supervises its children. A crash at any leaf is contained and restarted without affecting siblings or the parent supervisor.

Starting a Supervisor

import gleam/otp/supervisor
import gleam/otp/actor

// Define a child worker
fn start_logger() {
  actor.start("Logger ready", fn(msg, state) {
    io.println("LOG: " <> msg)
    actor.continue(state)
  })
}

// Build the supervisor
pub fn start_app() {
  supervisor.start(fn(children) {
    children
    |> supervisor.add(supervisor.worker(start_logger))
  })
}

Restart Strategies


Child Restart Options
──────────────────────────────────────────────────
supervisor.worker(start_fn)
  → Restarts when the child crashes abnormally
  → Does NOT restart on normal exit
  → Default choice for stateless workers

supervisor.worker_with_restart(start_fn, restart)
  → restart = supervisor.Permanent  (always restart)
  → restart = supervisor.Transient  (restart on crash)
  → restart = supervisor.Temporary  (never restart)

Supervisor Restart Policies


Supervisor-Level Policies
──────────────────────────────────────────────────
one_for_one (default)
  → Only the crashed child restarts
  → Other children continue running
  → Best for independent workers

one_for_all
  → If one child crashes, ALL children restart
  → Use when children depend on each other

rest_for_one
  → If child N crashes, N and all children
    after N restart in order
  → Use for ordered dependency chains

Multiple Children

import gleam/otp/supervisor

pub fn start_services() {
  supervisor.start(fn(children) {
    children
    |> supervisor.add(supervisor.worker(start_cache))
    |> supervisor.add(supervisor.worker(start_api))
    |> supervisor.add(supervisor.worker(start_scheduler))
  })
}

Service Supervisor Tree
──────────────────────────────────────────────────
         services_supervisor
          /       |        \
       Cache     API     Scheduler
        │         │          │
     actor     actor       actor

If API crashes → supervisor restarts API only (one_for_one)
Cache and Scheduler keep running.

Nested Supervisors

Supervisors can supervise other supervisors, building layered fault isolation:

pub fn start_root() {
  supervisor.start(fn(children) {
    children
    |> supervisor.add(supervisor.supervisor(start_web_layer))
    |> supervisor.add(supervisor.supervisor(start_data_layer))
  })
}

fn start_web_layer() {
  supervisor.start(fn(children) {
    children
    |> supervisor.add(supervisor.worker(start_router))
    |> supervisor.add(supervisor.worker(start_static_server))
  })
}

fn start_data_layer() {
  supervisor.start(fn(children) {
    children
    |> supervisor.add(supervisor.worker(start_db_pool))
    |> supervisor.add(supervisor.worker(start_cache))
  })
}

Nested Supervisor Tree
──────────────────────────────────────────────────
          Root Supervisor
         /               \
    Web Supervisor    Data Supervisor
    /          \       /          \
 Router  Static  DB Pool        Cache

A crash in DB Pool only restarts DB Pool.
Web layer is completely unaffected.

Maximum Restart Intensity

If a child crashes too many times in a short period, the supervisor itself stops. This prevents an infinite crash-restart loop from consuming all resources:


Default intensity: 1 crash per 5 seconds
If exceeded: supervisor stops and reports to its own supervisor

This propagates up the tree until a supervisor handles it
or the root supervisor shuts down the app.

Practical Example — Chat Server Supervision

import gleam/otp/supervisor

pub fn start_chat_server() {
  supervisor.start(fn(children) {
    children
    // Message broker — restart on crash
    |> supervisor.add(supervisor.worker(start_message_broker))
    // Session manager — restart on crash
    |> supervisor.add(supervisor.worker(start_session_manager))
    // Presence tracker — restart on crash
    |> supervisor.add(supervisor.worker(start_presence_tracker))
    // HTTP listener — restart on crash
    |> supervisor.add(supervisor.worker(start_http_listener))
  })
}

pub fn main() {
  case start_chat_server() {
    Ok(_supervisor) -> {
      io.println("Chat server started")
      process.sleep_forever()   // keep main alive
    }
    Error(reason) -> {
      io.println("Failed to start: " <> debug_reason(reason))
    }
  }
}

Chat Server Recovery Scenario
──────────────────────────────────────────────────
t=0:    All 4 services running
t=120:  presence_tracker crashes (memory issue)
t=120:  supervisor detects crash
t=121:  supervisor restarts presence_tracker
t=122:  presence_tracker running again

Users connected to message_broker and session_manager
experienced zero interruption.

Key Points


Supervisor Essentials
──────────────────────────────────────────────────
1. supervisor.start(fn(children) { ... })
2. supervisor.add(children, supervisor.worker(start_fn))
3. Default strategy: one_for_one (only crashed child restarts)
4. Supervisors form trees — crashes are contained locally
5. Maximum intensity prevents infinite restart loops
6. Nested supervisors give layered fault isolation
7. "Let it crash" + supervisors = high-availability systems

Supervisors transform individual processes into resilient systems. You write simple workers that do one thing well and crash loudly when something goes wrong. Supervisors handle the recovery. This clean separation — workers focus on logic, supervisors handle failures — is the secret behind Erlang's legendary reliability record.

Leave a Comment

Your email address will not be published. Required fields are marked *