fix(ribbon): report a prime fault before clearing its in-flight marker - #946
Merged
drmoisan merged 15 commits intoSep 30, 2026
Merged
Conversation
…ging test race Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ne-toggle-prime-fault-logging-test-races-942
…on C1) Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…orrection C2) Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…light delta R2-D1) Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…ering fix Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…r (issue 942) Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…idence Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested title
fix(ribbon): report a prime fault before clearing its in-flight marker
Summary
EngineToggleStateCoordinator.CompletePrimenow calls the injected error-log delegate before it removes the engine key from the in-flight prime dictionary. Previously the marker was removed first, so a caller that fetched the prime handle after the fault could receiveTask.CompletedTaskwhile the fault report was still pending on a thread-pool thread.GetPressed_WhenPrimeFaults_LogsErrorAndStillReturnsFalseseen on the required MSTest-with-coverage check (first observed on PR test(931): remove scheduler and file-handle dependence from breadcrumb thread-affinity and FileInfoWrapper tests #939). That test is left unchanged, byte for byte.GetPressed_WhenPrimeFaults_PrimeHandleStaysRegisteredUntilFaultIsLogged, in a new third partial of the coordinator fixture. It observes the prime handle from inside the error-log sink, so its outcome depends on program order, not on scheduling.Harnessgains an optionalOnLogErrorobserver hook, following the existingOnInvalidatepattern.CompletePrimeandGetPrimeTasknow states the report-then-clear order and the guarantee it provides.Why
The research record and spec in the feature folder established that the fault observer is inside the awaited task. The defect was the order of two statements inside that observer, not a missing await.
TryRemoveran before_logError. A test thread that calledGetPrimeTaskafterprobe.SetException(...)could therefore observe the marker already gone, await a completed task, and assert on an empty error list while the pool thread was still reporting.The invariant restored is: for a key whose prime did not run to completion, the in-flight marker is present until the fault report has returned. Any caller that observes the marker absent therefore observes a completed report.
What Changed
Production
TaskMaster/Ribbon/EngineToggleStateCoordinator.cs(+10 / -5):_primeTasks.TryRemove(engineName, out _);moved to after_logError(BuildPrimeFailedMessage(engineName), failure);, with a three-line comment explaining why the order is load-bearing.summaryonCompletePrimeand thereturnsonGetPrimeTaskare updated.TaskCanceledException,StartPrimeIfNeededand its lock. Notry,catchor lock was added.Tests
TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.PrimeFaultOrdering.cs(new, 77 lines): one MSTest test using the existing strict Moq engines mock and FluentAssertions with reason strings, laid out as Arrange, Act, Assert.TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.cs(+12 / -1):Harness.OnLogErrorhook, invoked immediately after the error is appended toErrors. The diff touches only theHarnesstype.TaskMaster.Test/TaskMaster.Test.csproj(+1): explicitCompile Includefor the new partial.Docs and evidence
docs/features/active/2026-09-29-engine-toggle-prime-fault-logging-test-races-942/contains: research, spec, plan, Phase 0 to Phase 3 evidence projections, and the policy, code and feature audits.docs/features/potential/promoted/2026-09-29-engine-toggle-prime-fault-logging-test-races.md: the promotion record, inherited from the promotion commit.Architecture / How It Fits Together
GetPressedstarts a prime throughStartPrimeIfNeeded, which stores theContinueWithcontinuation returned byStartObservedPrime. On completion, that continuation (CompletePrime) runs on the default scheduler. On any non-success outcome it now reports first, then clears the marker.GetPrimeTaskreturns the stored continuation while the marker is present, andTask.CompletedTaskonce it is cleared. The production sink is the log4net error call wired inRibbonController.EngineCommands.cs. That call is unchanged and does not re-enter the coordinator.Verification
Completed (recorded in the feature folder evidence):
evidence/regression-testing/prime-fault-ordering-fail-before.md):to refer toandmust still be registered.evidence/regression-testing/prime-fault-ordering-pass-after.md): exit 0, 25 of 25 passed, including both prime-fault tests and both cancellation tests. Re-confirmed on the rebuilt assembly in Phase 3.evidence/qa-gates/toolchain-final-pass.md), in CLAUDE.md order:dotnet tool run csharpier format ., thendotnet tool run csharpier check .: no differences.msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true: exit 0, no skippedCoreCompile.msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true: exit 0, no skippedCoreCompile.evidence/qa-gates/coverage-post-change.md):CompletePrimehas at least 1 hit, including the movedTryRemove.evidence/qa-gates/footprint-scope.md): only the four code files, the feature folder and the inherited promotion record changed. The Race partial, the ribbon controller wiring and both run-settings files are untouched.Recommended:
Backward Compatibility / Migration Notes
None.
CompletePrimeandHarnessare private.GetPrimeTaskis internal and keeps its signature. No public API, configuration or data change.Risks and Mitigations
try/finallyis a documented non-goal.getPressedpoll that arrives during the log call sees the marker present and does not re-prime on that poll; the next poll does. The window is bounded by one log call.Review Guide
TaskMaster/Ribbon/EngineToggleStateCoordinator.cs: theCompletePrimereorder and the documentation.TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.PrimeFaultOrdering.cs: the regression test.TaskMaster.Test/Ribbon/EngineToggleStateCoordinatorTests.cs: theHarnesshook.evidence/regression-testing/andevidence/qa-gates/. The plan file is large and mechanical.Follow-ups
These are listed for the coordinator; none are filed from this branch:
try/finallyhardening of the log call, needed only if a throwing sink is ever introduced.AppEvents.ReadinessHookup.csandOutlookFolderTreeService.cs. No test asserts on their logs, so they are outside this defect.GitHub Auto-close
🤖 Generated with Claude Code