Skip to content

fix(studio): a thumbnail reader forgets a start's failures after a minute - #6642

Merged
vanceingalls merged 4 commits into
mainfrom
fix/studio-thumbnail-review-nits
Oct 28, 2026
Merged

vanceingalls merged 4 commits into
mainfrom
fix/studio-thumbnail-review-nits

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #6629, from Genesis's review. A pooled thumbnail reader's per-start retry budget never reset, so two transient failures at one start early in a session made a third one, much later, give up at once. That retired the reader and showed the clip as an error.

A start's failures are now forgotten when a read from that start ends cleanly: either its body finishes, or mediabunny stops it at its target. mediabunny never cancels the body; _runWorker aborts the signal it passed in. So the request listens on its own signal, which follows mediabunny's and is unlinked once the read ends, and counts an abort as clean only when mediabunny's signal is the one aborted: never a stall (8 s silence) or a dispose (cancelReads).

I ruled out two other designs:

  • Clearing the count when bytes arrive. A server that ignores Range re-sends from byte 0 before it breaks at the same byte, so every attempt delivers bytes and the budget would never run out. The existing test gives up on a server that ignores Range and breaks at the same byte fails under that version.
  • A one-minute window (this PR's first version). It only held while each attempt took under a minute. An attempt has no time limit before its answer, and a body that keeps sending never trips the 8 s stall, so a slow server that breaks past a minute re-downloaded from 0 forever (Genesis's review). A server that breaks before the target never ends a read cleanly, so its budget still runs out, however slowly it fails.

No visible change

Retry bookkeeping in mediaFetch.ts: 10 lines, no .tsx, .css or .html. A thumbnail looks the same; it just stops failing when a start that once failed twice comes back later.

What I measured

  • mediaFetch.test.ts: 32 pass. Four of those tests are new:
    • a clean finish resets the budget;
    • mediabunny aborting its own signal mid-body (as _runWorker does at its target) resets the budget;
    • a read that broke before that abort keeps its count;
    • failures 20 minutes apart (fake clock) with no clean end still run out after two retries.
  • The existing unlinks every request from mediabunny's signal however it ends caught a first version that listened on mediabunny's signal directly.
  • fails without the fix: five mutations, each caught by a test:
    • no reset at all;
    • no reset on finish;
    • no reset on stop at target;
    • any abort counted as clean (a stall would reset its own budget);
    • reset on bytes (the Range test).
  • The comment ratchet passes, and so do the pre-commit hooks (lint, format, fallow, typecheck).

What I did NOT exercise

  • A real long session against a flaky server. This only exercises the retry arithmetic, against stubbed fetch.

🤖 Generated with Claude Code

…nute

A pooled thumbnail reader lives for as long as clips keep asking it, and each start
byte has a retry budget of two. That budget never reset, so two transient failures at
one start early in a session made a third, twenty minutes later after the cache evicted
it, give up at once: the reader retired and the clip showed an error.

A start's failures now count for a minute. One bout of retries (8 s stalls, 0.5 s and
1 s waits) fits inside it, so a start that keeps failing still gives up; one met again
much later starts afresh. Clearing the count when a read from that start delivers bytes
would not do: a server that ignores Range sends bytes 0 onward again before it breaks at
the same byte, and would retry forever.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 102 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@heygengenesis heygengenesis 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.

Verdict: APPROVE · 0 blocker · 1 important · 0 nits · 41e739b9

I agree with not clearing on bytes, since the Range-ignoring test shows why. One gap remains with the time window ↓. CI, Comments and PR captures are green on this head.

— Genesis

…leanly, not after a minute

The one-minute window only held while each attempt took under a minute. An attempt has
no limit before its answer, and a body that keeps sending never stalls, so a server that
ignores Range, sends slowly and breaks past a minute re-downloaded from 0 forever.

A start's failures are now forgotten when a read from it ends cleanly: its body finishes,
or mediabunny cancels it at its target. A server that breaks before the target never ends
a read cleanly, so its budget still runs out, however slowly it fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@heygengenesis heygengenesis 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.

Verdict: APPROVE · 1 resolved, 0 open, 1 new · 41e739b9 → cd7650a3

The new finding ↓ isn't a regression from main, but the cancel-at-target reset doesn't fire under mediabunny as it runs today. CI, CodeQL, Comments and PR captures are green on this head.

— Genesis

…esets its start's budget

mediabunny's UrlSource never cancels the body: once a read reaches its target, _runWorker
aborts the signal it passed in, and the pending pull fails with an AbortError. So the reset
on cancel never fired, and any open bytes=N- read stopped at its target kept its start's
failures.

The request now listens on its own signal, which follows mediabunny's and is unlinked once
the read ends: an abort while live counts as a clean end only when mediabunny's signal is
the one aborted, never a stall or a dispose.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@heygengenesis heygengenesis 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.

Verdict: APPROVE · 1 resolved, 0 open, 0 new · cd7650a3 → aa3cf5fe

I checked the fix against a real mediabunny 1.45.3 UrlSource. Two failures at a start, then a read stopped at its target, now leaves that start's count at zero. The same check fails on cd7650a3. CI, CodeQL, Comments and PR captures are green on this head.

— Genesis

@vanceingalls
vanceingalls added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit a88751d Oct 28, 2026
66 checks passed
@vanceingalls
vanceingalls deleted the fix/studio-thumbnail-review-nits branch October 28, 2026 10:33
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