diff --git a/lib/mainFn/Daemons.ts b/lib/mainFn/Daemons.ts index cbc9fd3..ce10ead 100644 --- a/lib/mainFn/Daemons.ts +++ b/lib/mainFn/Daemons.ts @@ -3,7 +3,12 @@ import { CheckResult } from "../health/checkFns" import { Trigger } from "../trigger" import { TriggerInput } from "../trigger/TriggerInput" import { defaultTrigger } from "../trigger/defaultTrigger" -import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types" +import { + DaemonReturned, + Effects, + Signals, + ValidIfNoStupidEscape, +} from "../types" import { createUtils } from "../util" type Daemon = { id: "" extends Id ? never : Id @@ -128,10 +133,10 @@ export class Daemons { }) } return { - async term() { + async term(options?: { signal?: Signals; timeout?: number }) { await Promise.all( Object.values>(daemonsStarted).map((x) => - x.then((x) => x.term()), + x.then((x) => x.term(options)), ), ) }, diff --git a/lib/types.ts b/lib/types.ts index 64c9254..0ae4d1e 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -5,6 +5,8 @@ import { PortOptions } from "./interfaces/Host" import { UrlString } from "./util/getNetworkInterface" import { NetworkInterfaceType } from "./util/utils" +export type Signals = "SIGINT" | "SIGTERM" | "SIGKILL" | "SIGHUP" + export type ExportedAction = (options: { effects: Effects input?: Record @@ -128,7 +130,7 @@ export type DaemonReceipt = { } export type Daemon = { wait(): Promise - term(): Promise + term(options?: { signal?: Signals; timeout?: number }): Promise [DaemonProof]: never } @@ -148,7 +150,7 @@ export type CommandType = export type DaemonReturned = { wait(): Promise - term(): Promise + term(options?: { signal?: Signals; timeout?: number }): Promise } export type ActionMetadata = { @@ -222,12 +224,10 @@ export type ExposeUiPaths = Array<{ export type Effects = { executeAction(opts: { serviceId?: string + actionId: string input: Input }): Promise - /** Sandbox mode lets us read but not write */ - is_sandboxed(): Promise - /** Removes all network bindings */ clearBindings(): Promise /** Creates a host connected to the specified port with the provided options */ @@ -397,6 +397,8 @@ export type Effects = { message?: string }): Promise + setMainStatus(o: { status: "running" | "stopped" }): Promise + /** Set the dependencies of what the service needs, usually ran during the set config as a best practice */ setDependencies(dependencies: Dependencies): Promise /** Exists could be useful during the runtime to know if some service exists, option dep */ diff --git a/lib/util/utils.ts b/lib/util/utils.ts index ccfcb6c..61d056d 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -11,6 +11,7 @@ import { ExtractStore, InterfaceId, PackageId, + Signals, ValidIfNoStupidEscape, } from "../types" import { GetSystemSmtp } from "./GetSystemSmtp" @@ -38,6 +39,10 @@ import * as CP from "node:child_process" import { promisify } from "node:util" import { splitCommand } from "./splitCommand" +export const SIGTERM: Signals = "SIGTERM" +export const SIGKILL: Signals = "SIGTERM" +export const NO_TIMEOUT = -1 + const childProcess = { exec: promisify(CP.exec), execFile: promisify(CP.execFile), @@ -204,8 +209,19 @@ export const utils = ( wait() { return answer }, - async term() { - childProcess.kill() + async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) { + childProcess.kill(signal) + + if (timeout <= NO_TIMEOUT) { + const didTimeout = await Promise.race([ + new Promise((resolve) => setTimeout(resolve, timeout)).then( + () => true, + ), + answer.then(() => false), + ]) + if (didTimeout) childProcess.kill(SIGKILL) + } + await answer }, } },