Skip to content

fix(approuter): eliminate legacy-redirects bootstrap/refresh index race - #1636

Merged
jung-thomas merged 1 commit into
mainfrom
worktree-fix-legacy-redirects-loader-race
Aug 11, 2026
Merged

fix(approuter): eliminate legacy-redirects bootstrap/refresh index race#1636
jung-thomas merged 1 commit into
mainfrom
worktree-fix-legacy-redirects-loader-race

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Problem

test/unit/legacy-redirects-loader.test.js has been flaking on CI main runs — e.g. the #1626 merge run and the #1627 merge run both failed on it:

FAIL  test/unit/legacy-redirects-loader.test.js > … accepts the OData v4 envelope { value: [...] } (the #1311 regression)
AssertionError: expected undefined to be '/topics/abap-platform.html'

It passes in isolation and green on re-run — a classic CI-only async race (documented in #1311 / #1409).

Root cause

In approuter/lib/legacy-redirects-loader.js, the module-load bootstrap IIFE and refresh() both write the same shared _index after each awaits a dynamic import(). #1311/#1409 tried to timing-guard this with a _loadedFromSrv flag that makes the bootstrap a no-op once refresh() wins — but two producers mutating one variable across await points remains schedule-dependent, and under CI parallel load _index could still end up pointing at the 3-row BOOTSTRAP_MAP. getIndex() then returned an index without /abapresolveRedirect('/abap') is undefined.

Fix

Remove the shared mutable target instead of timing-guarding it:

  • Bootstrap seed and live rows now live in separate variables — _bootstrapIndex / _liveIndex.
  • getIndex() returns _loadedFromSrv ? _liveIndex : _bootstrapIndex.
  • _liveIndex is assigned before _loadedFromSrv (no await between), so any reader that observes the flag already sees populated live rows.
  • The bootstrap IIFE only ever writes _bootstrapIndex, so it can never clobber live rows regardless of when its import() settles.

This also closes the production boot window (noted in #1409) where the approuter briefly served only the 3 bootstrap redirects.

Why no new timing test

The failure is inherently CI-scheduler-dependent and not locally reproducible; the old _loadedFromSrv guard would also pass a naive "bootstrap-after-refresh" unit test, so such a test gives false confidence. The change is structural — there is no longer a shared variable two producers race on. The existing 3 loader tests (+ 23 related redirect tests) pass deterministically against the new design.

Verification

npx vitest run --project unit test/unit/legacy-redirects-loader.test.js test/unit/legacy-redirects-resolver.test.js test/unit/catalog-legacy-redirects.test.js26 passed.

Separate from the merged #1626 (island-manifest guard fix).

test/unit/legacy-redirects-loader.test.js flaked intermittently on CI
(seen on the #1626 and #1627 main runs): "expected undefined to be
'/topics/abap-platform.html'". Passes in isolation; fails only under CI
parallel load.

Root cause: the module-load bootstrap IIFE and refresh() both write the
SAME shared _index after awaiting a dynamic import(). #1311/#1409 tried
to timing-guard this with a _loadedFromSrv flag that makes the bootstrap
a no-op once refresh() wins, but under CI scheduler ordering the shared
variable could still end up pointing at the 3-row BOOTSTRAP_MAP, so
getIndex() returned an index without /abap.

Fix structurally rather than with a timing guard: the bootstrap seed and
the live rows live in SEPARATE variables (_bootstrapIndex / _liveIndex);
getIndex() returns _liveIndex once _loadedFromSrv is set, else the seed.
_liveIndex is assigned before the flag (no await between), so any reader
that observes the flag already sees populated live rows, and the
bootstrap can never clobber live rows no matter when its import settles.
Also closes the boot window where the approuter briefly served only the
3 bootstrap redirects.
@jung-thomas
jung-thomas merged commit 2ce45c7 into main Aug 11, 2026
2 of 3 checks passed
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