singularity

Singularity is a registry for shared singleton actors, in Gleam.

When an actor’s process exits, it will be removed from the registry automatically. Fetching an actor using the require function will block until that actor has been registered. These two properties allow OTP supervisors to start your actors only when their dependencies are ready.

Example Usage

import actor_a
import actor_b

// Define a wrapper type for the actors (Subjects) you want register.
type Actors {
  ActorA(Subject(actor_a.Message))
  ActorB(Subject(actor_b.Message))
}

pub fn main() {
  let assert Ok(registry) = singularity.start()
  let assert Ok(actor_a_subj) = actor_a.start()
  let assert Ok(actor_b_subj) = actor_b.start()

  // Register the actors specifying your wrapper (`Actors`) variant.
  singularity.register(registry, ActorA, actor_a_subj)
  singularity.register(registry, ActorB, actor_b_subj)

  // Retrieve registered actors, each will be returned via your
  // wrapper type.
  let assert ActorA(got_a) =
    singularity.require(registry, ActorA, timeout_ms: 1000)
  let assert ActorB(got_b) =
    singularity.require(registry, ActorB, timeout_ms: 1000)
}

The example above is for illustrative purposes only; it will not compile. Please see the README and tests for concrete examples.

Types

A DelayFunc accepts two parameters:

  • The uptime of the previous incarnation of this actor, in milliseconds.
  • The previous delay in milliseconds, starting with 0.
pub type DelayFunc =
  fn(Int, Int) -> Int

Singularity’s message/Subject type.

pub opaque type Message(wrap)

Functions

pub fn always_delay(ms delay: Int) -> fn(Int, Int) -> Int

Ignores inputs, always delays the same amount of time.

pub fn exponential_delay(
  good_ms good: Int,
  initial_ms initial: Int,
  max_ms max: Option(Int),
) -> fn(Int, Int) -> Int

Starts at initial_ms, doubling with each subsequent call. If max_ms is provided, delay will not exceed that value. If the previous incarnation lived for at least good_ms, the delay will be reset to initial_ms.

pub fn register(
  in actor: Subject(Message(a)),
  key variant: fn(Subject(b)) -> a,
  subject subj: Subject(b),
) -> Subject(b)

Registers an actor, using the actors wrapper type constructor as a key.

Registered actor processes will be monitored. Processes that exit will be removed from the registry automatically.

Returns the subject unchanged, for ease of use in pipelines.

pub fn require(
  in actor: Subject(Message(a)),
  key variant: fn(Subject(b)) -> a,
  timeout_ms timeout: Int,
) -> a

Retrieves an actor, using the actors wrapper type constructor as a key. If the actor is not present in the registry, the request will be retried every 100ms for a total of timeout_ms before crashing.

If your actors need to establish network connections during startup, consider using long (5000ms+) timeouts for them and any actors that depend on them.

pub fn restart_delay(
  in actor: Subject(Message(a)),
  key variant: fn(Subject(b)) -> a,
  with func: fn(Int, Int) -> Int,
) -> Nil

Tracks and delays restarts for the actor key using the provided DelayFunc.

See always_delay and exponential_delay for built-in delay functions, or write your own for custom timings.

pub fn start() -> Result(Subject(Message(a)), StartError)

Starts the registry.

Note: Gleam actors are linked to the parent process by default.

pub fn stop(actor: Subject(Message(a))) -> Nil

Shuts down the registry. Does not affect registered actors.

pub fn try_get(
  in actor: Subject(Message(a)),
  key variant: fn(Subject(b)) -> a,
) -> Result(a, Nil)

Retrieves an actor, using the actors wrapper type constructor as a key. If the actor is not present in the registry, returns None.

Search Document