fix(engine): fail-fast the sub-composition timeline wait when a script 404s#2045
Open
vanceingalls wants to merge 1 commit into
Open
fix(engine): fail-fast the sub-composition timeline wait when a script 404s#2045vanceingalls wants to merge 1 commit into
vanceingalls wants to merge 1 commit into
Conversation
…t 404s
pollSubCompositionTimelines waits for every [data-composition-id] host to
register window.__timelines[id]. When the script carrying that registration
fails to load (404 / request failure), the registration can never arrive —
but the poll still burned the full playerReadyTimeout (45s), then warned and
shipped a silently animation-less render. Wild scale: the capture-setup
histogram over 30 days of local renders decays smoothly (402/503/364/282/191
per 5s bucket) then spikes to 705 at the 45s bucket — ~1,000 renders/month
across 402 distinct users, ~15 user-hours of pure waiting.
- Sessions now record failed SCRIPT resources (requestfailed + HTTP>=400
response, listeners that already existed for diagnostics) in
session.scriptLoadFailures.
- pollSubCompositionTimelines takes a failure getter and cuts the wait to a
2s grace once any script failed, with a loud warning naming the URL(s).
Late-registering fetch-async comps are unaffected: no script failure means
the full timeout still applies, and a registration landing inside the
grace window still wins (tested).
- Outcome telemetry: session.subTimelineWaitOutcome ("ready" | "timeout" |
"script_failure") -> CapturePerfSummary -> RenderPerfSummary.subTimelineWait
(worst across sessions) -> render_complete sub_timeline_wait, so the wild
rate becomes directly trackable instead of setup-histogram forensics.
Validation: the discovery comp (0768f038, its animations.js unreachable)
drops from ~72s to 23.1s total — poll cut at 2.1s with the script named;
healthy comp reports "ready". Canary suite 7/7 (PSNRs identical). 4 new
poll unit tests; engine suite 907 passed (14 failures are PRE-EXISTING on
main at v0.7.42 — 18 fail on a clean checkout, stash A/B verified).
tsc/oxlint/oxfmt clean.
Corpus note: 258/1,762 corpus comps (14%) reference local scripts missing
from the corpus fetch — their historical eval INIT timings measured this
timeout, not the engine. Capture-stage ratios remain valid (both paths paid
it equally).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
pollSubCompositionTimelineswaits for every[data-composition-id]host to registerwindow.__timelines[id]. When the script carrying that registration fails to load (404 / request failure), the registration can never arrive — but the poll still burned the fullplayerReadyTimeout(45 seconds), then warned and shipped a silently animation-less render. The engine was logging the exact load failure and waiting anyway.Wild scale (30 days, local renders, n=89,786): the capture-setup histogram decays smoothly — 402/503/364/282/191 per 5s bucket — then spikes to 705 at the 45s bucket. A textbook timeout wall: ~1,000 renders/month across 402 distinct users, ≈15 user-hours of pure waiting, each render also delivering broken animations with only a console warn.
Found while benchmarking comp
0768f038: its "45s init" was 99% this one poll phase (139ms → 45,203ms) — itsanimations.jswas unreachable.What
requestfailed+ HTTP ≥ 400 responses withresourceType === "script"— extending the diagnostic listeners that already existed) insession.scriptLoadFailures.sub_timeline_wait("ready" | "timeout" | "script_failure") onrender_complete, threaded session →CapturePerfSummary→RenderPerfSummary(worst across sessions) → CLI. The wild rate becomes a direct filter instead of setup-histogram forensics.Validation
ready. Telemetry smoke:subTimelineWait: "script_failure"/"ready"respectively.parseVideoElements/color-grading, untouched by this diff).pollPageExpressionhelper was removed.Corpus note
258/1,762 corpus comps (14%) reference local scripts missing from the corpus fetch — historical eval init timings on those comps measured this timeout, not the engine. Capture-stage ratios remain valid (both paths paid it equally).
🤖 Generated with Claude Code