Skip to content

fix(sdk): release the parsed film when a session is disposed - #6289

Merged
miguel-heygen merged 2 commits into
mainfrom
fix/studio-film-swap-leak
Oct 25, 2026
Merged

miguel-heygen merged 2 commits into
mainfrom
fix/studio-film-swap-leak

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

What

Composition.dispose() now drops the parsed document and the element snapshot caches.

Why

A disposed session kept its whole parsed (linkedom) document. Anything that still referenced the session, such as a stale closure or a React fiber's old props, therefore kept the replaced film alive, so Studio's memory grew with every film swap.

Measured

Headless Chrome, a generated fixture project (150 clips, 2 videos, 25 off-canvas elements), swapping between two films 25 times with no other interaction, heap read after a forced GC:

Heap at swap 0 Heap at swap 25 Growth
Before 76.8 MB 91.9 MB +15.1 MB
After 76.1 MB 83.7 MB +7.6 MB

Retained copies of the parsed film HTML in a heap snapshot at swap 25: 29 before, 4 after (the live session plus interned strings). Live documents, DOM nodes and listeners were flat in both runs.

Not fixed here

Growth is halved, not gone: about 0.26 MB per swap remains. Stale render scopes of several memoized callbacks still chain to one another and keep a lower-case copy of each film's source and other per-swap data alive. That needs changes in several components, so it is a separate PR.

Test

session.dispose-release.test.ts opens ten sessions, keeps every disposed session referenced, forces a GC and requires every parsed document to be collected. It fails on main (10 of 10 documents retained) and passes with this change, three runs in a row.

A disposed session now answers reads from an empty document instead of its old one, and outside production logs one warning with a stack the first time it is read, so a caller still holding a stale session can be found.

A disposed session kept its whole parsed document and element caches. Anything
that still referenced the session, such as a stale closure or React fiber,
therefore kept the replaced film in memory, so memory grew with every film swap.
dispose() now drops the document and caches.
A disposed session answers reads from an empty document. Outside production it
now logs once, with a stack, so a caller still holding a stale session shows up
instead of silently reading nothing.
@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 138 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)

Unstable (1)

  • seqplainkeys-none-pct-r0-nested-z100: tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@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 · 0 important · 0 nits · b0bf28bd

Reviewed at b0bf28bd, which includes the follow-up commit that adds the disposed-read warning. 1e7cf53e alone failed the comment ratchet. That is green here.

Scope
  • Both files read end-to-end. Every document read goes through the parsed getter, so all post-dispose reads and dispatches hit the empty document and warn once outside production. dispose() is idempotent.
  • Studio paths that can touch a stale session (useGsapPropertyDebounce unmount flush, sdkEditTransaction candidates, useSdkSession swap): edits on an empty document decline or fail. The persist queue is already guarded by disposed, so nothing writes an emptied film to disk.
  • CI is green on this head. session.dispose-release.test.ts ran in SDK: unit + contract + smoke (2 tests).

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit 495dc95 Oct 25, 2026
70 of 71 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-film-swap-leak branch October 25, 2026 08:15
@heygengenesis heygengenesis Bot mentioned this pull request Oct 25, 2026
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