Repository navigation
fix(signals): don't retain the transaction a latest() read opened after it lands - #4460
Conversation
…er it lands `pendingVerdict` (#4453) remembered the flush's transaction a `latest` read of an unheld flight opened in a module-level `latestJoined`, compared only against `flushTransaction` and never cleared: after the seam it kept the last landed transaction (its `_nodes`, `_queues`, `_reruns`) alive until the next such read overwrote it. The mark now lives on the transaction itself (`Transaction.latest`), so it ends with it. Same answer for every probe: a transaction stays the flush's from its opening to the seam, a merge keeps the flush's own, and one merged away is no longer anyone's flush transaction, as before. The property is not `_`-prefixed (as `folded`), so the mangler's assignment is untouched; only bundles importing `latest`/`isPending` change, and they shrink (isPending/latest -13 B brotli, -10 B minified). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: e10c1fc The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 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 |
Size (brotli, eager entry chunk)
|
The #4400 case writes s, waits 3 ms, and writes again while a 10 ms ask is in flight. On CI's coverage run the real gap outlived the ask, which landed before the second write and asked `m1:23` (seen on #4455 and #4460). The gaps are part of each scenario, so the file now advances fake timers instead of sleeping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
The The gaps are part of each scenario, so e10c1fc moves that file to fake timers ( Generated by Claude Code |
Coverage Report for CI Build 41052495050Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage remained the same at 78.1%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will improve performance by 10.46%2 warnings
Performance Changes
Tip Curious why performance improved? Comment Comparing Footnotes
|
…ad; seat rows D1''/L1''/V3'' (#4472) * fix(signals): a netted write read in a strict untrack is no staged read; seat rows D1''/L1''/V3'' #4452 made a signal's staging equal to its committed value (writes that netted, D-8) a read of the screen in `read`'s fast block. The reader-seat table still raised Fl on that read (rows D1 / L1 / D7, and V3 for a verdict reader), so the shadow's seat parted from the flags and every later read of the pass reported too ("world after the read", "verdict seat after the read", B2 "born held"): 8 INV-seat-3 classes (x59) in the signals suite, 7 in web, and 60 in the generator (37 / 35 / 43 at 2,000 cases x seeds 3289 / 91501 / 7, every pair). Bisected over every merge 7135a77..131ceb8: 0 before #4452 (66c6f56), 60 at it, none added by #4453, #4458, #4460, #4454, #4457 or #4455. The runtime follows the rule (D-8: a tick whose writes net to the committed value made no proposal), so the table was stale: rows D1''/L1'' (D7'' in a lane) and V3'' serve P and raise nothing. Shadow: 0 classes in signals, web and the generator. With the rows in, the shadow found the path #4452 missed: in dev a labelled `untrack` read (`strictRead`) takes the slow path, where `serve` still called `stagedRead`, so dev held the errored re-run that prod (fast block) does not. `serve` now skips it for a netted signal in dev; prod folds the test away (0 B on every scenario). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): gate serve's netted skip on the strict read, not on dev Only the strict untrack read skips the fast block in dev; a hydration snapshot or a promoted write reaches serve in dev and prod alike and keeps the staged-read mark. 0 B in prod. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Before: #4453 recorded the flush transaction that a
latest()read of an unheld flight opened in a module-levellatestJoined, and nothing ever cleared it. After that flush's seam, it kept the landed transaction, along with its node and queue arrays, alive until the next such read overwrote it. A follow-up nit from the #4453 review.After: the mark lives on the transaction itself (
latest = 1), and the probe checksflushTransaction?.latest. The flag is collected together with the transaction, so nothing is retained. The answer is the same as the oldlatestJoined === flushTransactioncheck in every case:How
verdict.tsdropslatestJoined.pendingVerdictsets the flag where it used to assign, and checks it where it used to compare.scheduler.tsgains a type-onlylatest?: 1field onTransaction. There is no new export._-prefixed, following thefoldedprecedent, so the property mangler's name assignment doesn't shift.= 1beat= trueon brotli.Size vs next @ 42924f4 (brotli / minified):
signals: + isPending/latestpage: live server components(frozen floor)page: compiled live server componentspage: live + routerHow did you test this change?
packages/signals/tests/gc.test.ts: "releases the transaction a latest() read of an unheld refetch opened once it lands". It runs alatest(m)+isPending(m)reader through a refetch, takes aWeakRefto the transaction the read opened, lets the refetch land and forces GC. It fails onnextbecause the transaction is still reachable, and passes with the fix (5/5 runs).uncommitted-input-overwritten.spec.tsxfail in this container both onnextand on this branch, and everything else passes.Generated by Claude Code