/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2021-2023 Tony Garnock-Jones import { State } from '../gen/service'; import { Actor, AnyValue, LocalAction, Turn } from './actor'; import { Field } from './dataflow'; type StateName = State['_variant']; export enum SupervisorRestartPolicy { ALWAYS, ON_ERROR_ONLY, } export type SupervisorConfiguration = { intensity: number, period: number, /* seconds */ pauseTime: number, /* seconds */ sleepTime: number, /* seconds */ restartPolicy: SupervisorRestartPolicy, }; export const DEFAULT_CONFIG: SupervisorConfiguration = { intensity: 1, period: 5, pauseTime: 0.2, sleepTime: 10, restartPolicy: SupervisorRestartPolicy.ON_ERROR_ONLY, }; export class Supervisor { readonly config: SupervisorConfiguration; readonly nameFunction: () => AnyValue; readonly bootFunction: LocalAction; readonly restarts: number[] = []; /* timestamps */ readonly state: Field = Turn.active.field('started'); supervisee: Actor | null = null; constructor(config: Partial, nameFunction: () => AnyValue, bootFunction: LocalAction) { this.config = Object.assign({}, DEFAULT_CONFIG, config); this.nameFunction = nameFunction; this.bootFunction = bootFunction; this.startSupervisee(); } startSupervisee() { Turn.active.facet(() => { this.state.value = 'started'; this.supervisee = Turn.active.spawnLink(this.bootFunction); if (this.supervisee) this.supervisee.name = this.nameFunction(); Turn.activeFacet.onStop(() => { const exitReason = this.supervisee?.exitReason; if (!exitReason) { throw new Error("Expected supervisee to have terminated"); } if (exitReason.ok && (this.config.restartPolicy === SupervisorRestartPolicy.ON_ERROR_ONLY)) { this.state.value = 'complete'; } else { this.state.value = exitReason.ok ? 'complete' : 'failed'; const now = +(new Date()); const oldestToKeep = now - this.config.period * 1000.0; this.restarts.push(now); while (this.restarts.length && (this.restarts[0] < oldestToKeep)) { this.restarts.shift(); } const waitTime = (this.restarts.length > this.config.intensity) ? this.config.sleepTime : this.config.pauseTime; Turn.active.after(waitTime * 1000.0, () => this.startSupervisee()); } }); }); } }