wait for whole session to exit when sigterm (#2620)

* wait for whole session to exit when sigterm

* fix lint

* rename poorly named variable
This commit is contained in:
Aiden McClelland
2024-05-16 19:54:36 -06:00
committed by GitHub
parent 0b8a142de0
commit 0ccbb52c1f
3 changed files with 59 additions and 19 deletions

View File

@@ -91,7 +91,7 @@ export type ServiceInterfaceType = "ui" | "p2p" | "api"
export type MainEffects = Effects & { _type: "main" } export type MainEffects = Effects & { _type: "main" }
export type Signals = NodeJS.Signals export type Signals = NodeJS.Signals
export const SIGTERM: Signals = "SIGTERM" export const SIGTERM: Signals = "SIGTERM"
export const SIGKILL: Signals = "SIGTERM" export const SIGKILL: Signals = "SIGKILL"
export const NO_TIMEOUT = -1 export const NO_TIMEOUT = -1
function removeConstType<E>() { function removeConstType<E>() {

View File

@@ -1,9 +1,9 @@
import { NO_TIMEOUT, SIGTERM } from "../StartSdk" import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk"
import { SDKManifest } from "../manifest/ManifestTypes" import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects, ValidIfNoStupidEscape } from "../types" import { Effects, ValidIfNoStupidEscape } from "../types"
import { MountOptions, Overlay } from "../util/Overlay" import { MountOptions, Overlay } from "../util/Overlay"
import { splitCommand } from "../util/splitCommand" import { splitCommand } from "../util/splitCommand"
import { cpExecFile } from "./Daemons" import { cpExecFile, cpExec } from "./Daemons"
export class CommandController { export class CommandController {
private constructor( private constructor(
@@ -74,11 +74,16 @@ export class CommandController {
try { try {
return await this.runningAnswer return await this.runningAnswer
} finally { } 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((_) => {}) await this.overlay.destroy().catch((_) => {})
} }
} }
async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) { async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) {
if (this.pid === undefined) return
try { try {
await cpExecFile("pkill", [ await cpExecFile("pkill", [
`-${signal.replace("SIG", "")}`, `-${signal.replace("SIG", "")}`,
@@ -86,23 +91,58 @@ export class CommandController {
String(this.pid), String(this.pid),
]) ])
if (timeout > NO_TIMEOUT) { const didTimeout = await waitSession(this.pid, timeout)
const didTimeout = await Promise.race([ if (didTimeout) {
new Promise((resolve) => setTimeout(resolve, timeout)).then( await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch(
() => true, (_) => {},
), )
this.runningAnswer.then(() => false),
])
if (didTimeout) {
await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch(
(_: any) => {},
)
}
} else {
await this.runningAnswer
} }
} finally { } finally {
await this.overlay.destroy() await this.overlay.destroy()
} }
} }
} }
function waitSession(
sid: number,
timeout = NO_TIMEOUT,
interval = 100,
): Promise<boolean> {
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)
}
},
)
})
}

View File

@@ -19,7 +19,7 @@ import { HealthDaemon } from "./HealthDaemon"
import { Daemon } from "./Daemon" import { Daemon } from "./Daemon"
import { CommandController } from "./CommandController" import { CommandController } from "./CommandController"
const cpExec = promisify(CP.exec) export const cpExec = promisify(CP.exec)
export const cpExecFile = promisify(CP.execFile) export const cpExecFile = promisify(CP.execFile)
export type Ready = { export type Ready = {
display: string | null display: string | null