fix(signals): a memo computes under its own lane posture, never its puller's (#3442) - #3445
Conversation
🦋 Changeset detectedLatest commit: 6eda9c7 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 |
…uller's (solidjs#3442) A combined `isPending(() => [fast(), copy()])` over two async memos, with `copy` a sync memo wrapping the slow one, released the hold as soon as the fast flight landed: `Fast: 1` beside `Slow: 0` with `Pending: false`, then `Slow: 1` a second later. The probe effect carries the companion lane of the pending signals it reads, and its pull of `copy` ran under that lane — where a pending node on no lane serves its committed value instead of throwing — so `copy` published a stale settled `0`, dropped its pending status, and its readers stopped holding `slow`: the transaction settled on `fast`'s landing with `slow` still in flight. A plain getter in place of `copy` never had the gap (the probe read `slow` directly, and a probe observes without deriving). recompute now clears the ambient lane for a non-effect node before the lane branches re-establish the posture the memo itself owns (OPT-dirty, or adopted through its deps). A memo's value is one shared slot every reader sees, so the puller's lane carve-outs must not shape it; effects keep the ambient lane, since their runs are the lane's own view. Both values now reveal together, with the probe reporting pending until they do. Spec rule A31. +12 B core floor (noted, no bump); two brotli caps ratcheted 0.05 KB with measurements against `next`. Rules index regenerated, changeset included. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
eaddf9e to
6eda9c7
Compare
PR #3445 — memo lane posture (A31)Intent: Fix early reveal when Verdict: No blockers. I’d merge after a maintainer glance at the breadth note below. BlockersNone. Should fix / confirm before merge
|
Fixes #3442.
The gap
setCount(1)starts two flights;fastlands in a microtask and is held withslow. The hold then released early:Fast: 1besideSlow: 0withPending: false, andSlow: 1a second later. Without the probe, or withcopya plain getter, both reveal together.Traced: the Pending effect reads the pending-signal companions of
fastandcopy. Companions are optimistic nodes, so the effect runs under their lane. Inside the probe,pendingCheckReadpullscopyto refresh it, and that nested recompute inherited the effect's lane —recomputeonly sets a lane for a node that is OPT-dirty itself or adopts one through its deps, and otherwise leaves the puller's in force. Under a lane, a pending node on no lane serves its committed value instead of throwing (laneSuspends), socopyread the in-flightslowas0, published a clean value, and dropped its pending status. Its two readers then stopped counting as reporters forslow(reporterBlocksSource), andtransitionCompletesettled the transaction onfast's landing withslowstill in flight. The plain-getter shape never hit this: the probe readslowdirectly, and a probe observes without deriving (A23).Rule (A31)
A memo computes under its own lane posture, never its puller's. Its value is one shared slot every reader sees, so the lane's read carve-outs — sound for the lane's effects, whose runs are that view — must not shape a memo's cached result.
recomputeclears the ambient lane for a non-effect node before the two lane branches re-establish what the memo itself owns (OPT-dirty, or adopted through its deps); effects keep the ambient lane. One assignment:copy's pull now throwsNotReadyas a plain reader would, stays pending, and the hold lasts until both flights land:Note the rule is broader than the probe path: any lane-carrying effect that pulled a lane-less memo used to compute it under the lane's carve-outs. Both suites pass with the wider rule, but flagging it in case the inherited posture was load-bearing somewhere the tests don't reach. The spec entry is marked
live(stated by the fix), notruled.Verification
@solidjs/signals: 171 files / 1799 tests green, including the newtests/ispending-combined-atomic-3442.test.ts(the issue shape, the plain-getter control, and the no-probe control, each pinned to exact frames on a manual clock).@solidjs/web: 85 files / 781 tests green, including the newtest/ispending-combined-atomic-3442.spec.tsx— a jsdom port of the issue's playground (real click, DOM sampled through the hold) with the same two controls; fails onnext, passes here.nextbuild (+ createStore+62, hydrating-no-stores +44); every other scenario is flat or smaller. These are the same two caps fix(signals): an effect's dependencies are the committed frame's until its run applies (#3438) #3439 ratchets, so whichever lands second needs a two-line rebase.recomputeand the test; rules index regenerated (--checkclean; the one unresolved test id is pre-existing onnext). Changeset included.🤖 Generated with Claude Code