diff --git a/sdk/package/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts index 5ce08ce85..242c5827d 100644 --- a/sdk/package/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -2,11 +2,7 @@ import * as T from '../../../base/lib/types' import { asError } from '../../../base/lib/util/asError' import { logErrorOnce } from '../../../base/lib/util/logErrorOnce' import { Drop } from '../util' -import { - SubContainer, - SubContainerOwned, - SubContainerRc, -} from '../util/SubContainer' +import { SubContainer, SubContainerRc } from '../util/SubContainer' import { CommandController } from './CommandController' import { DaemonCommandType } from './Daemons' import { Oneshot } from './Oneshot' @@ -28,10 +24,9 @@ export class Daemon< C extends SubContainer | null = SubContainer | null, > extends Drop { private commandController: CommandController | null = null - private shouldBeRunning = false protected exitedSuccess = false - private exiting: Promise | null = null private onExitFns: ((success: boolean) => void)[] = [] + private loop: { abort: AbortController; done: Promise } | null = null protected constructor( private subcontainer: C, private startCommand: () => Promise>, @@ -77,31 +72,38 @@ export class Daemon< * until {@link term} is called. */ async start() { - if (this.commandController) { + if (this.loop) { return } - this.shouldBeRunning = true + const abort = new AbortController() + const done = this.runLoop(abort.signal) + this.loop = { abort, done } + } + + private async runLoop(signal: AbortSignal) { let timeoutCounter = 0 - ;(async () => { - while (this.shouldBeRunning) { - if (this.commandController) - await this.commandController - .term({}) - .catch((err) => logErrorOnce(err)) + try { + while (!signal.aborted) { + if (this.commandController) { + await this.commandController.term({}).catch(logErrorOnce) + this.commandController = null + } try { this.commandController = await this.startCommand() - if (!this.shouldBeRunning) { - // handles race condition if stopped while starting - await this.term() + if (signal.aborted) { + await this.commandController.term({}).catch(logErrorOnce) + this.commandController = null break } const success = await this.commandController.wait().then( (_) => true, (err) => { - if (this.shouldBeRunning) logErrorOnce(err) + if (!signal.aborted) logErrorOnce(err) return false }, ) + this.commandController = null + if (signal.aborted) break for (const fn of this.onExitFns) { try { fn(success) @@ -114,16 +116,28 @@ export class Daemon< break } } catch (e) { - console.error(e) + if (!signal.aborted) console.error(e) } - await new Promise((resolve) => setTimeout(resolve, timeoutCounter)) + 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) } - })().catch((err) => { - console.error(asError(err)) - }) + } finally { + this.loop = null + } } + /** * Terminate the daemon, stopping its underlying command. * @@ -140,19 +154,23 @@ export class Daemon< timeout?: number | undefined destroySubcontainer?: boolean }) { - this.shouldBeRunning = false this.exitedSuccess = false - if (this.commandController) { - this.exiting = this.commandController.term({ ...termOptions }) - this.commandController = null - this.onExitFns = [] + this.onExitFns = [] + + if (this.loop) { + this.loop.abort.abort() } - if (this.exiting) { - await this.exiting.catch(logErrorOnce) - if (termOptions?.destroySubcontainer) { - await this.subcontainer?.destroy() - } - this.exiting = null + + const exiting = this.commandController?.term({ ...termOptions }) + this.commandController = null + if (exiting) await exiting.catch(logErrorOnce) + + if (this.loop) { + await this.loop.done + } + + if (termOptions?.destroySubcontainer) { + await this.subcontainer?.destroy() } } /** Get a reference-counted handle to the daemon's subcontainer, or null if there is none */