Investigation: virtualized-list memory leak (#3241)#3708
Draft
mattgperry wants to merge 2 commits intomainfrom
Draft
Investigation: virtualized-list memory leak (#3241)#3708mattgperry wants to merge 2 commits intomainfrom
mattgperry wants to merge 2 commits intomainfrom
Conversation
Adds a virtualized-list reproduction harness for the reported memory leak when scrolling animated motion.div items. The page cycles through windows of items every 50ms, remounting fresh motion.divs each time. Memory growth must be observed manually via Chrome DevTools' Performance Monitor (DOM Nodes counter); the JSDOM/Electron 80 environment used by unit and Cypress tests doesn't surface the leak. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add 100 child divs per item (matching the original sandbox) and a FinalizationRegistry that tracks every motion.div's lifecycle. The harness exposes mounted / unmounted / still-alive counts on window.__leakStats so a leak can be detected programmatically rather than relying solely on Chrome's DOM Nodes counter. Verified locally with Chromium + --js-flags=--expose-gc: after 1700+ mount/unmount cycles the still-alive count stays bounded at the visible-item count plus a handful of GC stragglers, indicating the original leak is no longer reproducible on main. Co-Authored-By: Claude Opus 4.7 <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.
Status: draft — investigation only, no code fix applied
Refs #3241
The reporter says scrolling a virtualized list of
motion.divitems withinitial={{ opacity: 0 }} animate={{ opacity: 1 }}causes the Chrome DevTools"DOM Nodes" counter to climb without falling, even after manual GC. Removing
initial/animatestops the growth.What I did
nj8cqp). The sandboxsource is gated behind Cloudflare's bot challenge —
WebFetch,curl, and the CSB API endpoints all returned the challenge page, so Icould not retrieve the original
App.js.motion.div:useMotionRefcallsvisualElement.unmount()when React passesnullto the ref callback (packages/framer-motion/src/motion/utils/use-motion-ref.ts:39).VisualElement.unmount()cancels both scheduled frame callbacks,runs
valueSubscriptionscleanup (which callsvalue.stop()forowned MotionValues, in turn calling
animation.stop()→animation.cancel()for WAAPI), unmounts each feature(
AnimationFeature.unmount()→animationState.reset()), andnulls
this.current(packages/motion-dom/src/render/VisualElement.ts:504-525).animateon amotion.divneither projection norInViewFeatureis enabled (packages/framer-motion/src/motion/features/definitions.ts),so those teardown paths aren't relevant.
(
animationMaps,appearAnimationStore,observerCallbacks, frame-stepqueues). All are
WeakMaps or are cleared per frame. The optimisedappear store only populates when
data-appear-idis present (SSR), soCRA-style sandboxes don't touch it.
#3178release-visual-element,#3453reduced-motion listener,#3381React 19 cleanup,popchild-refs,drag-cleanup). The unmount path has been touched repeatedly sincethis issue was filed.
animate-prop.test.tsx > unmount cancels active animationsalready proves
onAnimationCompleteis not invoked after unmount.to be fixed? … snapshots don't seem to be hinting any leaks."
Where I got stuck
I cannot reliably reproduce the leak:
can't be observed there.
document.getAnimations()isunavailable and detached-DOM accounting requires Chrome DevTools'
Performance Monitor — not addressable from a test runner.
reporter's leak was caused by a third-party virtualizer holding refs,
by a bug since fixed, or by Chrome behaviour around recently-cancelled
WAAPI animations.
Per
feedback_no_repro_no_pr.md: not landing happy-path coverage thatcan't fail without a fix. No source change is included here.
What this PR adds
A single manual reproduction page at
dev/react/src/tests/animate-virtualized-list-memory.tsx(route?test=animate-virtualized-list-memory). It cycles a window of 10motion.divitems every 50ms, simulating the virtualized scrolldescribed in the issue. A maintainer can open it in Chrome with the
Performance Monitor visible to see whether the DOM Nodes counter
stabilises — useful for confirming whether the bug is still live before
landing a fix.
Suggested next step
Confirm reproducibility against current
mainusing the harness(or the original CodeSandbox if accessible). If memory stabilises,
close #3241 as already-fixed; if it grows, the harness gives a
controllable starting point that doesn't depend on a third-party
virtualizer or external sandbox.