-
Notifications
You must be signed in to change notification settings - Fork 32
fix: ClearProcessingState increments ProcessingGeneration to prevent resurrection #612
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
2ce7a0e
ec5ac94
40cddef
195ad8f
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 |
|---|---|---|
|
|
@@ -786,6 +786,14 @@ private void ClearProcessingState(SessionState state, bool accumulateApiTime = t | |
| ClearFlushedReplayDedup(state); | ||
| state.PendingReasoningMessages.Clear(); | ||
|
|
||
| // Increment generation so any InvokeOnUI callback that captured the old | ||
| // generation before this ClearProcessingState call will see a mismatch and | ||
| // bail out — preventing resurrection of a completed turn. Without this, | ||
| // the generation guard passes and only the !IsProcessing check saves us. | ||
| // Skip if orphaned (long.MaxValue) — incrementing would overflow to long.MinValue. | ||
| if (Interlocked.Read(ref state.ProcessingGeneration) != long.MaxValue) | ||
|
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. 🟢 MINOR — TOCTOU window in read-then-increment (Flagged by: 2/3 reviewers) Theoretical race between Practically low-risk. A |
||
| Interlocked.Increment(ref state.ProcessingGeneration); | ||
|
|
||
| state.Info.IsProcessing = false; | ||
| state.Info.IsResumed = false; | ||
| state.Info.ProcessingStartedAt = null; | ||
|
|
@@ -4475,6 +4483,8 @@ await InvokeOnUIAsync(() => | |
| state.IsReconnectedSend = false; // INV-1 item 8: prevent stale 35s timeout on next watchdog start | ||
| Interlocked.Exchange(ref state.SendingFlag, 0); | ||
| // Clear IsProcessing BEFORE completing TCS (INV-O3) | ||
| // No MaxValue guard needed: STEER-ERROR only reaches non-orphaned sessions | ||
| Interlocked.Increment(ref state.ProcessingGeneration); // Invalidate stale callbacks | ||
|
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. 🟡 MODERATE — Missing This bare Suggested fix: if (Interlocked.Read(ref state.ProcessingGeneration) != long.MaxValue)
Interlocked.Increment(ref state.ProcessingGeneration); // Invalidate stale callbacks |
||
| state.Info.IsProcessing = false; | ||
| if (state.Info.ProcessingStartedAt is { } steerStarted) | ||
| state.Info.TotalApiTimeSeconds += (DateTime.UtcNow - steerStarted).TotalSeconds; | ||
|
|
@@ -4754,6 +4764,8 @@ public async Task ReloadMcpServersAsync(string sessionName) | |
| await InvokeOnUIAsync(() => | ||
| { | ||
| FlushCurrentResponse(state); | ||
| // No MaxValue guard needed: MCP-reload only reaches non-orphaned sessions | ||
| Interlocked.Increment(ref state.ProcessingGeneration); // Invalidate stale callbacks | ||
|
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. 🟡 MODERATE — Missing Same issue as the STEER-ERROR path. This unconditional Suggested fix: if (Interlocked.Read(ref state.ProcessingGeneration) != long.MaxValue)
Interlocked.Increment(ref state.ProcessingGeneration); // Invalidate stale callbacks |
||
| state.Info.IsProcessing = false; | ||
| state.Info.IsResumed = false; | ||
| state.HasUsedToolsThisTurn = false; | ||
|
|
||
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.
🟢 MINOR — Test verifies arithmetic, not behavior (Flagged by: 2/3 reviewers)
This test checks that
6 ≠ 5after incrementing — tautologically true regardless of whether the production guard works. A stronger test would create a closure simulating a stale callback, complete the session, execute the closure, and assertIsProcessingwas NOT changed.Also: only the
ClearProcessingStatepath (viaCompleteResponse) is tested. The other 3 modified paths have zero generation increment verification.Not blocking — the first test (
ClearProcessingState_IncrementsGeneration) is solid.