Fix StartOS 0.4 TypeScript packaging to match SDK API
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
import * as T from '../../../base/lib/types';
|
||||
import { SubContainer } from '../util/SubContainer';
|
||||
import { Drop } from '../util';
|
||||
import { DaemonCommandType } from './Daemons';
|
||||
/**
|
||||
* Low-level controller for a single running process inside a subcontainer (or as a JS function).
|
||||
*
|
||||
* Manages the child process lifecycle: spawning, waiting, and signal-based termination.
|
||||
* Used internally by {@link Daemon} to manage individual command executions.
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type
|
||||
* @typeParam C - The subcontainer type, or `null` for JS-only commands
|
||||
*/
|
||||
export declare class CommandController<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null> extends Drop {
|
||||
readonly runningAnswer: Promise<null>;
|
||||
private state;
|
||||
private readonly subcontainer;
|
||||
private process;
|
||||
readonly sigtermTimeout: number;
|
||||
private constructor();
|
||||
/**
|
||||
* Factory method to create a new CommandController.
|
||||
*
|
||||
* Returns a curried async function: `(effects, subcontainer, exec) => CommandController`.
|
||||
* If the exec spec has an `fn` property, runs the function; otherwise spawns a shell command
|
||||
* in the subcontainer.
|
||||
*/
|
||||
static of<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null>(): (effects: T.Effects, subcontainer: C, exec: DaemonCommandType<Manifest, C>) => Promise<CommandController<Manifest, C>>;
|
||||
/**
|
||||
* Wait for the command to finish. Optionally terminate after a timeout.
|
||||
* @param options.timeout - Milliseconds to wait before terminating. Defaults to no timeout.
|
||||
*/
|
||||
wait({ timeout }?: {
|
||||
timeout?: number | undefined;
|
||||
}): Promise<void>;
|
||||
/**
|
||||
* Terminate the running command by sending a signal.
|
||||
*
|
||||
* Sends the specified signal (default: SIGTERM), then escalates to SIGKILL
|
||||
* after the timeout expires. Destroys the subcontainer after the process exits.
|
||||
*
|
||||
* @param options.signal - The signal to send (default: SIGTERM)
|
||||
* @param options.timeout - Milliseconds before escalating to SIGKILL
|
||||
*/
|
||||
term({ signal, timeout }?: {
|
||||
signal?: NodeJS.Signals | undefined;
|
||||
timeout?: number | undefined;
|
||||
}): Promise<void>;
|
||||
onDrop(): void;
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CommandController = void 0;
|
||||
const _1 = require(".");
|
||||
const types_1 = require("../../../base/lib/types");
|
||||
const T = __importStar(require("../../../base/lib/types"));
|
||||
const util_1 = require("../util");
|
||||
const fs = __importStar(require("node:fs/promises"));
|
||||
const logErrorOnce_1 = require("../../../base/lib/util/logErrorOnce");
|
||||
/**
|
||||
* Low-level controller for a single running process inside a subcontainer (or as a JS function).
|
||||
*
|
||||
* Manages the child process lifecycle: spawning, waiting, and signal-based termination.
|
||||
* Used internally by {@link Daemon} to manage individual command executions.
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type
|
||||
* @typeParam C - The subcontainer type, or `null` for JS-only commands
|
||||
*/
|
||||
class CommandController extends util_1.Drop {
|
||||
constructor(runningAnswer, state, subcontainer, process, sigtermTimeout = _1.DEFAULT_SIGTERM_TIMEOUT) {
|
||||
super();
|
||||
this.runningAnswer = runningAnswer;
|
||||
this.state = state;
|
||||
this.subcontainer = subcontainer;
|
||||
this.process = process;
|
||||
this.sigtermTimeout = sigtermTimeout;
|
||||
}
|
||||
/**
|
||||
* Factory method to create a new CommandController.
|
||||
*
|
||||
* Returns a curried async function: `(effects, subcontainer, exec) => CommandController`.
|
||||
* If the exec spec has an `fn` property, runs the function; otherwise spawns a shell command
|
||||
* in the subcontainer.
|
||||
*/
|
||||
static of() {
|
||||
return async (effects, subcontainer, exec) => {
|
||||
try {
|
||||
if ('fn' in exec) {
|
||||
const abort = new AbortController();
|
||||
const cell = {
|
||||
ctrl: new CommandController(exec.fn(subcontainer, abort.signal).then(async (command) => {
|
||||
if (subcontainer && command && !abort.signal.aborted) {
|
||||
const newCtrl = (await CommandController.of()(effects, subcontainer, command)).leak();
|
||||
Object.assign(cell.ctrl, newCtrl);
|
||||
return await cell.ctrl.runningAnswer;
|
||||
}
|
||||
else {
|
||||
cell.ctrl.state.exited = true;
|
||||
}
|
||||
return null;
|
||||
}), { exited: false }, subcontainer, abort, exec.sigtermTimeout),
|
||||
};
|
||||
return cell.ctrl;
|
||||
}
|
||||
let commands;
|
||||
if (T.isUseEntrypoint(exec.command)) {
|
||||
const imageMeta = await fs
|
||||
.readFile(`/media/startos/images/${subcontainer.imageId}.json`, {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
.catch(() => '{}')
|
||||
.then(JSON.parse);
|
||||
commands = imageMeta.entrypoint ?? [];
|
||||
commands = commands.concat(...(exec.command.overridCmd ?? imageMeta.cmd ?? []));
|
||||
}
|
||||
else
|
||||
commands = (0, util_1.splitCommand)(exec.command);
|
||||
let childProcess;
|
||||
if (exec.runAsInit) {
|
||||
childProcess = await subcontainer.launch(commands, {
|
||||
env: exec.env,
|
||||
user: exec.user,
|
||||
cwd: exec.cwd,
|
||||
});
|
||||
}
|
||||
else {
|
||||
childProcess = await subcontainer.spawn(commands, {
|
||||
env: exec.env,
|
||||
user: exec.user,
|
||||
cwd: exec.cwd,
|
||||
stdio: exec.onStdout || exec.onStderr ? 'pipe' : 'inherit',
|
||||
});
|
||||
}
|
||||
if (exec.onStdout)
|
||||
childProcess.stdout?.on('data', exec.onStdout);
|
||||
if (exec.onStderr)
|
||||
childProcess.stderr?.on('data', exec.onStderr);
|
||||
const state = { exited: false };
|
||||
const answer = new Promise((resolve, reject) => {
|
||||
childProcess.on('exit', (code) => {
|
||||
state.exited = true;
|
||||
if (code === 0 ||
|
||||
code === 143 ||
|
||||
(code === null && childProcess.signalCode == 'SIGTERM')) {
|
||||
return resolve(null);
|
||||
}
|
||||
if (code) {
|
||||
return reject(new Error(`${commands[0]} exited with code ${code}`));
|
||||
}
|
||||
else {
|
||||
return reject(new Error(`${commands[0]} exited with signal ${childProcess.signalCode}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
return new CommandController(answer, state, subcontainer, childProcess, exec.sigtermTimeout);
|
||||
}
|
||||
catch (e) {
|
||||
await subcontainer?.destroy();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Wait for the command to finish. Optionally terminate after a timeout.
|
||||
* @param options.timeout - Milliseconds to wait before terminating. Defaults to no timeout.
|
||||
*/
|
||||
async wait({ timeout = types_1.NO_TIMEOUT } = {}) {
|
||||
if (timeout > 0)
|
||||
setTimeout(() => {
|
||||
this.term();
|
||||
}, timeout);
|
||||
try {
|
||||
if (timeout > 0 && this.process instanceof AbortController)
|
||||
await Promise.race([
|
||||
this.runningAnswer,
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out waiting for js command to exit')), timeout * 2)),
|
||||
]);
|
||||
else
|
||||
await this.runningAnswer;
|
||||
}
|
||||
finally {
|
||||
if (!this.state.exited) {
|
||||
if (this.process instanceof AbortController)
|
||||
this.process.abort();
|
||||
else
|
||||
this.process.kill('SIGKILL');
|
||||
}
|
||||
await this.subcontainer?.destroy();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Terminate the running command by sending a signal.
|
||||
*
|
||||
* Sends the specified signal (default: SIGTERM), then escalates to SIGKILL
|
||||
* after the timeout expires. Destroys the subcontainer after the process exits.
|
||||
*
|
||||
* @param options.signal - The signal to send (default: SIGTERM)
|
||||
* @param options.timeout - Milliseconds before escalating to SIGKILL
|
||||
*/
|
||||
async term({ signal = types_1.SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||
try {
|
||||
if (!this.state.exited) {
|
||||
if (this.process instanceof AbortController)
|
||||
return this.process.abort();
|
||||
if (signal !== 'SIGKILL') {
|
||||
setTimeout(() => {
|
||||
if (this.process instanceof AbortController)
|
||||
this.process.abort();
|
||||
else
|
||||
this.process.kill('SIGKILL');
|
||||
}, timeout);
|
||||
}
|
||||
if (!this.process.kill(signal)) {
|
||||
console.error(`failed to send signal ${signal} to pid ${this.process.pid}`);
|
||||
}
|
||||
}
|
||||
if (this.process instanceof AbortController)
|
||||
await Promise.race([
|
||||
this.runningAnswer,
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out waiting for js command to exit')), timeout * 2)),
|
||||
]);
|
||||
else
|
||||
await this.runningAnswer;
|
||||
}
|
||||
finally {
|
||||
await this.subcontainer?.destroy();
|
||||
}
|
||||
}
|
||||
onDrop() {
|
||||
this.term().catch(logErrorOnce_1.logErrorOnce);
|
||||
}
|
||||
}
|
||||
exports.CommandController = CommandController;
|
||||
//# sourceMappingURL=CommandController.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+77
@@ -0,0 +1,77 @@
|
||||
import * as T from '../../../base/lib/types';
|
||||
import { Drop } from '../util';
|
||||
import { SubContainer, SubContainerRc } from '../util/SubContainer';
|
||||
import { CommandController } from './CommandController';
|
||||
import { DaemonCommandType } from './Daemons';
|
||||
import { Oneshot } from './Oneshot';
|
||||
/**
|
||||
* A managed long-running process wrapper around {@link CommandController}.
|
||||
*
|
||||
* When started, the daemon automatically restarts its underlying command on failure
|
||||
* with exponential backoff (up to 30 seconds). When stopped, the command is terminated
|
||||
* gracefully. Implements {@link Drop} for automatic cleanup when the context is left.
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type
|
||||
* @typeParam C - The subcontainer type, or `null` for JS-only daemons
|
||||
*/
|
||||
export declare class Daemon<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null = SubContainer<Manifest> | null> extends Drop {
|
||||
private subcontainer;
|
||||
private startCommand;
|
||||
readonly oneshot: boolean;
|
||||
private commandController;
|
||||
protected exitedSuccess: boolean;
|
||||
private onExitFns;
|
||||
private loop;
|
||||
private _managed;
|
||||
protected constructor(subcontainer: C, startCommand: () => Promise<CommandController<Manifest, C>>, oneshot?: boolean);
|
||||
/** Returns true if this daemon is a one-shot process (exits after success) */
|
||||
isOneshot(): this is Oneshot<Manifest>;
|
||||
/**
|
||||
* Factory method to create a new Daemon.
|
||||
*
|
||||
* Returns a curried function: `(effects, subcontainer, exec) => Daemon`.
|
||||
* Registers an `onLeaveContext` callback that terminates the daemon when the
|
||||
* effects context is left.
|
||||
*/
|
||||
static of<Manifest extends T.SDKManifest>(): <C extends SubContainer<Manifest> | null>(effects: T.Effects, subcontainer: C, exec: DaemonCommandType<Manifest, C>) => Daemon<Manifest, SubContainer<Manifest, T.Effects> | null>;
|
||||
/**
|
||||
* Start the daemon. If it is already running, this is a no-op.
|
||||
*
|
||||
* The daemon will automatically restart on failure with increasing backoff
|
||||
* until {@link term} is called.
|
||||
*/
|
||||
start(): Promise<void>;
|
||||
private runLoop;
|
||||
/**
|
||||
* Terminate the daemon, stopping its underlying command.
|
||||
*
|
||||
* Sends the configured signal (default SIGTERM) and waits for the process to exit.
|
||||
* Optionally destroys the subcontainer after termination.
|
||||
*
|
||||
* @param termOptions - Optional termination settings
|
||||
* @param termOptions.signal - The signal to send (default: SIGTERM)
|
||||
* @param termOptions.timeout - Milliseconds to wait before SIGKILL
|
||||
* @param termOptions.destroySubcontainer - Whether to destroy the subcontainer after exit
|
||||
*/
|
||||
term(termOptions?: {
|
||||
signal?: NodeJS.Signals | undefined;
|
||||
timeout?: number | undefined;
|
||||
destroySubcontainer?: boolean;
|
||||
}): Promise<void>;
|
||||
/**
|
||||
* Mark this daemon as managed by a {@link Daemons} instance.
|
||||
* Suppresses the individual `onLeaveContext` termination since the
|
||||
* `Daemons` instance handles ordered shutdown.
|
||||
*/
|
||||
markManaged(): void;
|
||||
/** Get a reference-counted handle to the daemon's subcontainer, or null if there is none */
|
||||
subcontainerRc(): SubContainerRc<Manifest> | null;
|
||||
/** Check whether this daemon shares the same subcontainer as another daemon */
|
||||
sharesSubcontainerWith(other: Daemon<Manifest, SubContainer<Manifest> | null>): boolean;
|
||||
/**
|
||||
* Register a callback to be invoked each time the daemon's process exits.
|
||||
* @param fn - Callback receiving `true` on clean exit, `false` on error
|
||||
*/
|
||||
onExit(fn: (success: boolean) => void): void;
|
||||
onDrop(): void;
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Daemon = void 0;
|
||||
const asError_1 = require("../../../base/lib/util/asError");
|
||||
const logErrorOnce_1 = require("../../../base/lib/util/logErrorOnce");
|
||||
const util_1 = require("../util");
|
||||
const CommandController_1 = require("./CommandController");
|
||||
const TIMEOUT_INCREMENT_MS = 1000;
|
||||
const MAX_TIMEOUT_MS = 30000;
|
||||
/**
|
||||
* A managed long-running process wrapper around {@link CommandController}.
|
||||
*
|
||||
* When started, the daemon automatically restarts its underlying command on failure
|
||||
* with exponential backoff (up to 30 seconds). When stopped, the command is terminated
|
||||
* gracefully. Implements {@link Drop} for automatic cleanup when the context is left.
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type
|
||||
* @typeParam C - The subcontainer type, or `null` for JS-only daemons
|
||||
*/
|
||||
class Daemon extends util_1.Drop {
|
||||
constructor(subcontainer, startCommand, oneshot = false) {
|
||||
super();
|
||||
this.subcontainer = subcontainer;
|
||||
this.startCommand = startCommand;
|
||||
this.oneshot = oneshot;
|
||||
this.commandController = null;
|
||||
this.exitedSuccess = false;
|
||||
this.onExitFns = [];
|
||||
this.loop = null;
|
||||
this._managed = false;
|
||||
}
|
||||
/** Returns true if this daemon is a one-shot process (exits after success) */
|
||||
isOneshot() {
|
||||
return this.oneshot;
|
||||
}
|
||||
/**
|
||||
* Factory method to create a new Daemon.
|
||||
*
|
||||
* Returns a curried function: `(effects, subcontainer, exec) => Daemon`.
|
||||
* Registers an `onLeaveContext` callback that terminates the daemon when the
|
||||
* effects context is left.
|
||||
*/
|
||||
static of() {
|
||||
return (effects, subcontainer, exec) => {
|
||||
let subc = subcontainer;
|
||||
if (subcontainer && subcontainer.isOwned())
|
||||
subc = subcontainer.rc();
|
||||
const startCommand = () => CommandController_1.CommandController.of()(effects, (subc?.rc() ?? null), exec);
|
||||
const res = new Daemon(subc, startCommand);
|
||||
effects.onLeaveContext(() => {
|
||||
if (!res._managed) {
|
||||
res.term({ destroySubcontainer: true }).catch((e) => (0, logErrorOnce_1.logErrorOnce)(e));
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Start the daemon. If it is already running, this is a no-op.
|
||||
*
|
||||
* The daemon will automatically restart on failure with increasing backoff
|
||||
* until {@link term} is called.
|
||||
*/
|
||||
async start() {
|
||||
if (this.loop) {
|
||||
return;
|
||||
}
|
||||
const abort = new AbortController();
|
||||
const done = this.runLoop(abort.signal);
|
||||
this.loop = { abort, done };
|
||||
}
|
||||
async runLoop(signal) {
|
||||
let timeoutCounter = 0;
|
||||
try {
|
||||
while (!signal.aborted) {
|
||||
if (this.commandController) {
|
||||
await this.commandController.term({}).catch(logErrorOnce_1.logErrorOnce);
|
||||
this.commandController = null;
|
||||
}
|
||||
try {
|
||||
this.commandController = await this.startCommand();
|
||||
if (signal.aborted) {
|
||||
await this.commandController.term({}).catch(logErrorOnce_1.logErrorOnce);
|
||||
this.commandController = null;
|
||||
break;
|
||||
}
|
||||
const success = await this.commandController.wait().then((_) => true, (err) => {
|
||||
if (!signal.aborted)
|
||||
(0, logErrorOnce_1.logErrorOnce)(err);
|
||||
return false;
|
||||
});
|
||||
this.commandController = null;
|
||||
if (signal.aborted)
|
||||
break;
|
||||
for (const fn of this.onExitFns) {
|
||||
try {
|
||||
fn(success);
|
||||
}
|
||||
catch (e) {
|
||||
console.error('EXIT handler', e);
|
||||
}
|
||||
}
|
||||
if (success && this.oneshot) {
|
||||
this.exitedSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (!signal.aborted)
|
||||
console.error(e);
|
||||
}
|
||||
if (signal.aborted)
|
||||
break;
|
||||
await new Promise((resolve) => {
|
||||
const timer = setTimeout(resolve, timeoutCounter);
|
||||
signal.addEventListener('abort', () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
}, { once: true });
|
||||
});
|
||||
timeoutCounter += TIMEOUT_INCREMENT_MS;
|
||||
timeoutCounter = Math.min(MAX_TIMEOUT_MS, timeoutCounter);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.loop = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Terminate the daemon, stopping its underlying command.
|
||||
*
|
||||
* Sends the configured signal (default SIGTERM) and waits for the process to exit.
|
||||
* Optionally destroys the subcontainer after termination.
|
||||
*
|
||||
* @param termOptions - Optional termination settings
|
||||
* @param termOptions.signal - The signal to send (default: SIGTERM)
|
||||
* @param termOptions.timeout - Milliseconds to wait before SIGKILL
|
||||
* @param termOptions.destroySubcontainer - Whether to destroy the subcontainer after exit
|
||||
*/
|
||||
async term(termOptions) {
|
||||
this.exitedSuccess = false;
|
||||
this.onExitFns = [];
|
||||
if (this.loop) {
|
||||
this.loop.abort.abort();
|
||||
}
|
||||
const exiting = this.commandController?.term({ ...termOptions });
|
||||
this.commandController = null;
|
||||
if (exiting)
|
||||
await exiting.catch(logErrorOnce_1.logErrorOnce);
|
||||
if (this.loop) {
|
||||
await this.loop.done;
|
||||
}
|
||||
if (termOptions?.destroySubcontainer) {
|
||||
await this.subcontainer?.destroy();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Mark this daemon as managed by a {@link Daemons} instance.
|
||||
* Suppresses the individual `onLeaveContext` termination since the
|
||||
* `Daemons` instance handles ordered shutdown.
|
||||
*/
|
||||
markManaged() {
|
||||
this._managed = true;
|
||||
}
|
||||
/** Get a reference-counted handle to the daemon's subcontainer, or null if there is none */
|
||||
subcontainerRc() {
|
||||
return this.subcontainer?.rc() ?? null;
|
||||
}
|
||||
/** Check whether this daemon shares the same subcontainer as another daemon */
|
||||
sharesSubcontainerWith(other) {
|
||||
return this.subcontainer?.guid === other.subcontainer?.guid;
|
||||
}
|
||||
/**
|
||||
* Register a callback to be invoked each time the daemon's process exits.
|
||||
* @param fn - Callback receiving `true` on clean exit, `false` on error
|
||||
*/
|
||||
onExit(fn) {
|
||||
this.onExitFns.push(fn);
|
||||
}
|
||||
onDrop() {
|
||||
this.term().catch((e) => (0, logErrorOnce_1.logErrorOnce)((0, asError_1.asError)(e)));
|
||||
}
|
||||
}
|
||||
exports.Daemon = Daemon;
|
||||
//# sourceMappingURL=Daemon.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Daemon.js","sourceRoot":"","sources":["../../../../package/lib/mainFn/Daemon.ts"],"names":[],"mappings":"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"}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
import { HealthCheckResult } from '../health/checkFns';
|
||||
import { Trigger } from '../trigger';
|
||||
import * as T from '../../../base/lib/types';
|
||||
import { SubContainer } from '../util/SubContainer';
|
||||
import * as CP from 'node:child_process';
|
||||
export { Daemon } from './Daemon';
|
||||
export { CommandController } from './CommandController';
|
||||
import { HealthDaemon } from './HealthDaemon';
|
||||
import { Daemon } from './Daemon';
|
||||
import { CommandController } from './CommandController';
|
||||
/** Promisified version of `child_process.exec` */
|
||||
export declare const cpExec: typeof CP.exec.__promisify__;
|
||||
/** Promisified version of `child_process.execFile` */
|
||||
export declare const cpExecFile: typeof CP.execFile.__promisify__;
|
||||
/**
|
||||
* Configuration for a daemon's health-check readiness probe.
|
||||
*
|
||||
* Determines how the system knows when a daemon is healthy and ready to serve.
|
||||
*/
|
||||
export type Ready = {
|
||||
/** A human-readable display name for the health check. If null, the health check itself will be from the UI */
|
||||
display: string | null;
|
||||
/**
|
||||
* @description The function to determine the health status of the daemon
|
||||
*
|
||||
* The SDK provides some built-in health checks. To see them, type sdk.healthCheck.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
fn: () =>
|
||||
sdk.healthCheck.checkPortListening(effects, 80, {
|
||||
successMessage: 'service listening on port 80',
|
||||
errorMessage: 'service is unreachable',
|
||||
})
|
||||
* ```
|
||||
*/
|
||||
fn: () => Promise<HealthCheckResult> | HealthCheckResult;
|
||||
/**
|
||||
* A duration in milliseconds to treat a failing health check as "starting"
|
||||
*
|
||||
* defaults to 5000
|
||||
*/
|
||||
gracePeriod?: number;
|
||||
trigger?: Trigger;
|
||||
};
|
||||
/**
|
||||
* Options for running a daemon as a shell command inside a subcontainer.
|
||||
* Includes the command to run, optional signal/timeout, environment, user, and stdio callbacks.
|
||||
*/
|
||||
export type ExecCommandOptions = {
|
||||
command: T.CommandType;
|
||||
sigtermTimeout?: number;
|
||||
runAsInit?: boolean;
|
||||
env?: {
|
||||
[variable in string]?: string;
|
||||
} | undefined;
|
||||
cwd?: string | undefined;
|
||||
user?: string | undefined;
|
||||
onStdout?: (chunk: Buffer | string | any) => void;
|
||||
onStderr?: (chunk: Buffer | string | any) => void;
|
||||
};
|
||||
/**
|
||||
* Options for running a daemon via an async function that may optionally return
|
||||
* a command to execute in the subcontainer. The function receives an `AbortSignal`
|
||||
* for cooperative cancellation.
|
||||
*/
|
||||
export type ExecFnOptions<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null> = {
|
||||
fn: (subcontainer: C, abort: AbortSignal) => Promise<C extends null ? null : ExecCommandOptions | null>;
|
||||
sigtermTimeout?: number;
|
||||
};
|
||||
/**
|
||||
* The execution specification for a daemon: either an {@link ExecFnOptions} (async function)
|
||||
* or an {@link ExecCommandOptions} (shell command, only valid when a subcontainer is provided).
|
||||
*/
|
||||
export type DaemonCommandType<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null> = ExecFnOptions<Manifest, C> | (C extends null ? never : ExecCommandOptions);
|
||||
type NewDaemonParams<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null> = {
|
||||
/** What to run as the daemon: either an async fn or a commandline command to run in the subcontainer */
|
||||
exec: DaemonCommandType<Manifest, C>;
|
||||
/** The subcontainer in which the daemon runs */
|
||||
subcontainer: C;
|
||||
};
|
||||
type OptionalParamSync<T> = T | (() => T | null);
|
||||
type OptionalParamAsync<T> = () => Promise<T | null>;
|
||||
type AddDaemonParams<Manifest extends T.SDKManifest, Ids extends string, Id extends string, C extends SubContainer<Manifest> | null> = (NewDaemonParams<Manifest, C> | {
|
||||
daemon: Daemon<Manifest>;
|
||||
}) & {
|
||||
ready: Ready;
|
||||
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||
requires: Exclude<Ids, Id>[];
|
||||
};
|
||||
type AddOneshotParams<Manifest extends T.SDKManifest, Ids extends string, Id extends string, C extends SubContainer<Manifest> | null> = NewDaemonParams<Manifest, C> & {
|
||||
exec: DaemonCommandType<Manifest, C>;
|
||||
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||
requires: Exclude<Ids, Id>[];
|
||||
};
|
||||
type AddHealthCheckParams<Ids extends string, Id extends string> = {
|
||||
ready: Ready;
|
||||
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||
requires: Exclude<Ids, Id>[];
|
||||
};
|
||||
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`;
|
||||
export declare const runCommand: <Manifest extends T.SDKManifest>() => (effects: T.Effects, subcontainer: SubContainer<Manifest, T.Effects>, exec: DaemonCommandType<Manifest, SubContainer<Manifest, T.Effects>>) => Promise<CommandController<Manifest, SubContainer<Manifest, T.Effects>>>;
|
||||
/**
|
||||
* A class for defining and controlling the service daemons
|
||||
```ts
|
||||
Daemons.of({
|
||||
effects,
|
||||
started,
|
||||
interfaceReceipt, // Provide the interfaceReceipt to prove it was completed
|
||||
healthReceipts, // Provide the healthReceipts or [] to prove they were at least considered
|
||||
}).addDaemon('webui', {
|
||||
command: 'hello-world', // The command to start the daemon
|
||||
ready: {
|
||||
display: 'Web Interface',
|
||||
// The function to run to determine the health status of the daemon
|
||||
fn: () =>
|
||||
checkPortListening(effects, 80, {
|
||||
successMessage: 'The web interface is ready',
|
||||
errorMessage: 'The web interface is not ready',
|
||||
}),
|
||||
},
|
||||
requires: [],
|
||||
})
|
||||
```
|
||||
*/
|
||||
export declare class Daemons<Manifest extends T.SDKManifest, Ids extends string> implements T.DaemonBuildable {
|
||||
readonly effects: T.Effects;
|
||||
readonly ids: Ids[];
|
||||
readonly healthDaemons: HealthDaemon<Manifest>[];
|
||||
private termPromise;
|
||||
private constructor();
|
||||
/**
|
||||
* Returns an empty new Daemons class with the provided inputSpec.
|
||||
*
|
||||
* Call .addDaemon() on the returned class to add a daemon.
|
||||
*
|
||||
* Daemons run in the order they are defined, with latter daemons being capable of
|
||||
* depending on prior daemons
|
||||
*
|
||||
* @param effects
|
||||
*
|
||||
* @param started
|
||||
* @returns
|
||||
*/
|
||||
static of<Manifest extends T.SDKManifest>(options: {
|
||||
effects: T.Effects;
|
||||
}): Daemons<Manifest, never>;
|
||||
private addDaemonImpl;
|
||||
/**
|
||||
* Returns the complete list of daemons, including the one defined here
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(id: "" extends Id ? never : ErrorDuplicateId<Id> extends Id ? never : Id extends Ids ? ErrorDuplicateId<Id> : Id, options: OptionalParamSync<AddDaemonParams<Manifest, Ids, Id, C>>): Daemons<Manifest, Ids | Id>;
|
||||
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(id: "" extends Id ? never : ErrorDuplicateId<Id> extends Id ? never : Id extends Ids ? ErrorDuplicateId<Id> : Id, options: OptionalParamAsync<AddDaemonParams<Manifest, Ids, Id, C>>): Promise<Daemons<Manifest, Ids | Id>>;
|
||||
/**
|
||||
* Returns the complete list of daemons, including a "oneshot" daemon one defined here
|
||||
* a oneshot daemon is a command that executes once when started, and is considered "running" once it exits successfully
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(id: "" extends Id ? never : ErrorDuplicateId<Id> extends Id ? never : Id extends Ids ? ErrorDuplicateId<Id> : Id, options: OptionalParamSync<AddOneshotParams<Manifest, Ids, Id, C>>): Daemons<Manifest, Ids | Id>;
|
||||
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(id: "" extends Id ? never : ErrorDuplicateId<Id> extends Id ? never : Id extends Ids ? ErrorDuplicateId<Id> : Id, options: OptionalParamAsync<AddOneshotParams<Manifest, Ids, Id, C>>): Promise<Daemons<Manifest, Ids | Id>>;
|
||||
/**
|
||||
* Returns the complete list of daemons, including a new HealthCheck defined here
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addHealthCheck<Id extends string>(id: "" extends Id ? never : ErrorDuplicateId<Id> extends Id ? never : Id extends Ids ? ErrorDuplicateId<Id> : Id, options: OptionalParamSync<AddHealthCheckParams<Ids, Id>>): Daemons<Manifest, Ids | Id>;
|
||||
addHealthCheck<Id extends string>(id: "" extends Id ? never : ErrorDuplicateId<Id> extends Id ? never : Id extends Ids ? ErrorDuplicateId<Id> : Id, options: OptionalParamAsync<AddHealthCheckParams<Ids, Id>>): Promise<Daemons<Manifest, Ids | Id>>;
|
||||
/**
|
||||
* Runs the entire system until all daemons have returned `ready`.
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
runUntilSuccess(timeout: number | null): Promise<null>;
|
||||
/**
|
||||
* Gracefully terminate all daemons in reverse dependency order.
|
||||
*
|
||||
* Daemons with no remaining dependents are shut down first, proceeding
|
||||
* until all daemons have been terminated. Falls back to a bulk shutdown
|
||||
* if a dependency cycle is detected.
|
||||
*/
|
||||
term(): Promise<void>;
|
||||
private _term;
|
||||
/**
|
||||
* Start all registered daemons and their health checks.
|
||||
* @returns This `Daemons` instance, now running
|
||||
*/
|
||||
build(): Promise<this>;
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Daemons = exports.runCommand = exports.cpExecFile = exports.cpExec = exports.CommandController = exports.Daemon = void 0;
|
||||
const node_util_1 = require("node:util");
|
||||
const CP = __importStar(require("node:child_process"));
|
||||
var Daemon_1 = require("./Daemon");
|
||||
Object.defineProperty(exports, "Daemon", { enumerable: true, get: function () { return Daemon_1.Daemon; } });
|
||||
var CommandController_1 = require("./CommandController");
|
||||
Object.defineProperty(exports, "CommandController", { enumerable: true, get: function () { return CommandController_1.CommandController; } });
|
||||
const HealthDaemon_1 = require("./HealthDaemon");
|
||||
const Daemon_2 = require("./Daemon");
|
||||
const CommandController_2 = require("./CommandController");
|
||||
const Oneshot_1 = require("./Oneshot");
|
||||
/** Promisified version of `child_process.exec` */
|
||||
exports.cpExec = (0, node_util_1.promisify)(CP.exec);
|
||||
/** Promisified version of `child_process.execFile` */
|
||||
exports.cpExecFile = (0, node_util_1.promisify)(CP.execFile);
|
||||
const runCommand = () => CommandController_2.CommandController.of();
|
||||
exports.runCommand = runCommand;
|
||||
/**
|
||||
* A class for defining and controlling the service daemons
|
||||
```ts
|
||||
Daemons.of({
|
||||
effects,
|
||||
started,
|
||||
interfaceReceipt, // Provide the interfaceReceipt to prove it was completed
|
||||
healthReceipts, // Provide the healthReceipts or [] to prove they were at least considered
|
||||
}).addDaemon('webui', {
|
||||
command: 'hello-world', // The command to start the daemon
|
||||
ready: {
|
||||
display: 'Web Interface',
|
||||
// The function to run to determine the health status of the daemon
|
||||
fn: () =>
|
||||
checkPortListening(effects, 80, {
|
||||
successMessage: 'The web interface is ready',
|
||||
errorMessage: 'The web interface is not ready',
|
||||
}),
|
||||
},
|
||||
requires: [],
|
||||
})
|
||||
```
|
||||
*/
|
||||
class Daemons {
|
||||
constructor(effects, ids, healthDaemons) {
|
||||
this.effects = effects;
|
||||
this.ids = ids;
|
||||
this.healthDaemons = healthDaemons;
|
||||
this.termPromise = null;
|
||||
}
|
||||
/**
|
||||
* Returns an empty new Daemons class with the provided inputSpec.
|
||||
*
|
||||
* Call .addDaemon() on the returned class to add a daemon.
|
||||
*
|
||||
* Daemons run in the order they are defined, with latter daemons being capable of
|
||||
* depending on prior daemons
|
||||
*
|
||||
* @param effects
|
||||
*
|
||||
* @param started
|
||||
* @returns
|
||||
*/
|
||||
static of(options) {
|
||||
return new Daemons(options.effects, [], []);
|
||||
}
|
||||
addDaemonImpl(id, daemon, requires, ready) {
|
||||
const healthDaemon = new HealthDaemon_1.HealthDaemon(daemon, requires
|
||||
.map((x) => this.ids.indexOf(x))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]), id, ready, this.effects);
|
||||
const ids = [...this.ids, id];
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon];
|
||||
return new Daemons(this.effects, ids, healthDaemons);
|
||||
}
|
||||
addDaemon(id, options) {
|
||||
const prev = this;
|
||||
const res = (options) => {
|
||||
if (!options)
|
||||
return prev;
|
||||
const daemon = 'daemon' in options
|
||||
? options.daemon
|
||||
: Daemon_2.Daemon.of()(this.effects, options.subcontainer, options.exec);
|
||||
return prev.addDaemonImpl(id, daemon, options.requires, options.ready);
|
||||
};
|
||||
if (options instanceof Function) {
|
||||
const opts = options();
|
||||
if (opts instanceof Promise) {
|
||||
return opts.then(res);
|
||||
}
|
||||
return res(opts);
|
||||
}
|
||||
return res(options);
|
||||
}
|
||||
addOneshot(id, options) {
|
||||
const prev = this;
|
||||
const res = (options) => {
|
||||
if (!options)
|
||||
return prev;
|
||||
const daemon = Oneshot_1.Oneshot.of()(this.effects, options.subcontainer, options.exec);
|
||||
return prev.addDaemonImpl(id, daemon, options.requires, HealthDaemon_1.EXIT_SUCCESS);
|
||||
};
|
||||
if (options instanceof Function) {
|
||||
const opts = options();
|
||||
if (opts instanceof Promise) {
|
||||
return opts.then(res);
|
||||
}
|
||||
return res(opts);
|
||||
}
|
||||
return res(options);
|
||||
}
|
||||
addHealthCheck(id, options) {
|
||||
const prev = this;
|
||||
const res = (options) => {
|
||||
if (!options)
|
||||
return prev;
|
||||
return prev.addDaemonImpl(id, null, options.requires, options.ready);
|
||||
};
|
||||
if (options instanceof Function) {
|
||||
const opts = options();
|
||||
if (opts instanceof Promise) {
|
||||
return opts.then(res);
|
||||
}
|
||||
return res(opts);
|
||||
}
|
||||
return res(options);
|
||||
}
|
||||
/**
|
||||
* Runs the entire system until all daemons have returned `ready`.
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
async runUntilSuccess(timeout) {
|
||||
let resolve = (_) => { };
|
||||
const res = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
if (timeout)
|
||||
setTimeout(() => {
|
||||
const notReady = this.healthDaemons
|
||||
.filter((d) => !d.isReady)
|
||||
.map((d) => d.id);
|
||||
rej(new Error(`Timed out waiting for ${notReady}`));
|
||||
}, timeout);
|
||||
});
|
||||
const daemon = Oneshot_1.Oneshot.of()(this.effects, null, {
|
||||
fn: async () => {
|
||||
resolve();
|
||||
return null;
|
||||
},
|
||||
});
|
||||
const healthDaemon = new HealthDaemon_1.HealthDaemon(daemon, [...this.healthDaemons], '__RUN_UNTIL_SUCCESS', 'EXIT_SUCCESS', this.effects);
|
||||
const daemons = await new Daemons(this.effects, this.ids, [
|
||||
...this.healthDaemons,
|
||||
healthDaemon,
|
||||
]).build();
|
||||
try {
|
||||
await res;
|
||||
}
|
||||
finally {
|
||||
await daemons.term();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Gracefully terminate all daemons in reverse dependency order.
|
||||
*
|
||||
* Daemons with no remaining dependents are shut down first, proceeding
|
||||
* until all daemons have been terminated. Falls back to a bulk shutdown
|
||||
* if a dependency cycle is detected.
|
||||
*/
|
||||
async term() {
|
||||
if (!this.termPromise) {
|
||||
this.termPromise = this._term();
|
||||
}
|
||||
return this.termPromise;
|
||||
}
|
||||
async _term() {
|
||||
const remaining = new Set(this.healthDaemons);
|
||||
while (remaining.size > 0) {
|
||||
// Find daemons with no remaining dependents
|
||||
const canShutdown = [...remaining].filter((daemon) => ![...remaining].some((other) => other.dependencies.some((dep) => dep.id === daemon.id)));
|
||||
if (canShutdown.length === 0) {
|
||||
// Dependency cycle that should not happen, just shutdown remaining daemons
|
||||
console.warn('Dependency cycle detected, shutting down remaining daemons');
|
||||
canShutdown.push(...[...remaining].reverse());
|
||||
}
|
||||
// remove from remaining set
|
||||
canShutdown.forEach((daemon) => remaining.delete(daemon));
|
||||
// Shutdown daemons with no remaining dependents concurrently
|
||||
await Promise.allSettled(canShutdown.map(async (daemon) => {
|
||||
try {
|
||||
console.debug(`Terminating daemon ${daemon.id}`);
|
||||
const destroySubcontainer = daemon.daemon
|
||||
? ![...remaining].some((d) => d.daemon?.sharesSubcontainerWith(daemon.daemon))
|
||||
: false;
|
||||
await daemon.term({ destroySubcontainer });
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start all registered daemons and their health checks.
|
||||
* @returns This `Daemons` instance, now running
|
||||
*/
|
||||
async build() {
|
||||
this.effects.onLeaveContext(() => {
|
||||
this.term().catch((e) => console.error(e));
|
||||
});
|
||||
for (const daemon of this.healthDaemons) {
|
||||
daemon.daemon?.markManaged();
|
||||
await daemon.updateStatus();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
exports.Daemons = Daemons;
|
||||
//# sourceMappingURL=Daemons.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+46
@@ -0,0 +1,46 @@
|
||||
import { HealthCheckResult } from '../health/checkFns';
|
||||
import { Ready } from './Daemons';
|
||||
import { Daemon } from './Daemon';
|
||||
import { Effects, SDKManifest } from '../../../base/lib/types';
|
||||
export declare const EXIT_SUCCESS: "EXIT_SUCCESS";
|
||||
/**
|
||||
* Wanted a structure that deals with controlling daemons by their health status
|
||||
* States:
|
||||
* -- Waiting for dependencies to be success
|
||||
* -- Running: Daemon is running and the status is in the health
|
||||
*
|
||||
*/
|
||||
export declare class HealthDaemon<Manifest extends SDKManifest> {
|
||||
readonly daemon: Daemon<Manifest> | null;
|
||||
readonly dependencies: HealthDaemon<Manifest>[];
|
||||
readonly id: string;
|
||||
readonly ready: Ready | typeof EXIT_SUCCESS;
|
||||
readonly effects: Effects;
|
||||
private _health;
|
||||
private healthWatchers;
|
||||
private running;
|
||||
private started?;
|
||||
private resolveReady;
|
||||
private resolvedReady;
|
||||
private readyPromise;
|
||||
private session;
|
||||
constructor(daemon: Daemon<Manifest> | null, dependencies: HealthDaemon<Manifest>[], id: string, ready: Ready | typeof EXIT_SUCCESS, effects: Effects);
|
||||
/** Run after we want to do cleanup */
|
||||
term(termOptions?: {
|
||||
signal?: NodeJS.Signals | undefined;
|
||||
timeout?: number | undefined;
|
||||
destroySubcontainer?: boolean;
|
||||
}): Promise<void>;
|
||||
/** Want to add another notifier that the health might have changed */
|
||||
addWatcher(watcher: () => unknown): void;
|
||||
get health(): Readonly<HealthCheckResult>;
|
||||
private changeRunning;
|
||||
private stopSession;
|
||||
private resetReady;
|
||||
private startSession;
|
||||
private runHealthCheckLoop;
|
||||
onReady(): Promise<void>;
|
||||
get isReady(): boolean;
|
||||
private setHealth;
|
||||
updateStatus(): Promise<void>;
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.HealthDaemon = exports.EXIT_SUCCESS = void 0;
|
||||
const defaultTrigger_1 = require("../trigger/defaultTrigger");
|
||||
exports.EXIT_SUCCESS = 'EXIT_SUCCESS';
|
||||
/**
|
||||
* Wanted a structure that deals with controlling daemons by their health status
|
||||
* States:
|
||||
* -- Waiting for dependencies to be success
|
||||
* -- Running: Daemon is running and the status is in the health
|
||||
*
|
||||
*/
|
||||
class HealthDaemon {
|
||||
constructor(daemon, dependencies, id, ready, effects) {
|
||||
this.daemon = daemon;
|
||||
this.dependencies = dependencies;
|
||||
this.id = id;
|
||||
this.ready = ready;
|
||||
this.effects = effects;
|
||||
this._health = { result: 'waiting', message: null };
|
||||
this.healthWatchers = [];
|
||||
this.running = false;
|
||||
this.resolvedReady = false;
|
||||
this.session = null;
|
||||
this.readyPromise = new Promise((resolve) => (this.resolveReady = () => {
|
||||
resolve();
|
||||
this.resolvedReady = true;
|
||||
}));
|
||||
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()));
|
||||
}
|
||||
/** Run after we want to do cleanup */
|
||||
async term(termOptions) {
|
||||
this.healthWatchers = [];
|
||||
this.running = false;
|
||||
await this.stopSession();
|
||||
await this.daemon?.term({
|
||||
...termOptions,
|
||||
});
|
||||
}
|
||||
/** Want to add another notifier that the health might have changed */
|
||||
addWatcher(watcher) {
|
||||
this.healthWatchers.push(watcher);
|
||||
}
|
||||
get health() {
|
||||
return Object.freeze(this._health);
|
||||
}
|
||||
async changeRunning(newStatus) {
|
||||
if (this.running === newStatus)
|
||||
return;
|
||||
this.running = newStatus;
|
||||
if (newStatus) {
|
||||
console.debug(`Launching ${this.id}...`);
|
||||
this.startSession();
|
||||
this.daemon?.start();
|
||||
this.started = performance.now();
|
||||
}
|
||||
else {
|
||||
console.debug(`Stopping ${this.id}...`);
|
||||
await this.stopSession();
|
||||
await this.daemon?.term();
|
||||
}
|
||||
}
|
||||
async stopSession() {
|
||||
if (!this.session)
|
||||
return;
|
||||
this.session.abort.abort();
|
||||
await this.session.done;
|
||||
this.session = null;
|
||||
this.resetReady();
|
||||
}
|
||||
resetReady() {
|
||||
this.resolvedReady = false;
|
||||
this.readyPromise = new Promise((resolve) => (this.resolveReady = () => {
|
||||
resolve();
|
||||
this.resolvedReady = true;
|
||||
}));
|
||||
}
|
||||
startSession() {
|
||||
this.session?.abort.abort();
|
||||
const abort = new AbortController();
|
||||
this.daemon?.onExit((success) => {
|
||||
if (abort.signal.aborted)
|
||||
return;
|
||||
if (success && this.ready === 'EXIT_SUCCESS') {
|
||||
this.setHealth({ result: 'success', message: null });
|
||||
}
|
||||
else if (!success) {
|
||||
this.setHealth({
|
||||
result: 'failure',
|
||||
message: `${this.id} daemon crashed`,
|
||||
});
|
||||
}
|
||||
else if (!this.daemon?.isOneshot()) {
|
||||
this.setHealth({
|
||||
result: 'failure',
|
||||
message: `${this.id} daemon exited`,
|
||||
});
|
||||
}
|
||||
});
|
||||
const done = this.ready === 'EXIT_SUCCESS'
|
||||
? Promise.resolve()
|
||||
: this.runHealthCheckLoop(abort.signal);
|
||||
this.session = { abort, done };
|
||||
}
|
||||
async runHealthCheckLoop(signal) {
|
||||
if (this.ready === 'EXIT_SUCCESS')
|
||||
return;
|
||||
const trigger = (this.ready.trigger ?? defaultTrigger_1.defaultTrigger)(() => ({
|
||||
lastResult: this._health.result,
|
||||
}));
|
||||
const aborted = new Promise((resolve) => signal.addEventListener('abort', () => resolve({ done: true }), {
|
||||
once: true,
|
||||
}));
|
||||
try {
|
||||
for (let res = await Promise.race([aborted, trigger.next()]); !res.done; res = await Promise.race([aborted, trigger.next()])) {
|
||||
const response = await Promise.resolve(this.ready.fn()).catch((err) => {
|
||||
return {
|
||||
result: 'failure',
|
||||
message: 'message' in err ? err.message : String(err),
|
||||
};
|
||||
});
|
||||
if (signal.aborted)
|
||||
break;
|
||||
await this.setHealth(response);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
if (!signal.aborted) {
|
||||
console.error(`Daemon ${this.id} health check failed: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
onReady() {
|
||||
return this.readyPromise;
|
||||
}
|
||||
get isReady() {
|
||||
return this.resolvedReady;
|
||||
}
|
||||
async setHealth(health) {
|
||||
const changed = this._health.result !== health.result;
|
||||
this._health = health;
|
||||
if (this.resolveReady && health.result === 'success') {
|
||||
this.resolveReady();
|
||||
}
|
||||
if (changed)
|
||||
this.healthWatchers.forEach((watcher) => watcher());
|
||||
if (this.ready === 'EXIT_SUCCESS')
|
||||
return;
|
||||
const display = this.ready.display;
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
let result = health.result;
|
||||
if (result === 'failure' &&
|
||||
this.started &&
|
||||
performance.now() - this.started <= (this.ready.gracePeriod ?? 10_000))
|
||||
result = 'starting';
|
||||
if (result === 'failure') {
|
||||
console.error(`Health Check ${this.id} failed:`, health.message);
|
||||
}
|
||||
await this.effects.setHealth({
|
||||
...health,
|
||||
id: this.id,
|
||||
name: display,
|
||||
result,
|
||||
});
|
||||
}
|
||||
async updateStatus() {
|
||||
const healths = this.dependencies.map((d) => ({
|
||||
health: d.running && d._health,
|
||||
id: d.id,
|
||||
display: typeof d.ready === 'object' ? d.ready.display : null,
|
||||
}));
|
||||
const waitingOn = healths.filter((h) => !h.health || h.health.result !== 'success');
|
||||
if (waitingOn.length)
|
||||
console.debug(`daemon ${this.id} waiting on ${waitingOn.map((w) => w.id)}`);
|
||||
if (waitingOn.length) {
|
||||
const waitingOnNames = waitingOn.flatMap((w) => w.display ? [w.display] : []);
|
||||
const message = waitingOnNames.length ? waitingOnNames.join(', ') : null;
|
||||
await this.setHealth({ result: 'waiting', message });
|
||||
}
|
||||
else {
|
||||
await this.setHealth({ result: 'starting', message: null });
|
||||
}
|
||||
await this.changeRunning(!waitingOn.length);
|
||||
}
|
||||
}
|
||||
exports.HealthDaemon = HealthDaemon;
|
||||
//# sourceMappingURL=HealthDaemon.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+87
@@ -0,0 +1,87 @@
|
||||
import * as T from '../../../base/lib/types';
|
||||
import { MountOptions } from '../util/SubContainer';
|
||||
type MountArray = {
|
||||
mountpoint: string;
|
||||
options: MountOptions;
|
||||
}[];
|
||||
type SharedOptions = {
|
||||
/** The path within the resource to mount. Use `null` to mount the entire resource */
|
||||
subpath: string | null;
|
||||
/** Where to mount the resource. e.g. /data */
|
||||
mountpoint: string;
|
||||
/**
|
||||
* Whether to mount this as a file or directory
|
||||
*
|
||||
* defaults to "directory"
|
||||
* */
|
||||
type?: 'file' | 'directory' | 'infer';
|
||||
};
|
||||
type VolumeOpts<Manifest extends T.SDKManifest> = {
|
||||
/** The ID of the volume to mount. Must be one of the volume IDs defined in the manifest */
|
||||
volumeId: Manifest['volumes'][number];
|
||||
/** Whether or not the resource should be readonly for this subcontainer */
|
||||
readonly: boolean;
|
||||
} & SharedOptions;
|
||||
type DependencyOpts<Manifest extends T.SDKManifest> = {
|
||||
/** The ID of the dependency */
|
||||
dependencyId: Manifest['id'];
|
||||
/** The ID of the volume to mount. Must be one of the volume IDs defined in the manifest of the dependency */
|
||||
volumeId: Manifest['volumes'][number];
|
||||
/** Whether or not the resource should be readonly for this subcontainer */
|
||||
readonly: boolean;
|
||||
} & SharedOptions;
|
||||
/**
|
||||
* Immutable builder for declaring filesystem mounts into a subcontainer.
|
||||
*
|
||||
* Supports mounting volumes, static assets, dependency volumes, and backup directories.
|
||||
* Each `mount*` method returns a new `Mounts` instance (immutable builder pattern).
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type
|
||||
* @typeParam Backups - Tracks whether backup mounts have been added (type-level flag)
|
||||
*/
|
||||
export declare class Mounts<Manifest extends T.SDKManifest, Backups extends SharedOptions = never> {
|
||||
readonly volumes: VolumeOpts<Manifest>[];
|
||||
readonly assets: SharedOptions[];
|
||||
readonly dependencies: DependencyOpts<T.SDKManifest>[];
|
||||
readonly backups: Backups[];
|
||||
private constructor();
|
||||
/**
|
||||
* Create an empty Mounts builder with no mounts configured.
|
||||
* @returns A new Mounts instance ready for chaining mount declarations
|
||||
*/
|
||||
static of<Manifest extends T.SDKManifest>(): Mounts<Manifest, never>;
|
||||
/**
|
||||
* Add a volume mount from the service's own volumes.
|
||||
* @param options - Volume ID, mountpoint, readonly flag, and optional subpath
|
||||
* @returns A new Mounts instance with this volume added
|
||||
*/
|
||||
mountVolume(options: VolumeOpts<Manifest>): Mounts<Manifest, Backups>;
|
||||
/**
|
||||
* Add a read-only mount of the service's packaged static assets.
|
||||
* @param options - Mountpoint and optional subpath within the assets directory
|
||||
* @returns A new Mounts instance with this asset mount added
|
||||
*/
|
||||
mountAssets(options: SharedOptions): Mounts<Manifest, Backups>;
|
||||
/**
|
||||
* Add a mount from a dependency package's volume.
|
||||
* @param options - Dependency ID, volume ID, mountpoint, readonly flag, and optional subpath
|
||||
* @returns A new Mounts instance with this dependency mount added
|
||||
*/
|
||||
mountDependency<DependencyManifest extends T.SDKManifest>(options: DependencyOpts<DependencyManifest>): Mounts<Manifest, Backups>;
|
||||
/**
|
||||
* Add a mount of the backup directory. Only valid during backup/restore operations.
|
||||
* @param options - Mountpoint and optional subpath within the backup directory
|
||||
* @returns A new Mounts instance with this backup mount added
|
||||
*/
|
||||
mountBackups(options: SharedOptions): Mounts<Manifest, {
|
||||
subpath: string | null;
|
||||
mountpoint: string;
|
||||
}>;
|
||||
/**
|
||||
* Compile all declared mounts into the low-level mount array consumed by the subcontainer runtime.
|
||||
* @throws If any two mounts share the same mountpoint
|
||||
* @returns An array of `{ mountpoint, options }` objects
|
||||
*/
|
||||
build(): MountArray;
|
||||
}
|
||||
export {};
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Mounts = void 0;
|
||||
/**
|
||||
* Immutable builder for declaring filesystem mounts into a subcontainer.
|
||||
*
|
||||
* Supports mounting volumes, static assets, dependency volumes, and backup directories.
|
||||
* Each `mount*` method returns a new `Mounts` instance (immutable builder pattern).
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type
|
||||
* @typeParam Backups - Tracks whether backup mounts have been added (type-level flag)
|
||||
*/
|
||||
class Mounts {
|
||||
constructor(volumes, assets, dependencies, backups) {
|
||||
this.volumes = volumes;
|
||||
this.assets = assets;
|
||||
this.dependencies = dependencies;
|
||||
this.backups = backups;
|
||||
}
|
||||
/**
|
||||
* Create an empty Mounts builder with no mounts configured.
|
||||
* @returns A new Mounts instance ready for chaining mount declarations
|
||||
*/
|
||||
static of() {
|
||||
return new Mounts([], [], [], []);
|
||||
}
|
||||
/**
|
||||
* Add a volume mount from the service's own volumes.
|
||||
* @param options - Volume ID, mountpoint, readonly flag, and optional subpath
|
||||
* @returns A new Mounts instance with this volume added
|
||||
*/
|
||||
mountVolume(options) {
|
||||
return new Mounts([...this.volumes, options], [...this.assets], [...this.dependencies], [...this.backups]);
|
||||
}
|
||||
/**
|
||||
* Add a read-only mount of the service's packaged static assets.
|
||||
* @param options - Mountpoint and optional subpath within the assets directory
|
||||
* @returns A new Mounts instance with this asset mount added
|
||||
*/
|
||||
mountAssets(options) {
|
||||
return new Mounts([...this.volumes], [...this.assets, options], [...this.dependencies], [...this.backups]);
|
||||
}
|
||||
/**
|
||||
* Add a mount from a dependency package's volume.
|
||||
* @param options - Dependency ID, volume ID, mountpoint, readonly flag, and optional subpath
|
||||
* @returns A new Mounts instance with this dependency mount added
|
||||
*/
|
||||
mountDependency(options) {
|
||||
return new Mounts([...this.volumes], [...this.assets], [...this.dependencies, options], [...this.backups]);
|
||||
}
|
||||
/**
|
||||
* Add a mount of the backup directory. Only valid during backup/restore operations.
|
||||
* @param options - Mountpoint and optional subpath within the backup directory
|
||||
* @returns A new Mounts instance with this backup mount added
|
||||
*/
|
||||
mountBackups(options) {
|
||||
return new Mounts([...this.volumes], [...this.assets], [...this.dependencies], [...this.backups, options]);
|
||||
}
|
||||
/**
|
||||
* Compile all declared mounts into the low-level mount array consumed by the subcontainer runtime.
|
||||
* @throws If any two mounts share the same mountpoint
|
||||
* @returns An array of `{ mountpoint, options }` objects
|
||||
*/
|
||||
build() {
|
||||
const mountpoints = new Set();
|
||||
for (let mountpoint of this.volumes
|
||||
.map((v) => v.mountpoint)
|
||||
.concat(this.assets.map((a) => a.mountpoint))
|
||||
.concat(this.dependencies.map((d) => d.mountpoint))) {
|
||||
if (mountpoints.has(mountpoint)) {
|
||||
throw new Error(`cannot mount more than once to mountpoint ${mountpoint}`);
|
||||
}
|
||||
mountpoints.add(mountpoint);
|
||||
}
|
||||
return []
|
||||
.concat(this.volumes.map((v) => ({
|
||||
mountpoint: v.mountpoint,
|
||||
options: {
|
||||
type: 'volume',
|
||||
volumeId: v.volumeId,
|
||||
subpath: v.subpath,
|
||||
readonly: v.readonly,
|
||||
filetype: v.type ?? 'directory',
|
||||
idmap: [],
|
||||
},
|
||||
})))
|
||||
.concat(this.assets.map((a) => ({
|
||||
mountpoint: a.mountpoint,
|
||||
options: {
|
||||
type: 'assets',
|
||||
subpath: a.subpath,
|
||||
filetype: a.type ?? 'directory',
|
||||
idmap: [],
|
||||
},
|
||||
})))
|
||||
.concat(this.dependencies.map((d) => ({
|
||||
mountpoint: d.mountpoint,
|
||||
options: {
|
||||
type: 'pointer',
|
||||
packageId: d.dependencyId,
|
||||
volumeId: d.volumeId,
|
||||
subpath: d.subpath,
|
||||
readonly: d.readonly,
|
||||
filetype: d.type ?? 'directory',
|
||||
idmap: [],
|
||||
},
|
||||
})));
|
||||
}
|
||||
}
|
||||
exports.Mounts = Mounts;
|
||||
const a = Mounts.of().mountBackups({ subpath: null, mountpoint: '' });
|
||||
// @ts-expect-error
|
||||
const m = a;
|
||||
//# sourceMappingURL=Mounts.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Mounts.js","sourceRoot":"","sources":["../../../../package/lib/mainFn/Mounts.ts"],"names":[],"mappings":"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"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as T from '../../../base/lib/types';
|
||||
import { SubContainer } from '../util/SubContainer';
|
||||
import { Daemon } from './Daemon';
|
||||
import { DaemonCommandType } from './Daemons';
|
||||
/**
|
||||
* This is a wrapper around CommandController that has a state of off, where the command shouldn't be running
|
||||
* and the others state of running, where it will keep a living running command
|
||||
* unlike Daemon, does not restart on success
|
||||
*/
|
||||
export declare class Oneshot<Manifest extends T.SDKManifest, C extends SubContainer<Manifest> | null = SubContainer<Manifest> | null> extends Daemon<Manifest, C> {
|
||||
static of<Manifest extends T.SDKManifest>(): <C extends SubContainer<Manifest> | null>(effects: T.Effects, subcontainer: C, exec: DaemonCommandType<Manifest, C>) => Oneshot<Manifest, C>;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Oneshot = void 0;
|
||||
const CommandController_1 = require("./CommandController");
|
||||
const Daemon_1 = require("./Daemon");
|
||||
/**
|
||||
* This is a wrapper around CommandController that has a state of off, where the command shouldn't be running
|
||||
* and the others state of running, where it will keep a living running command
|
||||
* unlike Daemon, does not restart on success
|
||||
*/
|
||||
class Oneshot extends Daemon_1.Daemon {
|
||||
static of() {
|
||||
return (effects, subcontainer, exec) => {
|
||||
let subc = subcontainer;
|
||||
if (subcontainer && subcontainer.isOwned())
|
||||
subc = subcontainer.rc();
|
||||
const startCommand = () => CommandController_1.CommandController.of()(effects, (subc?.rc() ?? null), exec);
|
||||
return new Oneshot(subcontainer, startCommand, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.Oneshot = Oneshot;
|
||||
//# sourceMappingURL=Oneshot.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Oneshot.js","sourceRoot":"","sources":["../../../../package/lib/mainFn/Oneshot.ts"],"names":[],"mappings":";;;AAEA,2DAAuD;AACvD,qCAAiC;AAGjC;;;;GAIG;AAEH,MAAa,OAGX,SAAQ,eAAmB;IAC3B,MAAM,CAAC,EAAE;QACP,OAAO,CACL,OAAkB,EAClB,YAAe,EACf,IAAoC,EACpC,EAAE;YACF,IAAI,IAAI,GAAkC,YAAY,CAAA;YACtD,IAAI,YAAY,IAAI,YAAY,CAAC,OAAO,EAAE;gBAAE,IAAI,GAAG,YAAY,CAAC,EAAE,EAAE,CAAA;YACpE,MAAM,YAAY,GAAG,GAAG,EAAE,CACxB,qCAAiB,CAAC,EAAE,EAAe,CACjC,OAAO,EACP,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,IAAI,CAAM,EACzB,IAAI,CACL,CAAA;YACH,OAAO,IAAI,OAAO,CAAc,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,CAAA;QACnE,CAAC,CAAA;IACH,CAAC;CACF;AArBD,0BAqBC"}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import * as T from '../../../base/lib/types';
|
||||
import { Daemons } from './Daemons';
|
||||
import '../../../base/lib/interfaces/ServiceInterfaceBuilder';
|
||||
import '../../../base/lib/interfaces/Origin';
|
||||
/** Default time in milliseconds to wait for a process to exit after SIGTERM before escalating to SIGKILL */
|
||||
export declare const DEFAULT_SIGTERM_TIMEOUT = 60000;
|
||||
/**
|
||||
* Used to ensure that the main function is running with the valid proofs.
|
||||
* We first do the folowing order of things
|
||||
* 1. We get the interfaces
|
||||
* 2. We setup all the commands to setup the system
|
||||
* 3. We create the health checks
|
||||
* 4. We setup the daemons init system
|
||||
* @param fn
|
||||
* @returns
|
||||
*/
|
||||
export declare const setupMain: <Manifest extends T.SDKManifest>(fn: (o: {
|
||||
effects: T.Effects;
|
||||
}) => Promise<Daemons<Manifest, any>>) => T.ExpectedExports.main;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.setupMain = exports.DEFAULT_SIGTERM_TIMEOUT = void 0;
|
||||
require("../../../base/lib/interfaces/ServiceInterfaceBuilder");
|
||||
require("../../../base/lib/interfaces/Origin");
|
||||
/** Default time in milliseconds to wait for a process to exit after SIGTERM before escalating to SIGKILL */
|
||||
exports.DEFAULT_SIGTERM_TIMEOUT = 60_000;
|
||||
/**
|
||||
* Used to ensure that the main function is running with the valid proofs.
|
||||
* We first do the folowing order of things
|
||||
* 1. We get the interfaces
|
||||
* 2. We setup all the commands to setup the system
|
||||
* 3. We create the health checks
|
||||
* 4. We setup the daemons init system
|
||||
* @param fn
|
||||
* @returns
|
||||
*/
|
||||
const setupMain = (fn) => {
|
||||
return async (options) => {
|
||||
const result = await fn(options);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
exports.setupMain = setupMain;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../package/lib/mainFn/index.ts"],"names":[],"mappings":";;;AAEA,gEAA6D;AAC7D,+CAA4C;AAE5C,4GAA4G;AAC/F,QAAA,uBAAuB,GAAG,MAAM,CAAA;AAC7C;;;;;;;;;GASG;AACI,MAAM,SAAS,GAAG,CACvB,EAAkE,EAC1C,EAAE;IAC1B,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,CAAA;QAChC,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;AACH,CAAC,CAAA;AAPY,QAAA,SAAS,aAOrB"}
|
||||
Reference in New Issue
Block a user