Supervision

This commit is contained in:
Tony Garnock-Jones 2021-12-12 23:03:22 +01:00
parent 04bbcd25ab
commit 2940b80563
2 changed files with 78 additions and 0 deletions

View File

@ -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';

View File

@ -0,0 +1,77 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
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<StateName> = Turn.active.field('started');
supervisee: Actor | null = null;
constructor(config: Partial<SupervisorConfiguration>,
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());
}
});
});
}
}