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,