`createRoot`'s dispose function keeps `fn` alive, so `<For>`/`<Index>` rows retain removed list items
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 45/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- javascript, typescript
- Lĩnh vực
- frontend, performance
Hướng nghiên cứu
Start with createRoot and the updateFn line that builds the dispose closure, then trace how mapArray and indexArray store each row's disposer in disposers. Run the repro script with node --expose-gc --conditions=browser repro.mjs. Done means both lines print false, and the existing tests for createRoot and the array helpers still pass.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
createRoot creates the dispose function it passes to fn inside its own scope:
updateFn = unowned ? ... : () => fn(() => untrack(() => cleanNode(root)));
That scope also holds fn, because updateFn closes over it. So anything that keeps the dispose function also keeps fn, and everything fn closes over.
mapArray and indexArray keep every row's dispose function in disposers. Every row's fn is mapper, and mapper closes over newItems, the list that was current when the row was created. So a mounted row keeps that whole list reachable, including items that were removed from the list later.
We found this in production. A long-lived <For> row kept a list it was created from, and with it a replaced document of a few hundred MB.
Your Example Website or App
Standalone script below (Node 22, solid-js 1.9.17, client build).
Steps to Reproduce the Bug or Issue
// npm i [email protected]
// node --expose-gc --conditions=browser repro.mjs
import { createRoot, createSignal, createMemo, mapArray, indexArray } from "solid-js";
const collect = async () => {
// WeakRef targets stay alive until the current job ends.
await new Promise(r => setTimeout(r, 0));
globalThis.gc();
};
// A row created from [kept, removed] stays mounted after the list becomes [kept].
async function check(name, map) {
const kept = { name: "kept" };
let removed, setList;
const dispose = createRoot(dispose => {
const [list, set] = createSignal(
(() => {
const item = { name: "removed" };
removed = new WeakRef(item);
return [kept, item];
})()
);
setList = set;
createMemo(map(list))();
return dispose;
});
setList([kept]);
await collect();
console.log(`${name}: removed item still reachable:`, removed.deref() !== undefined);
dispose();
}
await check("mapArray (<For>)", list => mapArray(list, item => item.name));
await check("indexArray (<Index>)", list => indexArray(list, item => item().name));
Output:
mapArray (<For>): removed item still reachable: true
indexArray (<Index>): removed item still reachable: true
The dev build (dist/dev.js) gives the same output, and so does Bun (JavaScriptCore) with Bun.gc(true).
Expected behavior
Once the row for removed is disposed, nothing should keep removed reachable, so both lines should print false. They do once the dispose function is created outside createRoot's scope, in both V8 and JavaScriptCore.
Platform
- solid-js: 1.9.17 (
main) - Node 22.23 (V8), Bun 1.4.2 (JavaScriptCore)
Additional context
I haven't checked next (2.0) yet. The fix for 1.x is in the PR linked below.
- Ngôn ngữ chính
- TypeScript
- Star
- 36.1k
- Fork
- 1.1k
- Merge trung bình
- 11 giờ 17 phút
- Pull request đã merge (30 ngày)
- 371
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của solidjs/solid
-
`lazy()`: remounting an already-loaded component renders empty (or the Suspense fallback) since 1.9.14Có thể đã có người làm @itisrmk đã nhận 4 ngày trước. Đang mở1.x
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[2.0] The MIGRATION.md replacement for `from()` keeps the upstream subscription after dispose; add a subscription helperCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
solidjs/solid#4182 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
-
signals: a landing reveals a held write its stale reader re-derives onto a new flight (U-5, D-9)Đang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 12/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của solidjs/solid
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 72/100
zts212653/clowder-ai#1678 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 64/100
Progressive-Victory/the-website#680 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100