fix(signals): a source pending on a superseding re-ask keeps its transaction blocked (#3462) - #3466
Merged
ryansolid merged 2 commits intoSep 15, 2026
Conversation
…saction blocked (solidjs#3462) `transitionComplete` judged a reporter's source by its own flight alone (the self entry in `_pendingSources`). An upstream re-ask that supersedes that flight retires the entry and leaves the source pending on the re-ask, so the verdict flipped to complete while the source's reader still could not render. The transaction was parked, so nothing re-judged it until a re-entry: repeating `setShow(true)` while the first write was held re-entered it through setSignal's stamp path, and the flush committed `Show: true` beside `Panel: hidden`, with the panel catching up seconds later at the chain's landing. The source now blocks while its `_pendingSources` is non-empty; the landing folds the transaction in as before (A15), and the no-repeat frames are unchanged. Doc bullet restated, rules index regenerated, changeset added. Core floor brotli 8,849 -> 8,851 B (+2) tips a cap that sat 1 B under the baseline: ratcheted 8.85 -> 8.90 KB with the measurement noted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 2a3ae8c 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 |
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/signals/docs/RULES-INDEX.md # scripts/size/.size-limit.js
ryansolid
added a commit
to brenelz/solid
that referenced
this pull request
Sep 15, 2026
…reset walk on top of solidjs#3464/solidjs#3465/solidjs#3466 Measured at 28861 B against next's 28815 (+46). The PR alone sat 2 B under against the pre-solidjs#3464 next; the three fixes that landed meanwhile used the room. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid
added a commit
that referenced
this pull request
Sep 15, 2026
…s still wait on (#3459) (#3467) * fix(signals): a Loading `on` reset collects what its forwarded readers still wait on (#3459) The `on` reset cleared the boundary's sources and rebuilt them from the pending notifications that followed. A reader already pending from an earlier write never re-notifies (status propagation dedupes on its `_pendingSources`), so with `fast` and `slow` read from sibling effects only the fresh flight was collected; the wake committed the held write (#3375: a reader behind a collecting boundary holds nothing), `fast` landed a microtask later, and the boundary revealed `B: 1 | Fast: 1 | Slow: 0` with `slow` still in the air. The reset now harvests, from every live transaction's `_asyncReporters` (INV-3, the one record of a forwarded reader), the sources of each reporter it routes (`_holds`: under this queue with no collecting pending-type boundary between, the `reporterBlocksSource` test) plus that reporter's `_pendingSources`, and flips to the fallback when it found any. The hold the ruling takes off the lane lands on the boundary: `B: 1 | Loading`, then `Fast: 1 | Slow: 1` together. `scheduler.ts` exports `transitions` for the walk; no scheduling logic changes. Core floor unchanged (23,752). Docs bullet, rules index, changeset. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(size): hydrating+stores cap 28.85 -> 28.90 KB — #3459's reset walk on top of #3464/#3465/#3466 Measured at 28861 B against next's 28815 (+46). The PR alone sat 2 B under against the pre-#3464 next; the three fixes that landed meanwhile used the room. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com> * docs(signals): regenerate rules index after the INTERNALS merge resolution Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Brenley Dueck <brenleydueck@Brenleys-Mac-mini.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Ryan Carniato <ryansolid@gmail.com> Co-authored-by: Claude via Cursor <noreply@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 #3462.
The gap
setB(1)re-asksselectedwhile nothing observes it, so the flight is in the air with no hold.setShow(true)flips the conditional onto the pendingselected: the reveal opens a transaction holdingshow = true, blocked onselected(INV-3).setA(1)re-asksdetails;selectedis now pending on that flight instead of its own. Then the secondsetShow(true): onnext,Show: truepublishes at once besidePanel: hidden, and Panel says3only when the whole chain lands seconds later. Without the repeated write, both publish together at the landing.Traced:
transitionCompletejudged a reporter's source by its own flight alone, the self entry in_pendingSources(#3375 chose that over_error.source, which a later-pending input overwrites). Thedetailsre-ask supersededselected's own flight and retired the self entry, leavingselectedpending ondetails; the verdict flipped to complete whileselected's reader still could not render. The transaction was parked, so nothing re-judged it. The repeated same-value write re-entered it throughsetSignal'sinitTransition(el._transition)(which runs before the equality check), the flush trusted the verdict and committedshow = true, and the panel effect, replayed against aselectedwhose inputs had been published beneath its first flight (CONFIG_INPUTS_PUBLISHED), refused the carve-out and parked on the chain with no transaction of its own.Fix
A registered source blocks while it is still pending on anything, not only on its own flight:
This is the verdict A15 already implies (async work observed by a reader settles as one unit with the writes that asked it) and matches what the landing does anyway:
enterWaitingfolds every transaction with a live reporter on the source into the landing's transaction. The only state the change touches is a source whose own flight a superseding re-ask retired; the #3375 boundary-reset case is decided earlier byreporterBlocksSource's fallback prune and still passes. The reporter set is unchanged and nothing entangles earlier than before; the transaction simply stays parked until the chain lands.Known edge not addressed: a parked transaction whose blocking source later settles synchronously (its re-run after the upstream landing returns a plain value, so there is no landing of its own to fold the waiter in) has no wake, before and after this change; the fold at a landing is the only re-entry the reporter path has.
Verification
@solidjs/signals: 180 files / 2038 tests (2037 green, 1 skipped as onnext; the timing-onlyheap-mark-incremental"linear in N" case failed once in the full run and passes in isolation), including the newtests/superseded-source-blocks-3462.test.ts(the issue shape on deferred gates, plus the no-repeat control pinned to the same two frames).@solidjs/web: 88 files / 793 tests green, including the newtest/superseded-source-blocks-3462.spec.tsx, a jsdom port of the playground (real click, DOM sampled through the hold, delays scaled to 100ms) with the same control; the issue shape fails onnext, passes here.next(a8a8949) build: core floor 8,849 -> 8,851 (+2 B), which tips a cap that sat 1 B under the baseline, so it is ratcheted 8.85 -> 8.90 KB with the measurement in the note;+ createStoreis pre-existing red onnextby 9 B (15,809 vs 15,800, from perf(signals,web,universal,html): merge/omit are always lazy views; consumers read the leaves #3454/test(signals): store oracle + four signal states; latest() seed leak and body-end visibility fixes #3455) and reads 15,770 here; every other scenario is within noise of the baseline and under its cap.INTERNALS-ASYNC-STATE.md'stransitionCompletebullet restated (judged by the set, not the self entry); rules index regenerated (--checkclean). Changeset included.🤖 Generated with Claude Code