fix(968): reference-count the UiThreadDispatcherFixture ensure pin, remove the dead theme-test calls and fold the #972 datamodel residuals - #976
Merged
drmoisan merged 20 commits intoOct 3, 2026
Conversation
…, plan; preflight pending) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcieP6KJzhzgHkRh4F21Po
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcieP6KJzhzgHkRh4F21Po
… verbatim round-1 preflight report Parent parallel-orchestrator edit (run bugs-2026-09-28, /parallel-add 968 resume). Adds a binding Coordinator Scope Amendment to issue.md, restores the #972 promoted record, and recovers the round-1 reviewer report verbatim from the dead preparation child's transcript. No plan or spec content authored by the parent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X979KwR3sAjLjLkJJtnTQR
…the liveness residual Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X979KwR3sAjLjLkJJtnTQR
…eness fold) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X979KwR3sAjLjLkJJtnTQR
…s deltas in place Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X979KwR3sAjLjLkJJtnTQR
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…start union knock-on Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…ance Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
…emove the dead theme-test calls and fold the #972 datamodel residuals
…strator ruling Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests-leak-shared-dispatcher-setup-968
…n AC22) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All four toolchain jobs succeeded on head 78e24a6 (MSTest 7388 of 7388). Evidence recorded in evidence/qa-gates/ci-run-ac22.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cov8xWT2homg3SU8L3MMT
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(968): reference-count the UiThreadDispatcherFixture ensure pin, remove the dead theme-test calls and fold the #972 datamodel residuals
Summary
UiThreadDispatcherFixture.EnsureDispatcher()now counts its pins underFieldLock. Releasing one pin no longer nulls the shared UI dispatcher while another test class still holds a pin. The last release writes null only when the fixture itself installed the parked dispatcher and the field still holds it.QfcItemController_UiThreadDispatcherPinCountTests, holds one fail-before regression test and three specification tests for the counted pin.QfcItemController_FocusAndThemeTestsno longer callsEnsureUiThreadDispatcher. Those calls were dead, because the theme path dispatches through the theme's injectedIUiDispatchermock. The duplicate privateBuildExecutingViewerhelper is also removed.SynchronousBackgroundWorkertest helper replaces three per-file copies. Test-owned workers are now disposed by the test method that creates them.ArmingFakeTimeProvider) instead ofTask.Yieldand clock-advance loops.QfcDatamodel.cs(495 to 367 lines). The comment that describes the producer-liveness flag inQfcDatamodel.QueueProcessing.csis corrected.Why
Under the parallel test settings (workers zero, class-level scope), a test class that released or reset the shared dispatcher could leave another class running against a null dispatcher. The research record traced the cause to the fixture pin. It was not reference counted, so the first release reverted the seeding even while other pins were live. Issue #972 lists residuals from the #950 review in the same datamodel test files. The maintainer's related-defect directive brings them into this item.
What Changed
Tests and test support (
QuickFiler.Test/):Controllers/QfcItemController.UiThreadDispatcherFixture.cs: counted pin and install-ownership flag, both private statics guarded byFieldLock. The last-release null write sits in the same critical section as the decrement. The docs are updated.Controllers/QfcItemController.UiThreadDispatcherPinCountTests.cs(new): one regression test and three specification tests.Controllers/QfcItemController.UiThreadDispatcherFixtureTests.cs: inTransaction_SecondCallerCannotInstallUntilTheFirstRestores, the doc is updated, the baseline pin is removed and the transaction is disposed in afinally. No assertion changed.Controllers/QfcItemController.FocusAndThemeTests.cs: the deadEnsureUiThreadDispatchercalls and the duplicate helper are removed, and the arrange comments are corrected.Controllers/QfcItemController.TestSupport.cs: wrapper and helper docs updated.EnsureSynchronizationContextis unchanged.TestSupport/SynchronousBackgroundWorker.csandTestSupport/ArmingFakeTimeProvider.cs(new).QuickFiler.Test.csprojgains threeCompile Includeitems.Controllers/QfcDatamodelLivenessTests.cs,QfcDatamodelTeardownTests.cs,QfcInitEmailQueueZeroBatchTests.csandQfcDatamodelTests.csnow use the shared worker, dispose their workers and use the deterministic liveness rewrites.Production (
QuickFiler/):Controllers/QfcDatamodel.cs: removes the duplicate privatelogfield,Worker_RunWorkerCompleted, the synchronousLoadRemainingEmailsToQueueand the worker-taking overload ofLoadRemainingEmailsToQueueAsync. A zero-caller proof from two independent searches is inevidence/qa-gates/qfc-datamodel-legacy-callers.md. EveryIQfcDatamodelmember is still implemented.Controllers/QfcDatamodel.QueueProcessing.cs: comment-only correction to the_remainingLoadActivedoc.Docs: the feature folder
docs/features/active/2026-10-02-focus-and-theme-tests-leak-shared-dispatcher-setup-968/and two promoted records underdocs/features/potential/promoted/.Architecture / How It Fits Together
Each
EnsureScopeis one counted pin.EnsureDispatcher()increments the count and installs the parked dispatcher only when the field is null, recording that the fixture owns the install.EnsureScope.Dispose()is idempotent. It decrements the count, and on the last release it reverts the field only when the fixture owns the install. A dispatcher installed by a foreign transaction is never nulled. Every caller other than theQfcItemControllerTestSupportforwarder takes and releases its pin inside a held fixture transaction, as recorded inevidence/qa-gates/call-site-census.md.Verification
Completed (local, recorded in the feature evidence folder):
EnsureDispatcher_TwoPinsHeld_ReleasingTheFirstKeepsTheDispatcherUntilTheLastReleasefailed against the unmodified fixture withfound <null>and passed after the fixture change (evidence/regression-testing/fail-before-pin-count.md,pass-after-pin-count.md).evidence/qa-gates/toolchain-final.md):dotnet tool run csharpier check .: exit 0, 1640 files checked./t:Rebuildand theTreatWarningsAsErrors/t:Rebuild: exit 0, 0 errors, 0 warnings, no skipped compile.dotnet-coveragearoundvstest.console.exe): 7365 of 7365 passed.evidence/qa-gates/coverage-comparison.md).evidence/regression-testing/).policy-audit,code-review,feature-auditat2026-10-03T04-00): 31 of 32 acceptance criteria pass, and AC22 is pending this PR's CI.Pending:
Recommended:
dotnet tool run csharpier check .msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=truetest: MSTest with Coverage (Koverage)VS Code task.Backward Compatibility / Migration Notes
QfcDatamodelhad no callers and are not part ofIQfcDatamodel. The class keeps its type-levelExcludeFromCodeCoverageattribute.QfcDatamodelfiles.Risks and Mitigations
EnsureScopewithout disposing it pins the dispatcher for the life of the process. The fixture docs state this consequence. Every current caller disposes inside a held transaction.Review Guide
QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherFixture.cs(the fix)QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherPinCountTests.csQuickFiler.Test/Controllers/QfcItemController.FocusAndThemeTests.csandQfcItemController.UiThreadDispatcherFixtureTests.csQuickFiler/Controllers/QfcDatamodel.csandQfcDatamodel.QueueProcessing.csTestSupporthelpersFollow-ups
GitHub Auto-close
🤖 Generated with Claude Code