Skip to content

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 into
mainfrom
bug/focus-and-theme-tests-leak-shared-dispatcher-setup-968
Oct 3, 2026
Merged

drmoisan merged 20 commits into
mainfrom
bug/focus-and-theme-tests-leak-shared-dispatcher-setup-968

Conversation

@drmoisan

@drmoisan drmoisan commented Oct 3, 2026

Copy link
Copy Markdown
Owner

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 under FieldLock. 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.
  • A new test class, QfcItemController_UiThreadDispatcherPinCountTests, holds one fail-before regression test and three specification tests for the counted pin.
  • QfcItemController_FocusAndThemeTests no longer calls EnsureUiThreadDispatcher. Those calls were dead, because the theme path dispatches through the theme's injected IUiDispatcher mock. The duplicate private BuildExecutingViewer helper is also removed.
  • Folded Bug: qfc-datamodel-950-review-residuals #972: one shared SynchronousBackgroundWorker test helper replaces three per-file copies. Test-owned workers are now disposed by the test method that creates them.
  • Folded Bug: qfc-datamodel-950-review-residuals #972: the two dequeue-liveness tests now wait on explicit completion signals (ArmingFakeTimeProvider) instead of Task.Yield and clock-advance loops.
  • Folded Bug: qfc-datamodel-950-review-residuals #972: caller-free legacy members are removed from QfcDatamodel.cs (495 to 367 lines). The comment that describes the producer-liveness flag in QfcDatamodel.QueueProcessing.cs is 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 by FieldLock. 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: in Transaction_SecondCallerCannotInstallUntilTheFirstRestores, the doc is updated, the baseline pin is removed and the transaction is disposed in a finally. No assertion changed.
  • Controllers/QfcItemController.FocusAndThemeTests.cs: the dead EnsureUiThreadDispatcher calls and the duplicate helper are removed, and the arrange comments are corrected.
  • Controllers/QfcItemController.TestSupport.cs: wrapper and helper docs updated. EnsureSynchronizationContext is unchanged.
  • TestSupport/SynchronousBackgroundWorker.cs and TestSupport/ArmingFakeTimeProvider.cs (new). QuickFiler.Test.csproj gains three Compile Include items.
  • Controllers/QfcDatamodelLivenessTests.cs, QfcDatamodelTeardownTests.cs, QfcInitEmailQueueZeroBatchTests.cs and QfcDatamodelTests.cs now use the shared worker, dispose their workers and use the deterministic liveness rewrites.

Production (QuickFiler/):

  • Controllers/QfcDatamodel.cs: removes the duplicate private log field, Worker_RunWorkerCompleted, the synchronous LoadRemainingEmailsToQueue and the worker-taking overload of LoadRemainingEmailsToQueueAsync. A zero-caller proof from two independent searches is in evidence/qa-gates/qfc-datamodel-legacy-callers.md. Every IQfcDatamodel member is still implemented.
  • Controllers/QfcDatamodel.QueueProcessing.cs: comment-only correction to the _remainingLoadActive doc.

Docs: the feature folder docs/features/active/2026-10-02-focus-and-theme-tests-leak-shared-dispatcher-setup-968/ and two promoted records under docs/features/potential/promoted/.

Architecture / How It Fits Together

Each EnsureScope is 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 the QfcItemControllerTestSupport forwarder takes and releases its pin inside a held fixture transaction, as recorded in evidence/qa-gates/call-site-census.md.

Verification

Completed (local, recorded in the feature evidence folder):

  • Fail-before: EnsureDispatcher_TwoPinsHeld_ReleasingTheFirstKeepsTheDispatcherUntilTheLastRelease failed against the unmodified fixture with found <null> and passed after the fixture change (evidence/regression-testing/fail-before-pin-count.md, pass-after-pin-count.md).
  • Single toolchain pass (evidence/qa-gates/toolchain-final.md):
    • dotnet tool run csharpier check .: exit 0, 1640 files checked.
    • The analyzer /t:Rebuild and the TreatWarningsAsErrors /t:Rebuild: exit 0, 0 errors, 0 warnings, no skipped compile.
    • Coverage run by the DIRECT route (dotnet-coverage around vstest.console.exe): 7365 of 7365 passed.
  • First-party coverage: lines 85.35% to 85.36%, branches 79.73% to 79.75% (evidence/qa-gates/coverage-comparison.md).
  • Parallel runs of the three dispatcher classes together and of the four datamodel classes together passed (evidence/regression-testing/).
  • Feature review (policy-audit, code-review, feature-audit at 2026-10-03T04-00): 31 of 32 acceptance criteria pass, and AC22 is pending this PR's CI.

Pending:

  • AC22: the full toolchain through the standard coverage runner. Locally, a shell-icon test fails on this workstation for environmental reasons (the same failure reproduces on main), so the coverage stage ran by the DIRECT route. AC22 is checked off only from this pull request's own CI run on the final head.

Recommended:

  • dotnet tool run csharpier check .
  • msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true
  • The test: MSTest with Coverage (Koverage) VS Code task.

Backward Compatibility / Migration Notes

  • The members removed from QfcDatamodel had no callers and are not part of IQfcDatamodel. The class keeps its type-level ExcludeFromCodeCoverage attribute.
  • No runsettings, coverage configuration or production behaviour changed outside the two QfcDatamodel files.

Risks and Mitigations

  • Risk: a future test that discards an EnsureScope without 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.
  • Rollback: revert the merge commit. The only production edits are dead-member removal and a comment.

Review Guide

  1. QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherFixture.cs (the fix)
  2. QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherPinCountTests.cs
  3. QuickFiler.Test/Controllers/QfcItemController.FocusAndThemeTests.cs and QfcItemController.UiThreadDispatcherFixtureTests.cs
  4. QuickFiler/Controllers/QfcDatamodel.cs and QfcDatamodel.QueueProcessing.cs
  5. The datamodel test files and the two new TestSupport helpers
  6. The feature folder (plan, evidence, review artifacts), which is large and mechanical

Follow-ups

  • None. The review found no related defect left open and no unrelated defect to file.

GitHub Auto-close

🤖 Generated with Claude Code

drmoisan and others added 20 commits October 2, 2026 07:10
…, plan; preflight pending)

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>
…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
@drmoisan
drmoisan merged commit 5d87e5b into main Oct 3, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: qfc-datamodel-950-review-residuals Bug: focus-and-theme-tests-leak-shared-dispatcher-setup

1 participant