Skip to content

test(router): pin the tag-store registry LRU order after a touch - #1247

Merged
ivogt merged 1 commit into
reorg/stackfrom
reorg/t158-lru-test
Oct 23, 2026
Merged

ivogt merged 1 commit into
reorg/stackfrom
reorg/t158-lru-test

Conversation

@ivogt

@ivogt ivogt commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

devcorner: rango-t158 · session rango-s285

Problem

The explicit tagged-store registry (registerExplicitTaggedStore in packages/rangojs-router/src/cache/cache-policy.ts) is LRU-bounded at 64 entries: re-resolving a store does set.delete(store); set.add(store) so a module-singleton store moves to the most-recent end and is not evicted by per-request churn. The test named for that touch, "keeps a re-resolved (live) store reachable despite per-request churn (LRU touch)", still passed with set.delete(store) removed. Without the touch, churn evicts the live store, and the next resolveCacheStore(liveStore) adds it back, so the final has(liveStore) holds either way. The touch had no test that pinned it (found in review rango-t131-c2; predates S1).

Test-only; no router code changes.

Before / after (internal shape)

Before: 200 rounds of "resolve the live store, resolve a fresh store", then expect(set.has(liveStore)).toBe(true).

After: fill the registry to its cap with s0..s63, re-resolve s0, resolve one newcomer, and assert the whole order by store identity:

expect([...explicitTaggedStores].map((s) => names.get(s))).toEqual([
  ...stores.slice(2).map((s) => names.get(s)), // s2 .. s63
  "s0",
  "newcomer",
]);

The stores are mapped to names because distinct empty MemorySegmentCacheStore instances are deep-equal, so toEqual on the instances themselves would not see the order.

Code changes

  • packages/rangojs-router/src/cache/__tests__/tag-store-registry.test.ts: the churn test is replaced by "moves a re-resolved store to the most-recent end, so eviction takes the least recently resolved (LRU touch)". The other six tests are unchanged.

Test plan

  • vitest run src/cache/__tests__/tag-store-registry.test.ts: 7/7 pass.
  • Red under the mutation: with set.delete(store) removed from registerExplicitTaggedStore, the new test fails (1 failed, 6 passed): expected [ 's1', 's2', … ] to deeply equal [ 's2', 's3', … ], i.e. s0 was evicted and s1 kept. Restored, it passes.
  • The base version of the test file under the same mutation: 7/7 pass, confirming the gap.
  • Router tsc --noEmit (its include covers src/**, tests included): pass.
  • node tools/check-internals.mjs: OK, counts unchanged (request-context-object-create 21, internal-vi-mock 327, rsc-param-literal 71, browser-import-into-server 1).
  • oxlint --deny-warnings: 0 warnings, 0 errors. oxfmt --check .: clean.
  • test:unit:all and e2e not run: test-only change in one file, run directly.

Practices

  • Lint: oxlint --deny-warnings and oxfmt --check . on the whole repo; nothing found.
  • Dead code: the change replaces one test body and adds no helpers or exports; nothing is left unused.
  • Tests: the touched file passes; the new test is shown red against the mutation it targets and green without it.
  • Code review: the diff was reviewed by hand. The registry cap (64) is spelled in the test with a comment naming EXPLICIT_STORE_REGISTRY_CAP, as the neighbouring bound test already does with the literal; exporting the constant from cache-policy.ts for a test was left out to keep this change test-only.
  • Docs: none describe this test; nothing needs changing.

Notes

  • The removed churn scenario's claim (a live store stays reachable) also holds through re-adding after eviction, so it did not pin the touch. The new test pins the mechanism itself; the bound test above it still covers growth under churn.

…an LRU touch

The "LRU touch" test passed with the touch (`set.delete(store)` in
registerExplicitTaggedStore) removed: a live store evicted by churn is
re-added on its next resolve, so it was still present at the end.

The test now fills the registry to its cap, re-resolves the oldest store,
adds one more, and asserts the full order by identity: the touched store
sits before the newcomer and the next-oldest is the one evicted.
@ivogt
ivogt merged commit d43d8f8 into reorg/stack Oct 23, 2026
19 checks passed
@ivogt
ivogt deleted the reorg/t158-lru-test branch October 23, 2026 17:59
ivogt added a commit that referenced this pull request Oct 23, 2026
Brings in S16 (#1248, wire-params.ts WIRE), S5 (#1245, deriveRenderPass),
S15 (#1243, discovery phases), #1246 and #1247.

Conflicts, each resolved by keeping S14's structure and applying S16's
intent to it:

- rsc/handler.ts imports: kept the request-stages.js import; dropped the
  classifyRequest, RequestPlan and INTERNAL_RANGO_DEBUG imports S14 had
  already moved out; added S16's WIRE import, still read by the code that
  stayed in the handler (warmup, loader refuse, redirect tracking mode,
  isPartialAction).
- rsc/handler.ts response tail: kept the finalizeRequestResponse call; the
  inline partial/action interception S16 rewrote lives in
  request-stages.ts and now reads WIRE.partial and WIRE.action.
- rsc/handler.ts terminal plans: kept the terminalPlanResponse call; the
  action-rewrite check, partial redirect and version-mismatch log S16
  rewrote live in request-stages.ts and now read WIRE.action, WIRE.partial
  and WIRE.version.
- testing/dispatch.ts imports: added WIRE; dropped applyStreamIdleTimeout,
  which the stages now call.
- testing/dispatch.ts isPartial: kept S14's shape (the reverse and isAction
  moved into the stages) with S16's WIRE.partial.
- tools/internals-baseline.json: took the stack's version;
  check-internals --update leaves it unchanged (rsc-param-literal 4, all in
  use-loader.tsx).

request-stages.ts has no `_rsc_*` literal left. The handler and stages
together make the same searchParams calls as S16's handler.ts: has() on
action x3, partial x3, loader and warmup; get() on loader and version.
ivogt added a commit that referenced this pull request Oct 23, 2026
Brings in S16 (WIRE), S5 (deriveRenderPass), S15 (discovery phases),
S14 and S14b (request stages, dispatch), S10b (Vercel store), S6
(ShellCaptureState), #1246 and #1247.

Conflicts:
- src/server/request-context.ts: S6 removed `_shellCaptureRun`, whose doc
  comment S9 edited. Kept S6's `_shellCaptureState` field and doc. S9's
  fact (the capture switch is read by `resolveOwnerLoaders`, not
  `fresh.ts emitStreaming`) moved to `ShellCaptureState.run` in
  rsc/shell-capture.ts, which carried the old text.
- docs/design/ppr-shell-resume.md: S6's `_shellCaptureState.run` with
  S9's `resolveOwnerLoaders` settle guard in place of `fresh.ts`
  `emitStreaming`.
- docs/internal/feature-file-map.md: one table hunk (both sides realigned
  the columns). Each row taken from the side that changed it; the two
  rows both sides changed are combined: "PPR shell caching (axis 2)" keeps
  S6's field names and S9's revalidation.ts link in place of fresh.ts;
  "Internal unit-test kit" lists S6's makeShellCaptureState and S9's
  makeFreshPass.
- docs/internal/feature-map.md: the transition() entry keeps S15's
  vite/discovery/dev-server-phase.ts and S9's `revalidation.ts` without
  `fresh.ts`.

Semantic fix:
- src/server/__tests__/render-pass.bench.tsx (S5): the background
  re-render's RouterContext stub gave `resolveAllSegments`, which S9
  removed; rerenderAndCacheRoute now calls
  `resolveAllSegmentsWithRevalidation`, so the stub gives that. Without
  it the "background re-render (stub resolution)" bench reports NaN.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant