perf(console): share one in-flight GET instead of racing duplicates on boot - #5658
Conversation
…n boot Fixes #5544 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An AbortSignal belongs to one caller; sharing behind two signals would let either caller's abort cancel the other's read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
PM review — Q1: A. Q2: A. And the falsified premise is mine to own, so let me do that first. The card's headline was my measurement error
For the record, that is the third measurement-methodology error of mine this epic has caught: the circular ~35-query estimate (cloud#1518), the stale staging baseline (cloud#1546), and now pathname-only grouping. All three share a shape — a number that looked measured but carried an unexamined assumption — and all three were caught by someone re-measuring rather than trusting the card. What survived is worth havingTwo real duplicates, both attributed to the line: Q2 — scope: A, and the reasoning deserves quoting
Q1 — branch: A. The
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #5544
What was actually duplicated
I re-derived every row of the card's two tables against
origin/mainbefore writinganything. Three of the five endpoints are not duplicates, and the earlier round on
this card (#5593, merged) already
established two of them:
maindata/sys_user_preference×3ConsoleShell.tsx:306/311/316attaches three adapters with three distinct keys (ui.favorites,ui.recent,ui.flow.palette.recents). The card's probe groups bynew URL(r.name).pathname, which discards the query string thekeypredicate rides in. Pinned by #5593.meta/object×2,meta/view×2MetadataProvider.requestBudget.test.tsx.auth/get-session×2apps/console/src/lib/auth-preflight.ts:52probes Bearer-only withcredentials: 'omit', precisely so the cookie cannot mask a stale token;AuthProviderthen asks by cookie. Deduping this destroys the signal the first call exists to read.runtime/config×2auth/me/localization×2The two real ones are what this PR closes. Both are the shape the card describes — separate
callers with no shared provider between them, so no per-component guard (the #1477 technique)
can see across them.
GET /api/v1/runtime/config— the pre-React branding script inlined inapps/console/index.html:25fetches it during HTML parse, andinitRuntimeConfig()(
packages/app-shell/src/runtime-config.ts:163) fetched it again from the module chunk.This is the expensive one:
apps/console/src/main.tsx:73awaitsinitRuntimeConfig()inside the
Promise.allthat gatescreateRoot().render(), so the duplicate sat on thecritical path to first paint. Joining the earlier request removes a round trip and lets
that await settle sooner, because it inherits a request that started hundreds of ms before
the bundle was even fetched.
GET /api/v1/auth/me/localization—seedTenantLanguage()keeps its request runningpast the 500 ms race by design, and
LocalizationFetchProvidermounts the moment that raceresolves, so on a first visit the two overlap.
languageSeed.ts's own docblock alreadydescribes them as one request ("without a second request here"); they were two.
The mechanism
@object-ui/typesgainssharedGetJson()(sibling tohttp-retry.ts, in the lowestpackage every caller can reach). It shares the in-flight promise and nothing else:
stale window. A caller arriving after settle fetches fresh, exactly as before;
LocalizationFetchProvider's retry policy still sees its own503+Retry-After;are as independent as two consumers of two requests;
AbortSignalopts out entirely (it neither joins nor is joined):a signal belongs to one caller, and sharing behind two would let either caller's abort
cancel the other's read.
Requests differing in credentials mode or headers keep separate identities, which is what
keeps the deliberate
get-sessionpair a pair.Why the registry lives on
globalThisOne of the two
runtime/configcallers is an inline classic script — it has to runbefore any module chunk executes, so it cannot import. A module-scoped
Mapcould neverreach it, and the most valuable duplicate would have survived the fix.
Symbol.for('objectui.inflightGet')gives both worlds one registry with no import and nobundler cooperation. The script spells the request key out by hand; the drift that creates
is closed mechanically, not by comment —
apps/console/src/__tests__/runtimeConfigBootDedup.test.tsextracts the shipped script's text out of
index.htmland executes it (the patterninsecure-origin-crypto.test.tsalready uses), andhttp-inflight.test.tsassertsinflightGetKey()produces the exact string that script builds.Verification — all at final head
a6bd4f812Exit codes captured before any pipe.
packages/types/src/__tests__/http-inflight.test.ts,apps/console/src/__tests__/runtimeConfigBootDedup.test.tsandapps/console/src/__tests__/localizationBootDedup.test.tsx. They pin all four propertiesthe card asks for — N concurrent same-key GETs ⇒ one call and every caller served;
rejection fans out; different keys never share; a wave after settle refetches — plus the
end-to-end boot through the real
index.html.Test Files 215 passed (215)/Tests 1950 passed (1950)(
packages/types/,apps/console/,packages/app-shell/src/{console,layout,providers}/,runtime-config.test.ts, and the i18n / gantt localization consumers).@object-ui/types+@object-ui/app-shell+@object-ui/console: exit 0,each script name echoed (so not a zero-match no-op), after building the console's
dependency closure.
pnpm lintexit 0 (0 errors repo-wide);eslint --no-inline-config --format jsonover the 7 changed files reportsfiles linted: 7, errors: 0, warnings: 0.check-lint-coverage:46/46 packages linted, 0 with outstanding errors.check-control-bytes:OK (scanned 4708 tracked text file(s); skipped 85 binary).✅ 8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s);✅ No changeset declares a major bump;✅ All workspace packages are in the changeset fixed group.Ablation
The join was disabled at its only decision point —
const existing = reg.get(key)becameconst existing = undefinedinsharedGetJson, i.e. the sharing removed while theregistration stays. No rebuild is required and that is proven rather than assumed:
vitest.config.mts:261aliases@object-ui/typestopackages/types/src, so the suitesload mutated source. The mutation was confirmed on disk by anchored counts in both
directions (original anchor 1 → 0, mutant anchor 0 → 1,
1 file changed, 1 insertion(+), 1 deletion(-)).Result: 9 of 21 cases red, every one of them the duplicate reappearing —
expected "vi.fn()" to be called 1 times, but got 2 times(and3, and5). The negative controlscorrectly stayed green: different keys still did not share, the key-format assertions
held, and the GET-only refusal held. Restored by a
trap … EXIT INT TERM; the restore legwas verified the same way (original anchor back at 1, mutant at 0,
git status --porcelainempty).
What I did not measure
The card's reproduce snippet is a browser probe against prod, and I did not take a local
equivalent, because a faithful one is not available from this repo: the sharing is in-flight
only, so it requires the two calls to overlap, and a Vite dev server answers
/api/v1/runtime/configwith an instant 404 — the pre-boot request settles before the modulechunk runs and both fixed and unfixed builds show ×2. The prod/staging numbers in the card
are its measurement, not mine. What the win depends on is stated plainly: it is proportional
to server latency, and the card's own prod durations (512 / 431 ms for a fetch that starts at
parse time) show the overlap is real there.
Scope
out of scope: #5593— its pin stays exactly as merged. No component receives anythingdifferent: same payloads, same errors, one fewer round trip.
Generated by Claude Code