Repository navigation
fix(quit): wait for ComfyUI to exit before quitting or installing an update #1625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b75adf9
0b9243b
dda20d2
b4a31db
f8851b3
62d08c8
8cd4ab7
7471a4b
b9dc6e8
6e90c51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,6 +117,8 @@ import { | |
| import type { AgentStartOutcome, OverridePins } from '../../agentRequirementsOverride' | ||
| import { recoverInterruptedComfyOp } from '../../opMarker' | ||
| import { waitLaunchSpawnHold } from '../../e2eOverrides' | ||
| import { trackExitWork } from '../../quitWait' | ||
| import { isQuitInProgress } from '../../quit-state' | ||
| import { | ||
| holderIsInstall, | ||
| resolvePriorProcess, | ||
|
|
@@ -623,22 +625,32 @@ export function onProcessTerminated( | |
| ): void { | ||
| let finished = false | ||
| let fallbackTimer: NodeJS.Timeout | undefined | ||
| // From `'exit'` until the callback has run, a quit waits for it: the record clear and | ||
| // `comfyui.exited` happen there. | ||
| let handled!: () => void | ||
| const handledPromise = new Promise<void>((resolve) => { | ||
| handled = resolve | ||
| }) | ||
|
|
||
| const finish = (code: number | null, signal: NodeJS.Signals | null, pipesHeld: boolean): void => { | ||
| if (finished) return | ||
| finished = true | ||
| if (fallbackTimer) clearTimeout(fallbackTimer) | ||
| try { | ||
| void Promise.resolve(callback(code, signal, { pipesHeld })).catch((err) => { | ||
| console.error('Process termination callback failed:', err) | ||
| }) | ||
| void Promise.resolve(callback(code, signal, { pipesHeld })) | ||
| .catch((err) => { | ||
| console.error('Process termination callback failed:', err) | ||
| }) | ||
| .finally(handled) | ||
| } catch (err) { | ||
| console.error('Process termination callback failed:', err) | ||
| handled() | ||
| } | ||
| } | ||
|
|
||
| proc.once('close', (code, signal) => finish(code, signal, false)) | ||
| proc.once('exit', (code, signal) => { | ||
| trackExitWork(handledPromise) | ||
| if (finished) return | ||
| fallbackTimer = setTimeout(() => finish(code, signal, true), PROCESS_CLOSE_GRACE_MS) | ||
| fallbackTimer.unref() | ||
|
|
@@ -983,6 +995,9 @@ export async function handleLaunch(ctx: ActionContext): Promise<ActionResult> { | |
| if (_runningSessions.has(sessionId)) { | ||
| return { ok: false, message: i18n.t('errors.alreadyRunning') } | ||
| } | ||
| // Quitting or installing an update: a ComfyUI started now would outlive the wait for the | ||
| // ones being stopped. | ||
| if (isQuitInProgress()) return { ok: false, cancelled: true } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High — This guard makes a stuck There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rejected: the paths where |
||
| // No `_hasActiveLaunch` here: this guard, `_beginLaunch`, and runLaunch's | ||
| // `_operationAborts.set` all run in one synchronous stretch, so a second | ||
| // launch can never slip between them. Checking it would instead reject a | ||
|
|
@@ -2713,7 +2728,10 @@ async function runLaunch( | |
| rebootModelCheckAbort = null | ||
| } | ||
|
|
||
| if (pendingModelFolderRelaunch || checkRebootMarker(sessionPath)) { | ||
| // Never respawn while quitting: the new ComfyUI would outlive the quit's wait. The marker | ||
| // is still consumed, as before. | ||
| const relaunch = pendingModelFolderRelaunch || checkRebootMarker(sessionPath) | ||
| if (relaunch && !isQuitInProgress()) { | ||
| const isModelRelaunch = pendingModelFolderRelaunch | ||
| pendingModelFolderRelaunch = false | ||
| if (!isModelRelaunch) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 High —
trackExitWork(handledPromise)is registered inside the'exit'listener, which is both too late and too narrow. A child that fails to spawn emits'error'/'close'with no'exit', sofinishruns with its bookkeeping untracked; and if'close'fires first the wait can drain before the'exit'listener registers anything. Worse, the tracked callback can consume a reboot marker and spawn a replacement ComfyUI during the quit, whichstopRunning()has already de-tracked — so Desktop exits or starts the installer with a live, untracked child. Register at wiring time and have the callback check quit state before relaunching. Raised by 4 of 8 reviewers (gpt-5.6-sol-max adversarial, gpt-5.6-sol-max edge-case, kimi-k2.7-code edge-case, claude-opus-5-thinking-max adversarial).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taken in part: the exit handler no longer respawns ComfyUI (Manager restart marker or model-folder relaunch) while Desktop is quitting. Rejected in part: Node emits
exitbeforeclose, and a child that failed to spawn has no pid, so a quit never stops it.Landed in f8851b3 (with b4a31db).