Supervisor

The Supervisor module provides supervision trees for managing actor lifecycles. Supervisors monitor child actors and can restart them when they stop or terminate, implementing fault-tolerant patterns inspired by Erlang/OTP.

Concurrency Capability

Supervisor operations that create, restart, or monitor actors require ActorAuth, ConcurrencyAuth, or RootAuth in scope. Derive these tokens through Auth.Concurrency and pass them into supervisor-facing APIs.

Supervision Trees

Supervisors form a hierarchy where each supervisor manages its children. When a child exits, the supervisor's restart strategy determines how to recover. Use Supervisor.stop-child when you want to remove a child without restarting it.

Restart Strategies

Supervisors support three restart strategies that determine what happens when a child exits:

:isolate
Only restart the failed child. Other children are unaffected. This is the default and most common strategy.
:all
Restart all children when one fails. Useful when children have interdependent state.
:cascade
Restart the failed child and all children started after it. Useful for dependencies where later children depend on earlier ones.

Child Specifications

Each child is specified as a record with the following fields:

# Child specification record
{
  handler: fn(msg, state) => new-state,  # Message handler function
  state: initial-state,                   # Initial state for the actor
  name: "worker-1",                      # Optional: registered actor name
  max-restarts: 3,                      # Optional: restarts allowed in the window
  restart-window-ms: 5000               # Optional: restart window in milliseconds
}

Named children are registered with the actor registry and can be found with Actor.lookup. When a named child is restarted, the registration is refreshed to point at the new actor ID. Restart limits are tracked per child; if a child exceeds max-restarts inside restart-window-ms, it is removed from the supervisor instead of being restarted again.

Supervisor API

Supervisor.start
Keyword -> List ChildSpec -> Int
Starts a supervisor with the given restart strategy and child specifications. Returns the supervisor ID.
handler = fn(-msg, state) => state + 1

children = [
  {handler: handler, state: 0, name: "worker-1"},
  {handler: handler, state: 0, name: "worker-2"}
]

sup = Supervisor.start :isolate children
Supervisor.stop
Int -> Unit
Stops the supervisor and all its child actors.
Supervisor.stop sup
Supervisor.spawn-child
Int -> ChildSpec -> Int
Dynamically adds a supervised child to a running supervisor. Returns the new actor ID, or a negative value if the child could not be started.
worker = Supervisor.spawn-child sup {
  handler: handler,
  state: 0,
  name: "dynamic-worker"
}
Supervisor.stop-child
Int -> Int -> Bool
Stops and removes a supervised child. Returns true when the child was managed by the supervisor and removed. Removed children are not restarted.
removed? = Supervisor.stop-child sup worker
Supervisor.children
Int -> List Int
Returns a list of all child actor IDs managed by the supervisor.
child-ids = Supervisor.children sup
println "Children: ${child-ids}"
Supervisor.child-count
Int -> Int
Returns the number of children managed by the supervisor.
Supervisor.is-running?
Int -> Bool
Returns true if the supervisor is currently running.

Error Types

type SupervisorError =
  | InvalidStrategy {message: NonEmptyString}
  | InvalidChildSpec {message: NonEmptyString}
  | StartFailed {name: NonEmptyString, message: NonEmptyString}
  | NotRunning
  | SupervisorError {message: NonEmptyString}

Complete Example

# Define a worker handler
worker-handler = fn(msg, state) =>
  match msg
    | :work -> state + 1
    | :reset -> 0
    | _ -> state

# Create child specifications
children = [
  {handler: worker-handler, state: 0, name: "worker-1"},
  {handler: worker-handler, state: 0, name: "worker-2"},
  {handler: worker-handler, state: 0, name: "worker-3"}
]

# Start supervisor with isolate strategy
sup = Supervisor.start :isolate children

# Get child actors and send them work
workers = Supervisor.children sup
workers |> List.each (fn(w) => w <- :work)

# Check status
println "Supervisor running: ${Supervisor.is-running? sup}"
println "Child count: ${Supervisor.child-count sup}"

# Add and remove a child dynamically
dynamic = Supervisor.spawn-child sup {
  handler: worker-handler,
  state: 0,
  name: "dynamic-worker",
  max-restarts: 2,
  restart-window-ms: 1000
}
Supervisor.stop-child sup dynamic

# Cleanup
Supervisor.stop sup