From 2940b80563dc86c935542bb04acd32a4c0608e3a Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 12 Dec 2021 23:03:22 +0100 Subject: [PATCH] Supervision --- packages/core/src/index.ts | 1 + packages/core/src/runtime/supervise.ts | 77 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 packages/core/src/runtime/supervise.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a2aa724..5810b0f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,6 +16,7 @@ export * as QuasiValue from './runtime/quasivalue.js'; export * from './runtime/randomid.js'; export * as Rewrite from './runtime/rewrite.js'; export * as Skeleton from './runtime/skeleton.js'; +export * from './runtime/supervise.js'; export * as Task from './runtime/task.js'; export * as Cryptography from './transport/cryptography.js'; diff --git a/packages/core/src/runtime/supervise.ts b/packages/core/src/runtime/supervise.ts new file mode 100644 index 0000000..95edcf0 --- /dev/null +++ b/packages/core/src/runtime/supervise.ts @@ -0,0 +1,77 @@ +/// SPDX-License-Identifier: GPL-3.0-or-later +/// SPDX-FileCopyrightText: Copyright © 2021 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()); + } + }); + }); + } +}