Skip to content

ci: hunt flakes on Windows too, where the exit edge is synthesized - #291

Merged
vyncint merged 2 commits into
mainfrom
stress-windows
Sep 8, 2026
Merged

ci: hunt flakes on Windows too, where the exit edge is synthesized#291
vyncint merged 2 commits into
mainfrom
stress-windows

Conversation

@vyncint

@vyncint vyncint commented Sep 8, 2026

Copy link
Copy Markdown
Owner

The stress workflow ran on Linux and macOS only, which left the leg that became required in 0.10 as the least exercised — and it is the one whose liveness the harness manufactures rather than reads.

On Unix "the terminal closed" is a kernel fact: EOF on the master means every slave descriptor is closed. On Windows the pseudoconsole's output pipe outlives the child, so EXIT_CLOSES_THE_TERMINAL + ExitWatch synthesize the edge — reap the child, drain for DRAIN_GRACE (500 ms), call it closed. Polling plus a grace window is the shape a one-in-N flake lives in, and nothing had run it more than once per commit. The other Windows-only path is common::fixture_bin, which shells out to cargo build -p from inside the tests, on the one filesystem that refuses to relink a running .exe.

Three things this is not:

  • Not a blind matrix add. windows-latest defaults to pwsh and the iteration loop is POSIX shell, so the job pins defaults.run.shell: bash. Git Bash ships seq and arithmetic expansion, so one script still serves all three; on Unix the only change is an unused pipefail.
  • Not the same clock. A Windows iteration costs two to three times a Linux one and the serial shard is the long pole, so Windows gets 60 minutes against 30 — a timeout that fires on a healthy run is a false alarm that costs more than the minutes it saves.
  • Not the same faults at the ends of the thread axis, and the comment now says so: at 1 thread Windows hunts the exit synthesis rather than the macOS teardown race, at 16 it hunts conhost churn on a four-core box.

The 94 #[cfg_attr(windows, ignore)] tests stay ignored — that is what ConPTY cannot honour, not what is untested. README and CONTRIBUTING §3 now name the third OS.

Proof, not just intent: a real 20-iteration dispatch of the new workflow ran against this branch before merge — https://github.com/vyncint/termlens/actions/runs/34200079944

Closes #290

The stress workflow ran on Linux and macOS only, which left the leg that
became required in 0.10 as the one least exercised — and it is the one
whose liveness the harness manufactures rather than reads.

On Unix "the terminal closed" is a kernel fact: EOF on the master means
every slave descriptor is closed. On Windows the pseudoconsole's output
pipe outlives the child, so `EXIT_CLOSES_THE_TERMINAL` and `ExitWatch`
synthesize the edge instead — reap the child, drain for `DRAIN_GRACE`,
call it closed. Polling plus a grace window is the shape a one-in-N flake
lives in, and nothing had run it more than once per commit. The other
Windows-only path is `common::fixture_bin`, which shells out to `cargo
build -p` from inside the tests, on the one filesystem that refuses to
relink a running `.exe`.

Three things this is not. Not a blind matrix add: `windows-latest`
defaults to `pwsh` and the iteration loop is POSIX shell, so the job now
pins `defaults.run.shell: bash` — Git Bash ships `seq` and arithmetic
expansion, so one script still serves all three, and on Unix the only
change is an unused `pipefail`. Not the same clock: a Windows iteration
costs two to three times a Linux one and the serial shard is the long
pole, so Windows gets sixty minutes against thirty. Not the same faults
at the ends of the thread axis, and the comment says so — at 1 thread it
hunts the exit synthesis rather than the macOS teardown race, at 16 it
hunts conhost churn on a four-core box.

The 94 `#[cfg_attr(windows, ignore)]` tests stay ignored: they are what
ConPTY cannot honour, not what is untested. The README's and CONTRIBUTING
§3's "Linux and macOS" now name the third.

Closes #290

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
`a_query_the_app_moved_past_is_context_not_a_cause` spent one 400ms budget
on both of its waits. The first must *succeed* — and the fixture
deliberately sleeps 200ms before printing the marker it waits for, so the
budget covered a spawn, a 200ms pause and the read with 200ms to spare. On
a loaded macOS runner at four threads that margin went, and the test failed
on its own setup line while asserting about someone else's diagnosis.

The deadline now sits where it is actually needed: the terminal gets ten
seconds, and the wait that must expire gets its 400ms through
`wait_until_for`. That is the split `probe` already makes in this file, and
the three sibling tests on 400ms are unaffected — their single wait is the
one that must time out, with no output to wait for in front of it.

Found by the stress workflow's first run on the three-OS matrix, at
iteration 5 of 5 on the macOS four-thread shard.

Refs #290

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint

vyncint commented Sep 8, 2026

Copy link
Copy Markdown
Owner Author

Dispatch results — the leg works, and it immediately earned its keep.

Two real dispatches of the new workflow against this branch:

run iterations Windows shards outcome
34200079944 20 5/5 green found a queries.rs flake on macOS @4 threads
34201210388 40 5/5 green found a frames.rs flake on macOS @1 thread

Neither flake is Windows and neither is caused by this change — both are pre-existing macOS timing faults that a deeper loop surfaced, which is the entire point of the workflow.

  • Fixed here (second commit): a_query_the_app_moved_past_is_context_not_a_cause spent one 400 ms budget on both a wait that must succeed and a wait that must expire, with a deliberate 200 ms fixture sleep in between. Root-caused, fixed by moving the short deadline onto the wait that needs it, and hammered 40× locally at 8 threads with zero failures.
  • Filed, not guessed at: A resize event goes missing on macOS: the app never acknowledges the SIGWINCH and wait_frame runs out its deadline #292 — a resize notification going missing on macOS so form-echo never acknowledges the SIGWINCH (last: empty, "1 complete frame in total" after 10 s). That is not the frame-cursor gap this test was originally written for, it did not reproduce in 30 local Linux runs, and widening a deadline would suppress it rather than fix it. It deserves its own investigation and does not block this change.

Sizing, now measured rather than estimated: 40 iterations cost ~10 min per Windows shard, so a full 100 extrapolates to ~25 min plus the ~6 min prebuild — comfortably inside the 60 minutes this PR gives Windows, with room for a slow runner day.

@vyncint
vyncint merged commit ba72142 into main Sep 8, 2026
29 of 30 checks passed
@vyncint
vyncint deleted the stress-windows branch September 8, 2026 08:05
@vyncint vyncint mentioned this pull request Sep 8, 2026
vyncint added a commit that referenced this pull request Sep 8, 2026
Patch release: the seven defects fixed in #301, plus the Windows stress leg from #291. Gated on a 15/15 green stress run across all three OSes.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
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.

The stress workflow skips the one platform whose liveness is synthesized rather than observed

1 participant