diff --git a/sdk/lib/StartSdk.ts b/sdk/lib/StartSdk.ts index f2eabe6be..381914c13 100644 --- a/sdk/lib/StartSdk.ts +++ b/sdk/lib/StartSdk.ts @@ -91,7 +91,7 @@ export type ServiceInterfaceType = "ui" | "p2p" | "api" export type MainEffects = Effects & { _type: "main" } export type Signals = NodeJS.Signals export const SIGTERM: Signals = "SIGTERM" -export const SIGKILL: Signals = "SIGTERM" +export const SIGKILL: Signals = "SIGKILL" export const NO_TIMEOUT = -1 function removeConstType() { diff --git a/sdk/lib/mainFn/CommandController.ts b/sdk/lib/mainFn/CommandController.ts index e2e11dbfc..a68e1e426 100644 --- a/sdk/lib/mainFn/CommandController.ts +++ b/sdk/lib/mainFn/CommandController.ts @@ -1,9 +1,9 @@ -import { NO_TIMEOUT, SIGTERM } from "../StartSdk" +import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk" import { SDKManifest } from "../manifest/ManifestTypes" import { Effects, ValidIfNoStupidEscape } from "../types" import { MountOptions, Overlay } from "../util/Overlay" import { splitCommand } from "../util/splitCommand" -import { cpExecFile } from "./Daemons" +import { cpExecFile, cpExec } from "./Daemons" export class CommandController { private constructor( @@ -74,11 +74,16 @@ export class CommandController { try { return await this.runningAnswer } finally { - await cpExecFile("pkill", ["-9", "-s", String(this.pid)]).catch((_) => {}) + if (this.pid !== undefined) { + await cpExecFile("pkill", ["-9", "-s", String(this.pid)]).catch( + (_) => {}, + ) + } await this.overlay.destroy().catch((_) => {}) } } async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) { + if (this.pid === undefined) return try { await cpExecFile("pkill", [ `-${signal.replace("SIG", "")}`, @@ -86,23 +91,58 @@ export class CommandController { String(this.pid), ]) - if (timeout > NO_TIMEOUT) { - const didTimeout = await Promise.race([ - new Promise((resolve) => setTimeout(resolve, timeout)).then( - () => true, - ), - this.runningAnswer.then(() => false), - ]) - if (didTimeout) { - await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch( - (_: any) => {}, - ) - } - } else { - await this.runningAnswer + const didTimeout = await waitSession(this.pid, timeout) + if (didTimeout) { + await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch( + (_) => {}, + ) } } finally { await this.overlay.destroy() } } } + +function waitSession( + sid: number, + timeout = NO_TIMEOUT, + interval = 100, +): Promise { + let nextInterval = interval * 2 + if (timeout >= 0 && timeout < nextInterval) { + nextInterval = timeout + } + let nextTimeout = timeout + if (timeout > 0) { + if (timeout >= interval) { + nextTimeout -= interval + } else { + nextTimeout = 0 + } + } + return new Promise((resolve, reject) => { + let next: NodeJS.Timeout | null = null + if (timeout !== 0) { + next = setTimeout(() => { + waitSession(sid, nextTimeout, nextInterval).then(resolve, reject) + }, interval) + } + cpExecFile("ps", [`--sid=${sid}`, "-o", "--pid="]).then( + (_) => { + if (timeout === 0) { + resolve(true) + } + }, + (e) => { + if (next) { + clearTimeout(next) + } + if (typeof e === "object" && e && "code" in e && e.code) { + resolve(false) + } else { + reject(e) + } + }, + ) + }) +} diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/lib/mainFn/Daemons.ts index 5771136c9..8962061f5 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/lib/mainFn/Daemons.ts @@ -19,7 +19,7 @@ import { HealthDaemon } from "./HealthDaemon" import { Daemon } from "./Daemon" import { CommandController } from "./CommandController" -const cpExec = promisify(CP.exec) +export const cpExec = promisify(CP.exec) export const cpExecFile = promisify(CP.execFile) export type Ready = { display: string | null