Skip to content

test(interaction-harness): too little to judge is named starvation - #10788

Merged
cixzhang merged 2 commits into
meta:mainfrom
vjeux:test/busy-proof-starvation
Nov 9, 2026
Merged

cixzhang merged 2 commits into
meta:mainfrom
vjeux:test/busy-proof-starvation

Conversation

@vjeux

@vjeux vjeux commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer impact

A busy-main-thread proof can fail as if the component stalled when the runner presented too few frames. The control moved on under four frames, and no gap passed 50 ms.

Intended invariant

A busy proof fails the component only on frames the compositor presented. Under the control's floor, a run with no component failure on the frames shown is too little to judge. Every timeout and floor is unchanged.

Change

  • busyWindowProof: a control under minJudgedFrames alone sets the starvation. A missed frame or a stall longer than the control's stays a verdict failure. maxMedianFrameMs judges the cast, so it keeps no red.
  • pooledBusyProof: too few judged runs or control frames throws the same way unless a judged run failed. A pool left short of judged runs by a run that began too late keeps its failure.
  • Every error names the control's moves, the floor, the cast's frames and its longest gap. The cast's numbers count every frame drawn, dated on the controls' clock in WebKit.
  • The README and JSDoc state the rule.

Evidence

  • Failure before the change: on a loaded runner, the Wheel's coast proof had the control on 3 frames and a 44 ms gap. Main returned verdict failures.
  • Success after the change:
    • Unit tests replay that run and a 4-run pool with 1 judged. On main's busy.ts (542b12003dd74fe22cfe160797e3716433c891c9), 10 tests fail. All pass at this head, e1f9922afdc43cc7fd4c1816a671834a00514849.
    • Mutant tests ran at CI's profile at this head: 1 worker, 3 runs per engine.
    • Every frozen, main-thread and slowed-settle mutant failed on its component, or as a run that began too late. None starved.
    • In an earlier run of the same check (9df4854d9d7cfe9a0bc473dad1b0853b5709b446), one WebKit slowed-settle calibration starved by name: no frame for 191 ms.
    • At CI's profile, every busy-proof spec passed on main and at this head: 655 Chromium and 562 WebKit tests.
    • One flip changes a test's outcome. LightboxPaging's slowed-settle mutant tests passed on main by counting a starved calibration's floor failures as the mutant's red.
    • In WebKit, that calibration sits at the 4-frame floor by construction. So it can name starvation under load, and a denser calibration is tracked separately.
  • Product/runtime behavior verified unchanged: only the harness changes.
  • CI impact: no workflow changes. A named starvation gets one job re-run; a second on the same head means no merge.

Scope

  • No intended public API, product behavior, visual direction, or policy change.
  • Product changes discovered during the work were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

  • At e1f9922: busy.control.test.ts, busy.webkitclock.test.ts, busy.test.ts, busy.unjudged.test.ts and busy.clock.test.ts pass, 151 tests.
  • The 23 interaction specs and 2 Chromium accessibility specs with a busy proof ran once per arm per engine. Each ran on the CI image, 1 worker on 4 CPUs. Main a1dabb30cd and this head both passed every test, accessibility 25/25.
  • The mutant runs added only two logging lines to busy.ts, to read each proof's verdict and starvation.
  • At e1f9922, the harness's tsc --noEmit: 0 errors. ESLint: 0 errors in the changed files. Prettier: clean. node scripts/release/changeset-coverage.mjs pr reports nothing that ships.

@vercel

vercel Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 9, 2026 5:03pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 9, 2026
@astracat-bot

astracat-bot Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 9, 2026, 5:11 PM UTC

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 1c1bef1

Storybook · Sandbox · built from this PR's head

Package size: no published package changed; no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 29 rows, core 36 rows, theme-butter 1 row, theme-y2k 1 row; run pnpm api:report.

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved pending CI: CI has not run on this head yet, because the pull request is a draft from a fork. Once it is ready for review, a maintainer adds the ci:run label so CI runs.

The proofs now name a run with too few compositor frames as starvation while keeping every component failure on the frames shown, and the new tests fail against the current busy.ts and pass with this change.

Optional follow-ups:

  • internal/interaction-harness/README.md:386 says a pool with a run that began too late "fails instead", but the code keeps that failure only when late runs leave the pool short of judged runs, so a pool with enough judged runs and too few control frames throws starvation.
  • The comments at internal/interaction-harness/src/harness.interaction.browser.spec.ts:2085-2086 and :2112-2113 still say the verdict fails where the test now expects a throw.

[Automated review]

Comment thread internal/interaction-harness/README.md Resolved
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 1c6cd25

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: one fix in the pooled starvation error.

  • Miss: when a pool is short of judged runs and its judged run has fewer than four control moves, pooledBusyProof takes the error's frames and longest gap from the judged runs only (internal/interaction-harness/src/busy.ts:2113) but words them for every block (:2118), so the new four-run case reports "5 frames while 4 blocks held the main thread" when they drew 20, and a 16.7 ms gap when the other blocks drew nothing for 90 ms; const counted = tooFewRuns ? runs : runs.filter((_, i) => judged[i]); fixes it, and the test should assert the error's frames and gap.

