[2.0 / next] A keyed Show on latest() of a staged write mounts its child twice when the flush parks (regressed by #4393)
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 48/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- javascript, typescript
调研方向
Start with the failing matrix rows in packages/signals/tests/boundary-not-born-held-3540.test.ts and the equivalent cases in packages/web/test/loading-on-keyed-boundary-3540.spec.tsx. Read the linked design notes D-2 and D-22 and the reader-seat rebuild plan in #4395 to understand the intended in-place park re-derive. Done means both cases produce mount counts [1, 2, 2] while preserving the stated latest() behavior.
由索引模型根据 Issue 内容生成。
描述
Summary
A keyed <Show when={latest(count)}> whose when reads a write that is staged in the current flush but not yet held mounts its child twice when that flush parks. The pass runs provisionally (reads latest(count) → new value, the keyed Show mounts the child for key 2), the flush parks on a pending async read, and the pass is re-derived once as a verdict reader (D-2). Because the provisional owner was voided before the re-run, the keyed comparison is against the last committed when, so the same key mounts a second child. Mount side effects run twice for one key change. This regressed with #4393 (which was itself correct for #4365) and is currently pinned at the wrong number.
Reproduction
Signals-level (packages/signals/tests/boundary-not-born-held-3540.test.ts, matrix rows static over latest / callback over latest); web-level equivalent in packages/web/test/loading-on-keyed-boundary-3540.spec.tsx (show-static-latest, show-callback-latest):
const [count, setCount] = createSignal(1);
const data = createMemo(async () => { const v = count(); await delay(1000); return v; });
let mounts = 0;
const L = () => { mounts++; return <Loading fallback="Loading A">{data()}</Loading>; };
<Show keyed when={latest(count)}><L /></Show>
<Loading fallback="Loading B">{data()}</Loading> // B holds the frame on data (A33)
- Render; advance 1000ms; flush →
mounts === 1. setCount(2); flush(); await microtask; flush()— the frame parks ondata; A showsLoading Aearly (correct).mountsis 3; expected 2.- Advance 1000ms; flush →
2|2|2;mountsstays at the mid-flight count.
Observed [1, 3, 3], expected [1, 2, 2]. The <L /> factory (and anything a real child would do on mount — effects, refs, subscriptions) runs twice for a single key change.
Why it is a bug
- D-2 says a provisional pass that read a staging not yet held is re-derived at the park. It says nothing about discarding the pass's output.
latest()serves the staging on both runs (D-22, 2026-10-28 paragraph: "Inside a flush,latest()of a staging not yet held is the frame's read like any other"), so the key is identical across the two runs. Same key → one child → one mount.- No rule licenses a mount that depends on whether a flush happened to park. It is a timing-dependent double invocation. A remount is only correct when the verdict run yields a different key — an ordinary keyed change.
History
- #3540 added a
!probingarm toprovisionalVerdict("a provisional pass re-derived at the seam ran everything downstream of it twice (#3540 web)"), which produced[1, 2, 2]. - #4393 removed that arm to fix #4365 — correctly:
latest()must not open a transaction for its verdict — and re-pinned both tests to[1, 3, 3]with a "not a bug" comment. The removal was right; the re-pin was not.
Intended fix
An in-place park re-derive: re-run the pass under the same owner, with keyed children compared against the provisional run rather than the last commit. Scheduled for the reader-seat rebuild S1 (#4395). Explicitly not: restoring the !probing arm, the stagedScreen transaction variant, or letting latest() open a transaction.
Interim
The companion PR flips both pins to it.fails asserting the target [1, 2, 2], so they turn green when fixed instead of guarding the wrong number. List under known bugs in the next RC notes.
- 主要语言
- TypeScript
- 星标
- 36.2k
- 派生
- 1.1k
- 平均合并
- 10 小时 10 分钟
- 30 天内合并 PR
- 505
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
solidjs/solid 的其他 Issue
-
难度 4/5 3-5 天 新手友好度 35/100
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 22/100
维护者通常 1 天内回复
-
What gets passed to the `when` prop of a `Show` still gets executed twice on SSR可能已有人在做 @brenelz 于 4 天前认领。 未关闭1.x
难度 3/5 1-2 天 新手友好度 52/100
维护者通常 1 天内回复
-
enhancement
难度 5/5 一周以上 新手友好度 35/100
维护者通常 1 天内回复
-
enhancement
难度 5/5 一周以上 新手友好度 35/100
维护者通常 1 天内回复
相似的 Issue
-
reproduced
难度 2/5 1-3 小时 新手友好度 68/100
anomalyco/opencode#61640 · 1 条评论 ·
维护者通常 1 天内回复
-
ai-driven-fuzzing bug Minor
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
ai-driven-fuzzing bug claude Minor
难度 2/5 1-3 小时 新手友好度 82/100
linagora/twake-calendar-frontend#1944 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 74/100
维护者通常 1 天内回复
-
Add: Yichang TV未关闭check:passed streams:add
难度 2/5 1-3 小时 新手友好度 74/100
维护者通常 1 天内回复