Skip to content

Fix the flaky timer precision and Windows handle-count gates (#30, #26) - #32

Merged
proggeramlug merged 3 commits into
mainfrom
lane/flakes
Sep 15, 2026
Merged

proggeramlug merged 3 commits into
mainfrom
lane/flakes

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #30 and #26. Report with the full evidence: docs/lanes/flakes.md.

#30 timer_precision: the bound sat below the platform floor

  • Lateness on windows-2025 is bimodal: a ~300 µs fast mode and a ~800 µs slow mode, with 2 of 20 samples in the slow mode on an idle runner (diag run 34957467265). The reported failures (756.2 µs on main run 34947726746, 594.2 µs on run 34950806076) are the slow mode taking the median, not the fast mode moving.
  • spikes/iocp/WINDOWS_RESULTS.md had already measured 275.2 µs p50 for this same 250 µs deadline against a fixed 500 µs bound.
  • New gate, two independent clauses, both reported with min/2nd/p50/p90/max:
    • Capability: the second-best of twenty expiries must meet the floor. Load only makes samples later, so no host noise excuses it.
    • Typical case: the median must be within max(floor, 2 × the host's own interleaved thread::sleep median).
  • The calibration never touches the Driver. It tracks the loop within a few percent on windows-2025, ubuntu-24.04 and both WASI targets, and is 8.7× too pessimistic on macos-15 (nanosleep coalescing) — which is why the capability clause does not depend on it. Retries are bounded at 3 and only for a demonstrably loaded host.
  • Mutation proof (macOS): a 5 ms wait floor fails 3/3; rounding deadlines up to whole milliseconds fails 3/3, and again under CPU load. Both trip the capability clause even when a loaded calibration widened the median allowance. The Windows arm of the mutation proof is UNRUN.

#26 Windows handle count: not a leak

  • The outstanding handle is a WaitCompletionPacket, identified by walking the handle table with NtQueryObject. RegisterWaitForSingleObject is backed by a thread-pool packet; UnregisterWaitEx(INVALID_HANDLE_VALUE) joins the callback, then the pool closes its own packet on its own threads.
  • A 192-cycle sweep on windows-2025 (run 34954538795): 0 movements idle, 117–138 movements loaded, each an oscillation 130 → 131 → 130. Nothing accumulates.
  • Fix: the comparison stays exact; only the sampling waits, requiring the count to return to the baseline within a bounded 5 s. Under load (run 34955502692): 183 of 576 cycles had a packet outstanding, worst wait 1.646 ms, none reached the deadline. Failures now name the handle.
  • Follow-up (not done): moving child-exit notification to NtAssociateWaitCompletionPacket on the driver's own port would remove the thread pool from this path.

CI

Three fully green runs: 34956471189 and 34957283051 (consecutive, same code) and 34958380080 on the final SHA, 38/38 jobs each.

Summary by CodeRabbit

  • Bug Fixes

    • Improved timer-precision checks across supported platforms, reducing false failures under host-system load.
    • Improved Windows resource cleanup validation by allowing asynchronous thread-pool cleanup to settle before reporting failures.
    • Added clearer diagnostics for Windows handle differences when cleanup does not complete.
  • Documentation

    • Documented the causes, fixes, and verification results for timer-precision and Windows cleanup reliability issues.

timer_precision compared the loop's median expiry lateness for a 250 us deadline
against a fixed 500 us (native) / 2 ms (WASI) bound. The M1 Windows spike had
already measured 275.2 us p50 on real hardware, so that bound carried under a
factor of two of clear air, and a loaded CI VM crossed it with no backend
regression: 756.2 us on main run 34947726746 and 594.2 us on PR #29 run
34950806076 within an hour, and 2.31 ms on WASI 0.3 during #22, whose samples
spanned 1.07-6.44 ms. Each failure blocked a merge or skipped the release
workflow (issue #30).

Two independent requirements replace the single fixed median.

Capability: the second-best expiry of twenty must still meet the same platform
floor. Load only ever makes a sample later, never earlier, so this clause cannot
be excused by a loaded or mismeasured host, while a wait floor or a deadline
rounded up to milliseconds raises every sample and fails it.

Typical case: the median must stay within the larger of that floor and twice
what this host achieves with its own sleep, measured interleaved with the
expiries so a load spike moves the bound as well as its subject. The calibration
never touches the Driver, so no backend regression can move it. Measured on CI,
it tracks the loop within a few per cent on windows-2025 (280.5-292.2 us against
285.1-298.5 us), ubuntu-24.04 (65.5 us against 69.6 us) and both WASI targets,
and is 8.7x too pessimistic on macos-15, which coalesces nanosleep to 1.66 ms
while the loop resolves 189.7 us -- exactly why the capability clause does not
depend on it.

Only a demonstrably loaded host retries, at most three times: an attempt whose
own sleep stays inside half the quiet-host floor fails immediately. Every
attempt reports min/2nd/p50/p90/max for both measurements and which clause it
missed.
loop_drop_terminates_live_children_and_releases_their_handles sampled
GetProcessHandleCount once per cycle and compared it to an exact baseline. It
failed on windows-2025 in PR #24's run 34941544032 with 83 against 82, on a PR
that only changed release scripts (issue #26).

The outstanding handle is a WaitCompletionPacket, and it is not leaked. The
backend learns a child exited through RegisterWaitForSingleObject, which the
thread pool backs with such a packet. UnregisterWaitEx(INVALID_HANDLE_VALUE) is
the strongest join Windows documents and it joins the callback; the pool closes
its own packet afterwards, on its own threads. An instrumented 192-cycle sweep
on windows-2025 (run 34954538795) saw the table oscillate 130 -> 131 -> 130,
gaining and releasing exactly one WaitCompletionPacket, on 117 to 138 of the 192
rounds with eight busy threads, and move zero times on the same idle runner.

So the comparison stays exact and gains a bounded deadline instead of a single
sample: settled() requires the count to be exactly the baseline again within
five seconds. A leaked handle never comes back and still fails, and now names
itself -- handle_table walks this process's handle table with
GetHandleInformation and NtQueryObject(ObjectTypeInformation), and the failure
reports what the table gained and released. The two other whole-process handle
comparisons in this file get the same treatment for the same reason.
@coderabbitai

coderabbitai Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

Caution

Review failed

Failed to post review comments.

GitHub was unavailable or timed out while CodeRabbit was posting the review. Please request a new review later if the pull request still needs one. This happened while posting 2 inline comments. Use @coderabbitai full review to retry the review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: c852b2ce-cf4f-4f50-833c-a430e67fbf62

📥 Commits

Reviewing files that changed from the base of the PR and between ed5ca4c and e73b5a5.

📒 Files selected for processing (4)
  • crates/turnloop-contract/Cargo.toml
  • crates/turnloop-contract/src/lib.rs
  • crates/turnloop-contract/tests/windows_lifetimes.rs
  • docs/lanes/flakes.md

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

⏰ Context from checks skipped due to timeout. (30)
  • GitHub Check: lint-native (ubuntu-24.04)
  • GitHub Check: test-native (ubuntu-24.04, default)
  • GitHub Check: test-native (ubuntu-24.04, all-features)
  • GitHub Check: test-native (macos-15, executor)
  • GitHub Check: test-native (ubuntu-24.04-arm, fallbacks)
  • GitHub Check: test-native (ubuntu-24.04, epoll-timerfd)
  • GitHub Check: test-native (macos-15, default)
  • GitHub Check: test-native (ubuntu-24.04, fallbacks)
  • GitHub Check: test-native (windows-2025, all-features)
  • GitHub Check: test-native (ubuntu-24.04-arm, process-sigchld)
  • GitHub Check: test-native (windows-2025, default)
  • GitHub Check: test-native (ubuntu-24.04-arm, executor)
  • GitHub Check: test-native (ubuntu-24.04, executor)
  • GitHub Check: test-native (macos-15, all-features)
  • GitHub Check: test-native (windows-2025, executor)
  • GitHub Check: test-native (ubuntu-24.04, process-sigchld)
  • GitHub Check: test-native (ubuntu-24.04-arm, epoll-timerfd)
  • GitHub Check: test-native (ubuntu-24.04-arm, all-features)
  • GitHub Check: lint-native (macos-15)
  • GitHub Check: lint-native (ubuntu-24.04-arm)
  • GitHub Check: protocol
  • GitHub Check: test-native (ubuntu-24.04-arm, default)
  • GitHub Check: lint-native (windows-2025)
  • GitHub Check: protocol-wasi (wasm32-wasip3, nightly-2026-09-07)
  • GitHub Check: wasi (wasm32-wasip3, nightly-2026-09-07)
  • GitHub Check: lint-wasm (wasm32-unknown-unknown)
  • GitHub Check: protocol-wasi (wasm32-wasip2, nightly-2026-08-20)
  • GitHub Check: wasi (wasm32-wasip2, nightly-2026-08-20)
  • GitHub Check: web
  • GitHub Check: lint-wasm (wasm32-wasip2)
🧰 Additional context used
🪛 LanguageTool
docs/lanes/flakes.md

[style] ~74-~74: Consider an alternative for the overused word “exactly”.
Context: ...its above its 500 µs constant. That is exactly what the capability clause keys on: the...

(EXACTLY_PRECISELY)


[grammar] ~135-~135: Use a hyphen to join words.
Context: ...lause both mutations trip. ### Mutation proof Neither mutation touches the cali...

(QB_NEW_EN_HYPHEN)


[locale-violation] ~189-~189: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...* — the pool then closes its own packet afterwards, on its own threads. `GetProcessHandleC...

(AFTERWARDS_US)


[style] ~213-~213: Consider an alternative for the overused word “exactly”.
Context: ...r busy enough to delay a pool thread is exactly when a single sample lands on the 131. ...

(EXACTLY_PRECISELY)

🪛 markdownlint-cli2 (0.23.2)
docs/lanes/flakes.md

[warning] 203-203: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

📝 Walkthrough

Walkthrough

The pull request replaces the fixed timer median check with calibrated, bounded validation. It also adds delayed Windows handle settling and named diagnostics. Documentation records the causes, fixes, and verification results for both CI flakes.

Changes

CI flake fixes

Layer / File(s) Summary
Timer precision validation
crates/turnloop-contract/src/lib.rs, docs/lanes/flakes.md
The timer check now compares expiry samples with platform and host-calibrated thresholds, retries up to three attempts, and reports distributions on failure.
Windows handle settling
crates/turnloop-contract/Cargo.toml, crates/turnloop-contract/tests/windows_lifetimes.rs, docs/lanes/flakes.md
Windows leak checks wait for asynchronous handle release, preserve exact baseline comparison, and report named handle differences. The Windows dependency enables Wdk_Foundation.
Verification record
docs/lanes/flakes.md
The documentation records mutation tests, diagnostic runs, toolchain checks, and CI results.

Priority: ⬆️ High

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: High

Sequence Diagram(s)

sequenceDiagram
  participant timer_precision
  participant precision_check
  participant Driver
  participant thread_sleep
  timer_precision->>precision_check: run check
  precision_check->>Driver: measure timer expiry
  precision_check->>thread_sleep: measure host sleep
  precision_check-->>timer_precision: return or panic after retries
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Windows handle-count changes are separate from issue #30. They change windows_lifetimes.rs, add handle_table and handle_diff diagnostics, add settling waits, and enable Wdk_Foundation. The… Move the Windows handle-count changes to a PR linked to #26, or provide #26 as a directly linked issue with its coding requirements before including that work in this PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 2 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies both main fixes: the flaky timer precision gate and the Windows handle-count gate. It matches the pull request objectives and changeset.
Linked Issues check ✅ Passed Issue #30 requires a timer check that handles loaded hosts without weakening quiet-host guarantees and still detects real precision regressions. The PR adds a capability floor: the second-best of 20 e…
Full details: Out of Scope Changes check

Explanation

The Windows handle-count changes are separate from issue #30. They change windows_lifetimes.rs, add handle_table and handle_diff diagnostics, add settling waits, and enable Wdk_Foundation. The supplied linked issue contains only #30, and its timer objective does not require Windows handle lifecycle changes. The PR summary attributes this work to #26, but no #26 issue requirements are supplied for this assessment.

Full details: Docstring Coverage

Explanation

Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 2 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lane/flakes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug merged commit e949b02 into main Sep 15, 2026
77 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.

Flaky timer_precision on loaded CI hosts (Windows 756µs; WASI 0.3 2.31ms)

1 participant