Fix session processor crash when a queue item is deleted while running#9352
Fix session processor crash when a queue item is deleted while running#9352lstein wants to merge 5 commits into
Conversation
Deleting a queue item (or clearing the queue) while it is executing is a supported operation: the delete/clear paths remove the DB row immediately and rely on the processor noticing the cancel event afterwards. The workflow call lifecycle re-fetched the queue item and its parents unconditionally after the session runner returned, raising SessionQueueItemNotFoundError and killing the processor thread when the row was already gone. Treat a missing queue item or parent as 'deleted mid-run' and return quietly instead, matching the existing handling in _on_after_run_session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g it Commit 5baa4bd moved the visible queue controls back to cancelation semantics but the cancelQueueItem hotkey was missed and still called the delete hook. Deleting removes the DB row while the item is still executing, which raced the session processor. Align the hotkey with the CancelCurrentQueueItemIconButton it mirrors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canceling the current item clears $lastProgressEvent on the terminal queue_item_status_changed event, but a denoise step callback can race the cancelation and emit invocation_progress afterwards, repopulating the progress bar which then sticks at its last value. The coordinator's state machine is designed to reject such trailing events, but the terminal status handler deleted the item's state entry at exactly the moment it was needed. Keep the entry instead - item ids are never reused (retries insert new rows) and the LRU cache bounds memory. Also reset the progress bar on queue_cleared: clearing the queue deletes the in-progress item without emitting any per-item terminal status event, so nothing else clears it on that path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Added a third commit fixing a follow-on UI issue: after canceling the current item, the progress bar stayed frozen at its last value instead of resetting. Root cause: the Also: Includes a regression test that fails against the previous coordinator behavior. 🤖 Generated with Claude Code |
JPPhoto
left a comment
There was a problem hiding this comment.
-
invokeai/frontend/web/src/services/events/setEventListeners.tsx:539- Thequeue_clearedhandler clears$lastProgressEventbut does not mark the deleted in-progress item terminal inworkflowExecutionCoordinator. A trailinginvocation_progressevent is therefore accepted atinvokeai/frontend/web/src/services/events/workflowExecutionCoordinator.ts:131and written back to$lastProgressEventatsetEventListeners.tsx:204, recreating the frozen progress bar this change intends to fix. The retained terminal state only protects cancellation paths that emitqueue_item_status_changed; the PR explicitly says queue clearing does not emit that event. To expose this issue, add a test that starts an item, handlesqueue_cleared, then delivers a trailing progress event and verifies that the coordinator rejects it and the progress store remains empty. -
invokeai/app/services/session_processor/workflow_call_runtime.py:192- Parent deletion is handled only during the initial lookup and selected re-fetches, leaving time-of-check/time-of-use races throughout all three parent transitions. Ifclear()ordelete_queue_item()removes the parent after_get_parent_queue_item()returns,set_queue_item_session()at lines 221 or 231,complete_queue_item()at line 235,resume_queue_item()at line 241, the failure path reached at line 259, orcancel_queue_item()at line 271 can still raiseSessionQueueItemNotFoundError. The SQLite implementations confirm these mutations re-read the row and raise when it has disappeared. That exception returns to the session processor as the same non-fatal error and traceback the PR is intended to eliminate. The new test deletes the parent before the guarded lookup, so it cannot exercise these windows. To expose this issue, add tests whose queue doubles delete the parent immediately afterget_queue_item()succeeds and before each completed, failed, and canceled parent mutation.
Per review: guarding only the parent lookups left windows where the parent row is deleted after get_queue_item succeeds but before the subsequent mutation (set_queue_item_session, complete_queue_item, resume_queue_item, cancel_queue_item, or the fail path via _on_node_error). Those mutations re-read the row and raise SessionQueueItemNotFoundError, which returned to the session processor as the same non-fatal error this PR eliminates. Catch the exception around the three parent transitions in run_queue_item: any item vanishing mid-transition means the chain is being torn down and there is nothing left to update. The dummy session queue now supports simulating deletion-after-lookup (mutations raise, lookups succeed), with one test per transition path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per review: clearing the queue reset the progress bar but did not mark the deleted in-progress item terminal in the workflowExecutionCoordinator, so a trailing invocation_progress event racing the clear was accepted and repopulated the bar - recreating the frozen progress bar on the clear path. The coordinator now exposes onQueueCleared, which aborts pending reconciliations and marks every tracked non-terminal item canceled so trailing events are rejected, and the queue_cleared listener calls it before resetting the progress store. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@JPPhoto Thanks for the careful review — both findings were real. Addressed in the two latest commits:
Parent-transition TOCTOU windows (6fdcd80): rather than chasing each window individually, 🤖 Generated with Claude Code |
|
@lstein One more:
|
Summary
Follow-up to #9350, addressing the root cause of the missing queue item.
Deleting a queue item while it is executing is a supported operation:
clearanddelete_queue_itemremove the DB row immediately and rely on the processor noticing the cancel event afterwards. But the workflow-call lifecycle re-fetched the queue item (and its parents) unconditionally after the session runner returned, raisingSessionQueueItemNotFoundError. Before #9350 this escalated to a fatal error that killed the session processor thread; after #9350 it still logs a scary non-fatal error and traceback on every cancel-via-delete.Two fixes:
Backend —
WorkflowCallQueueLifecyclenow treats a missing queue item or parent as "deleted mid-run" and returns quietly instead of raising, matching the existing handling in_on_after_run_session:run_queue_itemcatchesSessionQueueItemNotFoundErroron its post-run re-fetch (the exact failure point in the reported traceback)._get_parent_queue_itemreturnsNonewhen the parent row is gone (e.g. queue cleared while a child workflow was running); the resume/fail/cancel parent handlers all check it, as do the parent re-fetches inside the failure cascades.Frontend — the
shift+xhotkey labeled "cancel queue item" was still calling the delete endpoint. 2ddcde1 intentionally migrated the queue UI from cancel to delete semantics, but 5baa4bd later moved the visible queue controls back to cancelation and missed the hotkey. The hotkey now usesuseCancelCurrentQueueItem, aligning it with theCancelCurrentQueueItemIconButtonit mirrors and removing the most common trigger of the delete-while-running race.Related Issues / Discussions
QA Instructions
shift+x, use Queue → "Cancel and Clear All", or delete the current item from the queue list.Non-fatal error in session processor SessionQueueItemNotFoundError: No queue item with id N(and pre-Handle missing queue items in session failure #9350, a fatal processor error). After: no error; processing continues with the next item.Also covered by two new unit tests (item deleted mid-run; parent deleted while a child workflow runs, for completed/failed/canceled children).
_DummySessionQueue.get_queue_itemnow raises the realSessionQueueItemNotFoundErrorinstead ofKeyErrorto match production behavior.Merge Plan
None - straightforward merge.
Checklist
What's Newcopy (if doing a release after this PR)🤖 Generated with Claude Code