fix(signals): defer a transaction-owned node's committed children to its commit (#3404) - #3405
Merged
ryansolid merged 4 commits intoSep 14, 2026
Conversation
…its commit (solidjs#3404) A render effect parked on a pending source is stamped with the transaction without recomputing, so its owned children (nested render effects, memos, onCleanup registrations) still belong to the committed frame. When the source landed, recompute saw the stamp and disposed them on the spot, running their cleanups mid-hold — a Portal under a keyed Show disappeared when the first hop of an async chain landed and reappeared only when the whole chain settled. Children are now deferred as zombies until the node commits unless CONFIG_HELD_CHILDREN says the pass that built them never committed (a staged value, a pending window, a run under a held transaction), in which case no frame ever showed them and they die immediately. The bit is set at recompute's tail and cleared by commitPendingNode. A transaction-owned effect recomputed mainline (contested, solidjs#3322) publishes directly and releases its zombies itself rather than letting two generations render until the transaction commits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 0c48456 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Merging this PR will degrade performance by 36.55%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | projection derive: write one NESTED field (reference) |
222 µs | 523.2 µs | -57.56% |
| ❌ | memo + sync render effect only (reference) |
82.1 ms | 86.6 ms | -5.13% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing brenelz:fix/nested-render-effect-cleanup-3404 (0c48456) with next (50323b4)
This was referenced Sep 14, 2026
…aps for solidjs#3404's held-children bit (+2/+14/+12 B) Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve recompute's tail against solidjs#3420 (solidjs#3412): keep the held-children bookkeeping and the _valueTransition save/restore around the forced in-transaction re-run. Size ledger notes merged; caps unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.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.
Fixes #3404.
Bug
A render effect parked on a pending async source is stamped with the transaction by status propagation without being recomputed, so its owned children (nested render effects, memos,
onCleanupregistrations) still belong to the committed frame. When the source landed,recomputesaw the_transitionstamp and took the "dispose children immediately" branch, running their cleanups mid-hold. In the issue's repro aPortalunder a keyedShowvanished when the first hop of an async chain landed and only reappeared once the whole chain settled, instead of swapping atomically.Fix
CONFIG_HELD_CHILDRENbit: this node's current children were built by a pass whose result has not committed (a staged value, a pending window, a run under a held transaction).recomputedisposes children immediately only for tracked effects or when the bit is set (no frame ever showed them). Everything else is deferred as zombies until the node's commit, whether or not a transaction owns the node.recomputewhenever the pass's result waits on a commit, and cleared bycommitPendingNode.Tests
packages/signals/tests/nested-render-effect-async-cleanup.test.ts: the issue's shape, a memo withonCleanup, a sync write during the hold (held children die silently, the frame's swap and then wait for the reveal), and the plain sync ordering pinned as it already was.packages/web/test/nested-render-effect-cleanup-issue-3404.spec.tsx: the Portal / Show / Loading scenario from the playground. Fails onnextwith an empty portal target mid-hold, passes with the fix.Changeset and a note in
INTERNALS-ASYNC-STATE.mdincluded.🤖 Generated with Claude Code