From 0ff725d8fbcf13b1f4988a1cd6eb6626ddf94c11 Mon Sep 17 00:00:00 2001 From: hzy <28915578+hzy@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:01:47 +0800 Subject: [PATCH] fix(react): memory leak because of cycle reference This PR tres to eliminate a memory leak by a cycle like: list(FiberElement) -> componentAtIndex -> ctx array -> full SnapshotInstance tree (by property like `__parent`, `__nextSibling` and `firstChild`) -> list(FiberElement, which closes the circle). To minimize the impact on performance and behavior, only list-related logic was modified. --- .changeset/nine-chairs-care.md | 5 +++++ packages/react/runtime/src/list.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 .changeset/nine-chairs-care.md diff --git a/.changeset/nine-chairs-care.md b/.changeset/nine-chairs-care.md new file mode 100644 index 0000000000..d87b7d5b36 --- /dev/null +++ b/.changeset/nine-chairs-care.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/react": patch +--- + +Fix a memory leak when using ``. diff --git a/packages/react/runtime/src/list.ts b/packages/react/runtime/src/list.ts index 9e94b4cca8..6f3aed7ee9 100644 --- a/packages/react/runtime/src/list.ts +++ b/packages/react/runtime/src/list.ts @@ -6,6 +6,7 @@ import type { SnapshotInstance } from './snapshot.js'; export const gSignMap: Record> = {}; export const gRecycleMap: Record>> = {}; +const gParentWeakMap: WeakMap = new WeakMap(); export function clearListGlobal(): void { for (const key in gSignMap) { @@ -20,6 +21,23 @@ export function componentAtIndexFactory( ctx: SnapshotInstance[], hydrateFunction: (before: SnapshotInstance, after: SnapshotInstance) => void, ): [ComponentAtIndexCallback, ComponentAtIndexesCallback] { + // A hack workaround to ensure childCtx has no direct reference through `__parent` to list, + // to avoid memory leak. + // TODO(hzy): make `__parent` a WeakRef or `#__parent` in the future. + ctx.forEach((childCtx) => { + if (gParentWeakMap.has(childCtx)) { + // do it only once + } else { + gParentWeakMap.set(childCtx, childCtx.parentNode!); + Object.defineProperty(childCtx, '__parent', { + get: () => gParentWeakMap.get(childCtx)!, + set: (value: unknown) => { + gParentWeakMap.set(childCtx, value); + }, + }); + } + }); + const componentAtIndex = ( list: FiberElement, listID: number,