Skip to content

test(gap): de-flake test_gap_fs_fd_2749 — it raced the libuv threadpool#6400

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/fs-fd-2749-threadpool-order
Jul 14, 2026
Merged

test(gap): de-flake test_gap_fs_fd_2749 — it raced the libuv threadpool#6400
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/fs-fd-2749-threadpool-order

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

test_gap_fs_fd_2749 is flaky, and it has been silently red-lighting unrelated PRs. It took out #6397, #6398 and #6399 today — each of which had it as their only untriaged failure — and it cost a full bisect before the oracle itself became the suspect.

The test races the libuv threadpool

The three callback-form fd mutators are dispatched concurrently:

fs.ftruncate(fd, 1, (err) => cbCode("ftruncate closed cb", err));
fs.futimes(fd, 1, 2, (err) => cbCode("futimes closed cb", err));
fs.fchown(fd, 0, 0, (err) => cbCode("fchown closed cb", err));

Those land on the libuv threadpool and their callbacks fire in completion order, which is not deterministic across three different syscalls.

The gap harness compares perry's output against a freshly run node oracle — and the oracle itself is unstable. Measured directly:

distinct outputs
node --experimental-strip-types, 40 runs 4
perry, 40 runs 1

Node emits fchown closed cb before futimes closed cb on roughly one run in six:

   ftruncate closed cb: EBADF ftruncate
-  futimes closed cb: EBADF futime      <- ~84% of runs
-  fchown closed cb: EBADF fchown
+  fchown closed cb: EBADF fchown       <- ~16% of runs
+  futimes closed cb: EBADF futime

Perry is deterministic (always submission order), so it disagrees with the oracle whenever the oracle takes the minority path. That is a coin flip on every PR, and it is why the failure looked like a regression: it correlated with nothing.

The fix

The test is about the error surface (err.code / err.syscall) of each fd mutator, not about libuv's scheduling. Buffer the three callback results and flush them in a fixed order once all three have landed.

Every assertion is preserved, and the emitted 10 lines are byte-for-byte what they were — only the print order is pinned.

Verification

before after
node, 60 runs 4 distinct 1 distinct
perry, 40 runs 1 distinct 1 distinct
perry vs node flaky byte-identical
expected output unchanged (same 10 lines)

Deliberately left alone

While restructuring I briefly sequenced the promisified forms by calling main() from the last callback, and that surfaced a separate, real perry bug: awaiting a promisify(fs.ftruncate) rejection from inside an fs callback yields undefined undefined instead of EBADF ftruncate. That is not this PR's business — filing it separately with a repro rather than folding an unrelated runtime bug into a test de-flake. main() stays at top level here, exactly as before.

Summary by CodeRabbit

  • Tests
    • Stabilized callback error-reporting test output by enforcing a consistent result order.
    • Ensured callback results are collected before being displayed, reducing variability across runs.

The three callback-form fd mutators are dispatched to the libuv threadpool
concurrently, so their callbacks fire in completion order, which is not
deterministic. The gap harness compares perry against a freshly run node
oracle -- and the *oracle itself* is unstable: across 40 runs of
`node --experimental-strip-types` this test produced 4 distinct outputs,
emitting `fchown closed cb` before `futimes closed cb` on roughly one run
in six. Perry is deterministic here (always submission order), so the test
red-lit at random on unrelated PRs -- it took out PerryTS#6397, PerryTS#6398 and PerryTS#6399
today, and cost a full bisect before the oracle was suspected.

The test is about the error surface (err.code / err.syscall) of each fd
mutator, not about libuv's scheduling. Buffer the three callback results and
flush them in a fixed order once all three have landed. Every assertion is
preserved and the emitted 10 lines are byte-for-byte what they were; only the
print order is pinned.

After: node emits 1 distinct output across 60 runs, perry 1 across 40, and the
two are byte-identical.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b2447cf-5db0-4b72-8ae3-4ced7fbebf87

📥 Commits

Reviewing files that changed from the base of the PR and between 5a233c5 and e36420e.

📒 Files selected for processing (1)
  • test-files/test_gap_fs_fd_2749.ts

📝 Walkthrough

Walkthrough

The callback-form test now buffers all three fd-mutator results and prints them in a fixed order after every callback has completed, removing output variability caused by callback completion order.

Changes

Callback output determinism

Layer / File(s) Summary
Buffer and order callback results
test-files/test_gap_fs_fd_2749.ts
Defines CB_ORDER, stores callback results in cbLines, waits for all callbacks, and prints results according to the fixed order.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the test de-flake and matches the change to stabilize test_gap_fs_fd_2749.
Description check ✅ Passed The description covers the summary, fix, and verification, though it omits the template's related issue and checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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.

1 participant