The rest holds: the new tests fail against the current busy.ts and pass here, and every component failure under the floor still fails. Since this head, main changed busy.ts (#10772) and GitHub reports a conflict, so a rebase is needed. The two earlier non-blocking notes (the README's late-run sentence and the stale comments in the harness's pooled browser test) still apply.

[Automated review]

A busy proof whose control moved on fewer than minJudgedFrames frames
threw FrameStarvationError only when the cast also showed no frame for
longer than FRAME_STARVATION_MS. On a loaded runner the gap can stay
under that. The same shortfall then came back as verdict failures, and
the spec failed as if the component had stalled.

Under the floor, a run with no component failure on the frames shown
is now too little to judge. busyWindowProof throws FrameStarvationError
with the control's moves, the floor, the cast's frames and its longest
gap, counting every frame the cast drew. A component that missed a
frame the control moved on, or stalled longer than it, still fails the
verdict. The cast's median interval judges no component, so it keeps no
red there. pooledBusyProof does the same for too few judged runs or
control frames. A pool left short of judged runs by a run that began
too late keeps its failure. Every change turns a verdict failure into
named starvation.
@vjeux
vjeux force-pushed the test/busy-proof-starvation branch from 1c6cd25 to e1f9922 Compare November 9, 2026 15:10
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 9, 2026
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec e1f9922

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved pending CI: CI has not run on this head yet, because the pull request is a draft. It runs once the pull request is marked ready for review; the ci:run label is already on it.

The busy proofs now name an under-floor run as starvation and keep every component red, and the pooled error's frame count and gap now match its wording, with tests that fail on the current busy.ts and pass here. The earlier notes on the README's late-run sentence and the stale comments in the harness's pooled browser test are fixed.

Optional follow-ups:

  • Miss (non-blocking): FrameStarvationError's JSDoc in internal/interaction-harness/src/frames.ts still says the error means no frame came for longer than FRAME_STARVATION_MS, while the busy proofs now also throw it when the control moved on too few frames with no long gap.
  • Miss (non-blocking): no test fails when the 50 ms bound in busyWindowProof's throw (busy.ts:1399) or the pool's !lateFailure guard (busy.ts:2506) is removed; one unit test each would pin the two README rules they implement.

[Automated review]

// frame or a longer stall. That failure is the component's red.
if (
starvation != null &&
(starvation.longestGapMs > FRAME_STARVATION_MS || !componentFailed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: no test pins the starvation.longestGapMs > FRAME_STARVATION_MS half of this condition. With it removed all 151 busy tests still pass, and a starved run (no frame for 200 ms, control on 3 frames) whose component froze returns its red instead of throwing. A unit test with starvedCast() and a component frozen through the block would pin the README's "throws either way".

// pool's failure. So does a judged run's component red.
const lateFailure = judgedReads.length < minJudgedRuns && lateRuns > 0;
const componentFailed = failedRuns.some((failed, i) => judged[i] && failed);
if ((tooFewFrames || tooFewRuns) && !lateFailure && !componentFailed) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: no test pins !lateFailure. With it removed all 151 busy tests still pass, and a pool of two runs that settled before their block beside one 90 ms run (control on 3 frames) throws starvation instead of keeping "1 of 3 runs judged, under 2: 2 began too late". The existing late-run test uses a 600 ms judged run, so tooFewFrames never holds there.

@vjeux
vjeux marked this pull request as ready for review November 9, 2026 16:10
@vjeux
vjeux marked this pull request as draft November 9, 2026 16:50
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 1c1bef1

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved pending CI: CI has not run on this head yet, because the pull request is a draft; it runs once the pull request is marked ready for review, and the ci:run label is already on it.

The busy proofs now name an under-floor run with no component failure as starvation and keep every component red on the frames shown, and the current busy.ts fails 10 of the new and changed tests.

Optional follow-ups:

  • FrameStarvationError's JSDoc in internal/interaction-harness/src/frames.ts still says the error means no frame came for longer than FRAME_STARVATION_MS, but busy proofs now throw it with shorter gaps.
  • No test pins the 50 ms half of the throw condition in busyWindowProof or the late-run guard in pooledBusyProof; both are flagged in the inline comments on those lines.

[Automated review]

@vjeux
vjeux marked this pull request as ready for review November 9, 2026 17:15
@cixzhang
cixzhang merged commit bd57e0a into meta:main Nov 9, 2026
118 checks passed
@vjeux
vjeux deleted the test/busy-proof-starvation branch November 9, 2026 18:00

This branch was successfully deployed

1 active deployment
Preview — 1c1bef1f Deployed Nov 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants