Skip to content

CS-11029: in-process inflight dedup for #moduleCache transpile#4752

Draft
lukemelia wants to merge 1 commit intocs-11028-in-process-persist-after-invalidate-fix-for-realmmodulecachefrom
cs-11029-in-process-inflight-transpile-dedup-for-realmmodulecache
Draft

CS-11029: in-process inflight dedup for #moduleCache transpile#4752
lukemelia wants to merge 1 commit intocs-11028-in-process-persist-after-invalidate-fix-for-realmmodulecachefrom
cs-11029-in-process-inflight-transpile-dedup-for-realmmodulecache

Conversation

@lukemelia
Copy link
Copy Markdown
Contributor

Stacks on #4750 (CS-11028). Review/merge that first.

Summary

  • Adds Realm.#inFlightTranspiles — a Map<LocalPath, Promise<…>> keyed by local path. The first caller to miss #moduleCache installs a pending entry; concurrent same-path callers await the same promise instead of each running babel from scratch (50–500 ms per duplicate transpile saved).
  • Refactors loadModuleFromDisk to delegate the materialize + transpile step to #transpileModuleDeduped / #materializeAndTranspile. Public behavior unchanged — same response shape, same etag, same error handling.
  • Identity-checked .finally cleanup mirrors CachingDefinitionLookup.#inFlight (CS-10947) — a newer pending entry installed after an invalidate drop is preserved when an older promise eventually settles.
  • The #dropModuleCacheEntry / #dropAllModuleCacheEntries helpers from CS-11028 now also drop the in-flight entry, so every invalidation site (writeMany, delete, file-watcher callback, executable-invalidation cascade, full-index clear, etc.) clears in-flight too. Existing waiters on a dropped promise still receive its bytes — their requests preceded the invalidate, so pre-invalidation bytes are correct for them. New callers don't join the stale transpile because it's no longer in the map.
  • Test seams __testOnlyGetTranspileCallCount() and __testOnlyGetInFlightTranspileCount() let the dedup tests assert "exactly one transpile call serviced N concurrent same-path readers" and "the slot released on settle" directly rather than inferring it from timing.

Same shape as CS-10947 for CachingDefinitionLookup. Stacks well with CS-11028: the inflight dedup catches the "many readers, one transpile" case; the generation discard catches the "transpile completed against pre-invalidate bytes" case.

Linear: https://linear.app/cardstack/issue/CS-11029

Test plan

  • CI passes the new `Realm.#moduleCache in-flight transpile dedup` module in `module-cache-race-test.ts`:
    • N concurrent same-path readers trigger exactly one transpile call
    • concurrent different-path readers each trigger their own transpile (no false coalesce)
    • in-flight entry survives an unrelated path's invalidate
    • in-flight entry is dropped when its own path is invalidated; later caller starts a fresh transpile
    • identity-checked cleanup: A in-flight + invalidate + B in-flight + A settles → B survives
    • errored transpile is shared with concurrent waiters and the in-flight slot releases on rejection
  • Existing CS-11028 race tests still pass.
  • Existing module-cache regression tests in `realm-endpoints-test.ts` still pass (etag, 304, content-type).

🤖 Generated with Claude Code

Two concurrent in-process readers that miss #moduleCache for the same
path used to each call transpileJS independently (50–500 ms of babel +
ember-template-compilation + decorator transforms + scoped-css). Output
was bit-identical; the second pass was wasted CPU.

Adds Realm.#inFlightTranspiles — a Map<LocalPath, Promise<…>> keyed by
local path. The first caller to miss the cache installs a pending entry;
concurrent same-path callers await the same promise instead of running
babel again. Identity-checked .finally cleanup mirrors
CachingDefinitionLookup.#inFlight — a newer pending entry installed
after invalidate drops the slot is preserved when an older promise
eventually settles.

invalidateCache (via the shared #dropModuleCacheEntry helper from
CS-11028) drops the in-flight entry so post-invalidate callers don't
join a stale transpile whose cache.set would just be discarded by
CS-11028's generation guard anyway. Existing waiters still get the old
promise's bytes — their requests preceded the invalidate, so
pre-invalidation bytes are correct for them.

The realm exposes __testOnlyGetTranspileCallCount /
__testOnlyGetInFlightTranspileCount so dedup tests can assert "exactly
one transpile call serviced N concurrent same-path readers" and "the
slot released on settle" directly rather than inferring it from timing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

Host Test Results

    1 files      1 suites   1h 46m 37s ⏱️
2 644 tests 2 599 ✅ 15 💤  0 ❌ 30 🔥
2 663 runs  2 588 ✅ 15 💤 30 ❌ 30 🔥

Results for commit 5e2aa2b.

For more details on these errors, see this check.

Realm Server Test Results

    1 files      1 suites   14m 55s ⏱️
1 307 tests 1 289 ✅ 0 💤 18 ❌
1 386 runs  1 368 ✅ 0 💤 18 ❌

Results for commit 5e2aa2b.

For more details on these errors, see this check.

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