-
Notifications
You must be signed in to change notification settings - Fork 87
Fix cancel race: disarm safety-net timer before QA finalization #6934
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -48,6 +48,7 @@ final class ComputerUseSession: ObservableObject { | |
| private var isPaused = false | ||
| private var confirmationContinuation: CheckedContinuation<Bool, Never>? | ||
| private var messageLoopTask: Task<Void, Never>? | ||
| private var cancelSafetyNetTask: Task<Void, Never>? | ||
|
|
||
| private let enumerator: AccessibilityTreeProviding | ||
| private let screenCapture: ScreenCaptureProviding | ||
|
|
@@ -182,6 +183,11 @@ final class ComputerUseSession: ObservableObject { | |
| } else { | ||
| state = .failed(reason: "No focused window and screen capture failed") | ||
| logger.finishSession(result: "failed: no window") | ||
| // Disarm the cancel safety net — run() reached the post-loop and will | ||
| // handle finalization + abort itself. | ||
| cancelSafetyNetTask?.cancel() | ||
| cancelSafetyNetTask = nil | ||
|
|
||
| // Finalize QA recording BEFORE sending abort — the daemon's handleCuSessionAbort | ||
| // deletes cuSessionMetadata, so cu_session_finalized must arrive first for | ||
| // summary injection to work. | ||
|
|
@@ -276,6 +282,11 @@ final class ComputerUseSession: ObservableObject { | |
| } | ||
| } | ||
|
|
||
| // Disarm the cancel safety net — run() reached the post-loop and will | ||
| // handle finalization + abort itself. | ||
| cancelSafetyNetTask?.cancel() | ||
| cancelSafetyNetTask = nil | ||
|
|
||
| // Finalize QA recording and send cu_session_finalized | ||
| if qaMode { | ||
| await finalizeQARecording() | ||
|
|
@@ -1050,7 +1061,7 @@ final class ComputerUseSession: ObservableObject { | |
| // Deferred abort: give run() a chance to send finalization first, | ||
| // but guarantee abort eventually fires as a safety net in case | ||
| // run() never reaches the post-loop block (e.g., throws or gets stuck). | ||
| Task { @MainActor in | ||
| cancelSafetyNetTask = Task { @MainActor in | ||
| try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds | ||
| guard self.isCancelled else { return } // in case state changed | ||
| try? self.daemonClient.send(CuSessionAbortMessage(sessionId: self.id)) | ||
|
Comment on lines
+1064
to
1067
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. 🔴 Safety-net task cancellation is ineffective — abort still fires after The safety-net Root Cause: Task.isCancelled vs self.isCancelledWhen guard self.isCancelled else { return }Since Critically, The fix should check cancelSafetyNetTask = Task { @MainActor in
try? await Task.sleep(nanoseconds: 2_000_000_000)
guard !Task.isCancelled else { return } // <-- check cooperative cancellation
guard self.isCancelled else { return }
try? self.daemonClient.send(CuSessionAbortMessage(sessionId: self.id))
}Impact: The (Refers to lines 1064-1068) Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
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.
Canceling
cancelSafetyNetTaskinrun()does not actually disarm it because the task body ignoresTask.sleepcancellation (try? await Task.sleep(...)) and continues immediately; withisCancelled == true, it still sendscu_session_abortbeforefinalizeQARecording()finishes, so the original metadata-loss race can still occur on slow finalization paths. This is triggered specifically when the new post-loopcancelSafetyNetTask?.cancel()path runs during QA cancellation.Useful? React with 👍 / 👎.