Skip to content

Bug: quickfiler-transactiongate-permit-leak-unexcluded #882

Description

@drmoisan
  • Work Mode: full-bug

Summary

QuickFiler's one-permit TransactionGate may be able to leak or late-release a permit, and nothing in the repository currently excludes that possibility. Issue 743 set out to discriminate this hypothesis (H-LEAK) from elapsed fixture cost (H-COST) by instrumented measurement, and the measurement did not discriminate them: no expiry occurred in either instrumented run, so the discriminating experiment never took place. H-LEAK was never excluded, only never observed. Issue 743's fix routes the affected tests around the gate via a UI-marshalling seam rather than answering the question, so if H-LEAK is the real mechanism the defect still exists behind the seam. This issue carries that open question forward so it is not retired along with 743's symptom.

Environment

  • OS/version: Windows 11 Pro 10.0.26200 (the machine on which issue 743's instrumented runs were taken)
  • Python version: not applicable; this is a C# / .NET Framework 4.8 defect
  • Command/flags used: vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll /InIsolation "/TestCaseFilter:TestCategory!=LiveOutlook" for the SERIAL regime, and the same command with /Settings:TaskMaster.runsettings (Workers 0, Scope ClassLevel) for the PARALLEL regime
  • Data source or fixture: QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherFixture.cs - the UiThreadDispatcherFixture / UiThreadDispatcherTransaction pair, whose TransactionGate is the subject

Steps to Reproduce

  1. Read QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherFixture.cs and confirm the current shape of TransactionGate: a SemaphoreSlim(1, 1) acquired by BeginTransactionAsync and released by ReleaseTransactionGate from the transaction's Dispose.
  2. Observe that the acquisition is awaited with no timeout argument and no CancellationToken overload, so a caller that finds the permit held waits without bound.
  3. Construct the state the hypothesis requires: an async test that acquires a transaction and then exceeds its MSTest [Timeout(...)] bound before reaching the Dispose that releases the permit. MSTest abandons a timed-out async test rather than unwinding it, so the finally that would release the permit is no longer observed.
  4. Run any later test in the same assembly that acquires the gate.
  5. The open question is whether step 4 then blocks without bound on a permit no live holder owns. This has NOT been demonstrated, and demonstrating or refuting it is the work of this issue.

Expected Behavior

A one-permit gate either releases its permit on every path out of a transaction, including abandonment of a timed-out async test, or it acquires with a bounded timeout so that a lost permit surfaces as a prompt, diagnosable failure rather than an unbounded wait. A later test must not be able to block indefinitely because an earlier test was abandoned.

Actual Behavior

Unknown, and that is the defect being filed. Issue 743's instrumented measurement was designed to settle it and did not. Both instrumented runs recorded timeout=0 - no test was abandoned in either run. Because H-LEAK is by definition a cascade conditional on a prior expiry, the absence of any expiry meant no leak could have occurred under either hypothesis, so the pre-declared observable (contended, the count of acquisitions that find the permit held) read zero for a reason entirely independent of whether H-LEAK is true of this codebase. The serial reading acquisitions=11 releases=10 contended=0 was predetermined by the absence of expiry and carries no information about the hypothesis.

A hypothesis cannot be rejected by the absence of observations. Issue 743's verdict artifact originally claimed H-LEAK was "REJECTED by direct observation", and a first amendment then claimed the rejection "rests entirely on the counter observable, which is a legitimate basis". Both claims were wrong and both have been withdrawn on the 743 branch; the artifact now records that neither hypothesis was discriminated. See docs/features/active/2026-09-02-quickfiler-itemviewer-ui-marshalling-seam-743/evidence/baseline/ac1-mechanism-verdict.2026-09-12T17-00.md, Correction 2.

Logs / Screenshots

  • Attached minimal logs or screenshot
  • Snippet: instrumented GATECOUNTERS triples from issue 743, both taken on 2026-09-13 from the same instrumented assembly on an otherwise-idle machine with Outlook closed:
    • SERIAL regime, 1394 tests: acquisitions=11 releases=10 contended=0, balance test passed with difference exactly 1, timeout=0, failed=0.
    • PARALLEL regime (Workers 0, Scope ClassLevel), 1395 tests: acquisitions=19 releases=18 contended=14, balance test passed with difference exactly 1, timeout=0, failed=0.
    • The 14 contended acquisitions in the parallel regime are live-holder queueing by distinct test classes, not leaks; the balance test confirms it. Neither regime produced the expiry that the discriminating observation requires.

Impact / Severity

  • Blocker
  • High
  • Medium
  • Low

Medium rather than High because the failure mode is confined to the test assembly and has not been shown to affect the shipped add-in, and because issue 743's seam removes the affected tests from the gate's path. Medium rather than Low because an unbounded wait on a lost permit presents as a hung or timed-out test run whose cause is not local to the failing test, which is expensive to diagnose and is a plausible contributor to the historical flake rate recorded in issues 592, 511 and 571 that 743 was opened to address.

Source

From: docs/features/potential/2026-09-13-quickfiler-transactiongate-permit-leak-unexcluded.md

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions