Repository navigation
Bug: qfc-item-controller-init-tests-flaky-window-handle #571
Description
Activity
- added 7 commits that reference this issue
on Aug 21, 2026 Premise correction from the epic execution run (2026-08-22)
Investigated under epic
quickfiler-suite-determinism-foundation. The child halted rather than land a fix, because the stated root cause is falsified by measurement. Recording it here so the issue text is not acted on as written.What was measured
WinFormsPumpHost.RunPumpThreaddoes callApplication.Run(new ApplicationContext())without ever adding a form or control. But that does not mean the viewer lacks a window handle:QuickFiler.ItemViewer's constructor callsInitializeComponent(), which runs((ISupportInitialize)_l0v2h2_WebView2).BeginInit()/.EndInit()and the same pair for_l0vhBreadcrumb_WebView2—QuickFiler/Viewers/ItemViewer.Designer.cs:89-90and:6166-6167.EndInitcreates the WebView2 child window handles, and WinForms creates a parent's handle when a child's handle is created. TheItemViewer's own handle therefore exists the instant construction returns.- A bare
new QuickFiler.ItemViewer()on the pump thread — no harness, noSaveParameters, no.Handleread — already reports both children as handle-created.
So
Control.Invokewas never reached without a handle in this fixture, and the proposed remedy (forcing the handle on the pump thread before the act) is a measured no-op: with the statement present and with it commented out, the measured handle state is identical.What the real failure looks like
The one genuine pre-fix failure was seven expiries at the 60,000 ms
PumpTimeoutMsunder machine load. A missing handle makesControl.Invokethrow immediately; it does not hang for sixty seconds. Handle-forcing cannot address a timeout.The post-fix evidence does not demonstrate efficacy either. Pre-fix run-level failure rate is roughly 1 in 21 (~4.8%), so thirty consecutive clean runs has probability ≈
0.952^30≈ 0.23 under the null hypothesis of no effect — about a one-in-four chance of producing that record with no fix at all. Under the 17-node MSBuild contention that reproduced the original failure, a supplementary pass was only 8 of 10 green: the suite still fails under load.Where the work is preserved
Branch
bug/winformspumphost-suite-determinism-511-exec, commit53a2a08f— 36 markdown evidence artifacts includingevidence/regression-testing/webview-child-handle-measurement.2026-08-21T18-10.md, which records the four measurements. Nothing was merged. No production file was modified, and both coverage-bearing production files hash to their exact merge-base blobs, so the coverage justifications this issue's sibling depends on are untouched.The real defect is tracked separately; see the linked issue.
- added a commit that references this issue
on Aug 22, 2026 Closing as superseded by #592 — the symptom is real and still tracked
To be unambiguous: the flakiness described here is real and is not dismissed. It is measured at roughly a 1-in-21 (~4.8%) run-level failure rate, and reproduces under load at 8 of 10 green under induced 17-node MSBuild contention. What is wrong is the diagnosis, and that is why this issue is being closed rather than left open.
Why closing rather than correcting in place
Three reasons.
1. The stated root cause is falsified. This issue attributes the failure to a missing window handle on
ItemViewer, and its remedy forces handle creation. Verified against source:ItemViewer()callsInitializeComponent()—QuickFiler/Viewers/ItemViewer.cs:25InitializeComponentrunsBeginInit()on both WebView2 children —QuickFiler/Viewers/ItemViewer.Designer.cs:89-90- and
EndInit()on them —:6166-6167 EndInitcreates the child handles, and WinForms creates a parent's handle when a child's handle is created
The viewer's handle therefore already exists the instant construction returns. The remedy would force a handle that is already present — a no-op.
2. The failure signature contradicts the diagnosis.
PumpTimeoutMs = 60000(QuickFiler.Test/Controllers/QfcItemController.InitializationTests.cs:38, applied as[Timeout(PumpTimeoutMs)]). The one genuine pre-fix failure was seven expiries at 60,000 ms, not an exception. A missing handle makesControl.Invokethrow immediately; it does not hang for sixty seconds.3. A premise correction in a comment is not enough. The issue body still reads authoritatively, and a body is what gets implemented. This has already happened once: the epic-planning pass was given the seam-dependency premise straight from this issue text and had to refute it against source. Leaving the issue open with a corrective comment preserves the trap for the next reader.
#511 and #571 also contradict each other
Independently of the false premise, these two cannot both be implemented as written. #511 proposes replacing the real message pump with an injectable context; executed literally that deletes the very tests #571 exists to stabilize. That conflict disappears once both are superseded by a single correctly-diagnosed issue.
Where the work went
- Pump-hosted QfcItemController tests expire at the 60s PumpTimeoutMs under CPU contention (real cause behind #511/#571) #592 carries the real defect, the measurements, and an explicitly-labelled hypothesis to test — that
UiThreadDispatcherGate/SwapUiThreadDispatcherinQfcItemController.InitializationTests.Part2.csmutate the process-wide staticUtilitiesCS.UiThread._dispatcherby reflection, and thatQfcItemController.SeamFactoryTestsandQfcItemController.InitializationTestscontend on that gate. A sixty-second expiry under CPU starvation is consistent with gate contention or a lock-ordering stall. Stated there as a lead, not a conclusion. - Evidence and the halted implementation are preserved unmerged on branch
bug/winformspumphost-suite-determinism-511-execat commit53a2a08f, underdocs/features/active/winformspumphost-suite-determinism-511/evidence/. - Investigated under epic
quickfiler-suite-determinism-foundation. The child halted rather than ship, because landing the change would have written a false claim into repository history and auto-closed two issues that are not fixed. That epic's other three children (Bug: quickfiler-keyboard-action-contract-defects #445, Bug: quickfiler-explorer-controller-latent-defects #449, Bug: quickfiler-test-form1-live-form #491) delivered and merged in PR fix(quickfiler): suite-determinism foundation epic — remove live test form, repair keyboard-action and explorer-controller contract defects (#445, #449, #491) #595.
Reopen this issue if the #592 investigation shows the handle attribution was correct after all.
- added 8 commits that reference this issue
on Aug 23, 2026 Reconciliation from issue #743 (2026-09-13)
This comment reconciles the closing state of #571 against the current tree and the resolution delivered under #743.
(a) The premise correction stands
The premise correction recorded above on 2026-08-22, which refutes the window-handle root cause, still holds against the current tree and was not in error. Quoting it: "
EndInitcreates the WebView2 child window handles, and WinForms creates a parent's handle when a child's handle is created. TheItemViewer's own handle therefore exists the instant construction returns." Re-verified on branchbug/quickfiler-itemviewer-ui-marshalling-seam-743:ItemViewer()callsInitializeComponent()atQuickFiler/Viewers/ItemViewer.cs:25;InitializeComponentrunsBeginInit()on both WebView2 children atQuickFiler/Viewers/ItemViewer.Designer.cs:89-90andEndInit()on them atQuickFiler/Viewers/ItemViewer.Designer.cs:6165and:6166. One citation correction only: the two earlier comments cite theEndInit()pair as lines 6166-6167; in the current tree the pair sits at lines 6165 and 6166 (line 6169 is the_topicThreadEndInit()). The window-handle root cause remains refuted, and forcing the handle remains a measured no-op.(b) Forward pointer
The forward pointer to #592 is stale: #592 is closed and consolidated into #743 (
2026-09-02-quickfiler-itemviewer-ui-marshalling-seam-743). #743 carries the resolution:QfcItemController.ResolveControlGroupsAsyncis widened from the concreteItemViewertoIItemViewerthrough two additive interface members (DescendantControls()andItemNumberLabel), and theAssignControlsAsyncmarshal is routed through the injectedIUiDispatcherseam with the same null tolerance the other seam sites carry. The member is therefore driven from a deterministic seam test class,QuickFiler.Test/Controllers/QfcItemController.SeamMarshallingTests.cs, with no pump host and no concrete viewer (fail-before three of three runs withInvalidCastException, pass-after three of three, then 62 consecutive targeted runs with zero failures). The retained pump-hosted testResolveControlGroupsAsync_ThroughThePumpHost_PopulatesTipsAndControlGroups, which #571 exists to stabilize, is unchanged. Evidence lives underdocs/features/active/2026-09-02-quickfiler-itemviewer-ui-marshalling-seam-743/evidence/on that branch.(c) The gate hypothesis is superseded
The
UiThreadDispatcherGate/SwapUiThreadDispatcherhypothesis restated in the closing comment above is superseded. Per correction C1 in the #743 spec, those two identifiers exist in zero.csfiles in the current tree (they were removed under #493; the mechanism that exists today isUiThreadDispatcherFixture/UiThreadDispatcherTransaction). The mechanism was then identified by measurement rather than inference (AC1 verdict artifactevidence/baseline/ac1-mechanism-verdict.2026-09-12T17-00.md): three monotonic counters on the one-permitTransactionGaterecorded, in a serial-regime run of the wholeQuickFiler.Testassembly,acquisitions=11 releases=10 contended=0, with a balance test holding the permit and asserting acquisitions minus releases equals exactly 1. A serial run cannot queue a second live holder, so a contended count of zero rejects the gate-leak hypothesis (H-LEAK) by direct observation. The surviving mechanism is elapsed pump-hosted fixture cost under load (H-COST): the sixThroughThePumpHosttests measured 68-125 ms serially and up to 6,460 ms under class-level parallelism alone, and the recorded 6x-26x load multiplier applied to the latter exceeds the 60,000 msPumpTimeoutMsbound. No expiry was reproduced during the instrumented runs; that is recorded as a negative result, and the identification rests on the counter observable rather than on an observed expiry.
Summary
Two tests in
QuickFiler.Controllers.Tests.QfcItemController_InitializationTestsfail intermittently during a full-suite run with
InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created, but pass every time when the class isrun in isolation. The tests exercise a real WinForms
Control.Invokepath withno seam, so they depend on whether the control's window handle happens to exist
when the test reaches it.
Environment
vstest.console.exe <9 test assemblies> /EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook"Steps to Reproduce
*.Test.dllassemblies with the commandabove.
Observed on 2026-08-15: run 1 passed both tests, run 2 failed both, run 3 (full
suite) passed both. Running only
/TestCaseFilter:"FullyQualifiedName~QfcItemController_InitializationTests"passed 9 of 9 on every attempt.
Expected Behavior
Per
.claude/rules/general-unit-test.md, tests are deterministic: identicalinputs and environment produce identical results, and the suite does not depend
on ordering or on ambient UI state.
Actual Behavior
Both of the following fail non-deterministically:
InitializeNineArgOverload_ThroughThePumpHost_SavesParametersAndDelegatesInitializeBool_ThroughThePumpHost_CompletesAndInitializesStateLogs / Screenshots
2026-08-15.
Impact / Severity
The failure is intermittent and does not indicate a production defect, but a
flaky test in a protected gate is corrosive: it trains reviewers to re-run
rather than investigate, and it can fail an otherwise-green CI run at random.
Source
From: docs/features/potential/2026-08-15-qfc-item-controller-init-tests-flaky-window-handle.md