Skip to content

render-smoke's under-the-deadline test is timing-marginal, and aborts the whole gate when it flakes #2180

Description

@cliffhall

Summary

scripts/lib/render-smoke.test.mjs's case "a paint landing just under the deadline is not failed by the render timer" (added by #2147) leaves too little slack for process startup, so it fails intermittently under load — and because test:scripts is the 4th step in validate's && chain, a flake there aborts npm run local:gate before it reaches any client. Each occurrence costs a full re-run of the ~20-minute gate.

The margin

const paintAt = 250;
const r = await runRenderSmoke({
  ...stub(`setTimeout(() => { console.log(MARKER); … }, ${paintAt});`),
  timeoutMs: paintAt + 150,   // 400ms
  surviveMs: 400,
});

The stub is a real node -e child that paints 250ms after its own start, while the 400ms deadline runs from spawn. So the whole budget for spawn + Node boot + module init is 150ms. Node's cold start alone is routinely near or past that on a loaded machine.

Observed

Three failures in one session on an otherwise-idle-ish dev machine, two of them back to back:

✖ a paint landing just under the deadline is not failed by the render timer (416.44ms)
  AssertionError: expected pass, got: child did not render "MCP Servers" within 400ms
✖ … (407.93ms)   # same, next run

Both times it passes 3/3 immediately afterwards when run in isolation (node --test scripts/lib/render-smoke.test.mjs), which is what identifies it as a margin problem rather than a defect in runRenderSmoke.

Why not just raise the number

The tightness is deliberate and load-bearing — the comment says so: the render deadline has to expire while the survival window is still open, because that overlap is the bug being pinned. So the fix is not "make timeoutMs bigger"; it has to keep timeoutMs < surviveMs while stopping the child's startup from eating the render budget. Options worth weighing:

  • Have the stub signal readiness and start its paint timer from a point the harness observes, so the measured interval excludes Node boot.
  • Scale both numbers off a measured spawn baseline taken once per run.
  • Inject a clock so the case is deterministic rather than wall-clock timed.

Acceptance

  • The case pins the deadline-vs-survival-window overlap it was written for, without depending on process startup fitting in a fixed budget.
  • It passes under concurrent load, not only on an idle machine.
  • The other cases in the file keep their current meaning (forbidOutput, the exit-before-paint case, the survival assertions).

Found while running npm run local:gate for #2171.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingv2Issues and PRs for v2

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions