microsoft / microsoft/microsoft-ui-reactor

[Flaky] LT_Unmounts_Exactly_30 / LT_NoLeak_MountsEqualUnmounts — real unmount-accounting deficit, not the #988 watchdog

Open
#994 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
646
Forks
54
Avg merge
1d 3h
Merged PRs (30d)
84

Description

### Summary

`LT_Unmounts_Exactly_30` and `LT_NoLeak_MountsEqualUnmounts` (`LifecycleTortureFixtures.cs`, AOT Selftests) fail intermittently. They were initially swept into #988 (the 300 s selftest watchdog overrun) — **incorrectly**. Applying that issue's own discriminator rules the watchdog out, and what remains is a genuine nondeterministic defect in unmount accounting.

Filed separately so it isn't buried: if #988 is resolved by raising or removing the watchdog and closed, these two will keep failing, and the natural read will be *"the flake class is fixed, so this must be new."*

### Why it is not the watchdog

From the attempt-1 log of job `91100252584` (538,489 chars):

1. **No watchdog signature anywhere.** `timed out after (\d+)ms` → no match. `in flight` → no match. A budget kill prints both.
2. **The run did not die.** Failures at 07:38:38; the suite continued and completed normally at **07:42:38** with `# Total failures: 2` and exit code 1 — four more minutes of passing tests afterwards. A 300 s process kill truncates the run and marks everything after the in-flight fixture `Skipped`; it does not produce a clean summary line four minutes later.
3. **The failure shape is wrong for a single in-flight victim:**

```
# Running: LT_OnMountUnmountBalanced
ok LT_Mounts_Exactly_30
not ok LT_Unmounts_Exactly_30 - assertion failed
ok LT_OnMount_Received_Control
not ok LT_NoLeak_MountsEqualUnmounts - assertion failed
# Running: LT_EffectCleanupBalanced
ok LT_Effects_Exactly_20
ok LT_Cleanups_Exactly_20
ok LT_Effects_NoLeak
```

- **Two** failures, not one, separated by a *passing* assertion.
- They are **arithmetically consistent with each other**: unmounts fell short of 30, therefore mounts(30) ≠ unmounts. That is a coherent statement about a real deficit in unmount accounting, not an external kill.
- The sibling leak fixture immediately after **passed cleanly** (effects 20, cleanups 20, no leak), so the harness and runner were healthy on both sides.

### What it probably is

`LifecycleTortureFixtures.cs:70` already uses the correct idiom — `await Harness.WaitFor(() => mounts == 30 && unmounts == 30)` — with a comment documenting the async-unmount-flush lag (the #685 fix). So this is not a missing-wait bug.

Two candidate mechanisms, not yet distinguished:

1. **GC/finalizer timing inside the unmount accounting.** The deficit is in *unmounts*, which are flushed asynchronously; a real shortfall would produce exactly this pair of failures.
2. **`WaitFor` budget shortfall under AOT.** The default is `maxPasses: 25, perPassMs: 0` ≈ 25 × 16 ms ≈ **400 ms**, and `AGENTS.md` notes AOT consistently needs one more wave than JIT. Note this failed on the **AOT Selftests** leg specifically, which is consistent with a budget that is adequate under JIT and marginal under AOT.

If (2), the fix is raising `perPassMs` for this fixture. If (1), it is a real accounting defect. **These are distinguishable**: raise the budget substantially and see whether the failure disappears entirely or merely becomes rarer.

### Reproduction

Intermittent. Observed on PR #967's head (`gh run rerun --failed` on the same run id then passed at 10m52s, giving a clean before/after on identical inputs), and independently on `main` — see the caveat below.

### Caveat on the `main` datapoint

Run `30604098497` (head `03414f22`, `main`, no PR involved) has been cited in #988 as evidence the watchdog condition is baseline-wide. **That sighting should be re-checked with the discriminator before it is used as watchdog evidence** — it may be this bug instead. The "it's the baseline, not the branch" conclusion still holds either way, but the mechanism attribution may not.

### Notes

Diagnosis by the session on PR #967, which also self-corrected its earlier advice of *"if it's these two, just re-run"* — re-running does make it green, but greenness was concealing a real defect.

Related: #988 (selftest watchdog — **not** this), #990 (non-interactive environment degradation — also not this), #685 (the async-unmount-flush fix whose idiom this fixture already uses).

Contributor guide

Open the contributing guide

Research direction

Start with LifecycleTortureFixtures.cs:70 and reproduce the failing LT_Unmounts_Exactly_30 and LT_NoLeak_MountsEqualUnmounts cases on the AOT Selftests leg. Compare behavior after substantially increasing the WaitFor budget, using the logs and AGENTS.md guidance to distinguish timing from an accounting defect. Done means the failure mechanism is established and the fixture no longer reports an unaccounted unmount deficit.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.