Current section
Files
Jump to
Current section
Files
src/lightspeed/ops/supervision.gleam
//// Supervision tree guidance for production Lightspeed deployments.
import gleam/list
/// Restart behavior for a child process.
pub type Restart {
Permanent
Transient
Temporary
}
/// Supervision strategy.
pub type Strategy {
OneForOne
RestForOne
OneForAll
}
/// Child process guidance entry.
pub type ChildSpec {
ChildSpec(id: String, module_name: String, restart: Restart, shutdown_ms: Int)
}
/// Top-level supervision plan guidance.
pub type Plan {
Plan(name: String, strategy: Strategy, children: List(ChildSpec))
}
/// Plan validation errors.
pub type PlanError {
MissingRequiredChild(String)
DuplicateChildId(String)
}
/// Build a default plan with recommended Phase 7 children.
pub fn default_plan(app_name: String) -> Plan {
Plan(name: app_name <> "_supervisor", strategy: OneForOne, children: [
ChildSpec(
id: "session_registry",
module_name: "lightspeed.session_registry",
restart: Permanent,
shutdown_ms: 5000,
),
ChildSpec(
id: "session_supervisor",
module_name: "lightspeed.session_supervisor",
restart: Permanent,
shutdown_ms: 5000,
),
ChildSpec(
id: "transport_endpoint",
module_name: "lightspeed.transport_endpoint",
restart: Permanent,
shutdown_ms: 5000,
),
ChildSpec(
id: "metrics_reporter",
module_name: "lightspeed.metrics_reporter",
restart: Transient,
shutdown_ms: 3000,
),
])
}
/// Validate required child coverage and duplicate ids.
pub fn validate(plan: Plan) -> Result(Nil, PlanError) {
case duplicate_id(plan.children, []) {
Ok(Nil) ->
case
has_required(plan.children, "session_registry")
&& has_required(plan.children, "session_supervisor")
&& has_required(plan.children, "transport_endpoint")
&& has_required(plan.children, "metrics_reporter")
{
True -> Ok(Nil)
False -> missing_required(plan.children)
}
Error(error) -> Error(error)
}
}
/// Stable plan label.
pub fn strategy_label(strategy: Strategy) -> String {
case strategy {
OneForOne -> "one_for_one"
RestForOne -> "rest_for_one"
OneForAll -> "one_for_all"
}
}
/// Stable restart label.
pub fn restart_label(restart: Restart) -> String {
case restart {
Permanent -> "permanent"
Transient -> "transient"
Temporary -> "temporary"
}
}
/// Child ids in declaration order.
pub fn child_ids(plan: Plan) -> List(String) {
child_ids_loop(plan.children, [])
}
fn child_ids_loop(
children: List(ChildSpec),
ids_rev: List(String),
) -> List(String) {
case children {
[] -> list.reverse(ids_rev)
[child, ..rest] -> child_ids_loop(rest, [child.id, ..ids_rev])
}
}
fn duplicate_id(
children: List(ChildSpec),
seen_ids: List(String),
) -> Result(Nil, PlanError) {
case children {
[] -> Ok(Nil)
[child, ..rest] ->
case contains_id(seen_ids, child.id) {
True -> Error(DuplicateChildId(child.id))
False -> duplicate_id(rest, [child.id, ..seen_ids])
}
}
}
fn contains_id(ids: List(String), target: String) -> Bool {
case ids {
[] -> False
[id, ..rest] ->
case id == target {
True -> True
False -> contains_id(rest, target)
}
}
}
fn has_required(children: List(ChildSpec), required_id: String) -> Bool {
case children {
[] -> False
[child, ..rest] ->
case child.id == required_id {
True -> True
False -> has_required(rest, required_id)
}
}
}
fn missing_required(children: List(ChildSpec)) -> Result(Nil, PlanError) {
case has_required(children, "session_registry") {
False -> Error(MissingRequiredChild("session_registry"))
True ->
case has_required(children, "session_supervisor") {
False -> Error(MissingRequiredChild("session_supervisor"))
True ->
case has_required(children, "transport_endpoint") {
False -> Error(MissingRequiredChild("transport_endpoint"))
True ->
case has_required(children, "metrics_reporter") {
False -> Error(MissingRequiredChild("metrics_reporter"))
True -> Ok(Nil)
}
}
}
}
}