fix(signals): optimistic settle verdicts — combined isPending on the child lane, onSettled after the revert re-derive (#3409, #3411) - #3425
Merged
Conversation
…child lane, onSettled after the revert re-derive (#3409, #3411) optimistic value reported false while `isPending(a)` and `isPending(b)` both reported true. The combined effect computed `true` but its run was withheld: its two companion child lanes are siblings and merge, and the parent lane's next notification then hit assignOrMergeLane's "subscriber's lane was merged → take the source lane" shortcut, which skipped the parent/child check and moved the effect onto the held parent lane, where its verdict waited on the async it reports. A merged lane is now followed to its root like any other and goes through the same check. -33 B on the core floor. revert. The settle drops the override in the commit pass but only enqueues the reverted subscribers; the pass after re-derives them, and reads do not pull. The one-shot fired in the commit pass's user phase saw the source reverted beside a stale memo. The fire now waits for the heap to drain (re-enqueues itself while `dirtyQueue` has work; `run` swaps the queue so it lands in the next pass). Forcing the re-derive into the commit pass instead breaks the lane frame of a write completing in its own flush and the store clears (#3164 tear) — recorded in the internals doc. Pins for both (plus the no-action variant of #3411) in optimistic-settle-verdicts.test.ts. Co-authored-by: Claude <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 9fa294f 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 |
Coverage Report for CI Build 34830828074Coverage remained the same at 71.842%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
This was referenced Sep 14, 2026
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 #3409, fixes #3411 — the two optimistic-bookkeeping issues from gabbev's latest batch. Both are small, and both were reproduced core-level (manual clock) before fixing; the pins are in
optimistic-settle-verdicts.test.tsand fail onnext.#3409 — combined
isPendinglags the individual probesisPending(() => [a(), b()])over two async siblings of an optimistic value reportedfalsewhileisPending(a)andisPending(b)both reportedtrue, and flipped totrueonly oncealanded.Traced: the combined effect computes
trueat the write, but its run is withheld — it ends up on the heldvaluelane. Sequence inassignOrMergeLane:ps(a)'s child lane is assigned →a's parent-lane notification keeps the child (the parent/child carve-out) →ps(b)'s child lane is a sibling, so the two child lanes merge →b's parent-lane notification then hits the "subscriber's lane was merged → replace with the source lane" shortcut, which skips the parent/child check and puts the effect on the held parent.fxA/fxBeach have a single child lane and never reach that path.Fix: delete the shortcut. A merged lane is followed to its root (
findLane) like any other and goes through the same parent/child logic — the root is a child of the source root, so the effect keeps it. −33 B on the core floor.The pin runs through the revert too: there the three indicators clear together when the held reload commits, which matches the plain-signal baseline.
#3411 —
onSettledreadsValue: 0, Copy: 1during the revertThe settle drops the override in the commit pass but only enqueues the reverted subscribers (
_resolveOptimistic→insertSubs, likewise the contested/gated replays and store clears); the pass after re-derives them, and reads do not pull (prepareComputed(el, false)). An unownedonSettledis a one-shot in the commit pass's user phase, so it readvaluealready reverted next tocopystill holding1.Fix: the unowned fire waits for the heap to drain — while
dirtyQueuehas work it re-enqueues itself (runswaps the queue array, so the re-enqueue lands in the next pass;enqueuekeeps the drain alive). That is what settled means: no derivation outstanding. Also covers gabbev's no-action variant (ambient optimistic write reverting at the end of its own flush), pinned separately.I first tried the other lever — re-running the heap inside
finalizePureQueueafter_resolveOptimisticso the commit pass itself is consistent. It breaks three things that rely on the next-pass re-derive: the lane frame of an optimistic write completing in its own flush (applied bycleanupCompletedLanes;[0, 2, 0]became[0, 0]), the store optimistic-layer clears (the #3164 test produced a tornRUNTIME ERRORframe), and a gated replay. The internals doc records why the observer-side fix is the right one.Verification
turbo run testserially, rebased on currentnext(includes fix(signals): defer a transaction-owned node's committed children to its commit (#3404) #3405): 23/23 tasks green.src/typechecks clean (the 26 test-file type errors are pre-existing onnext).Co-authored with Claude via Cursor.