Skip to content

Commit 70c2124

Browse files
lifeartclaude
andcommitted
fix(gxt-backend): cap LinkTo reactor fires in renderLinkToElement wrapper
The validator.ts cap (commit 29062f5) is gated by tracked metadata in _reactorMeta WeakMap that doesn't get populated reliably in the CI-built bundle (rollup tree-shake of @glimmer/validator's registerClassicReactor export, see renderer.ts:117 — the dev-mode alias works but the build-time static analysis falls back to the no-op shim, which means the renderer.ts reactor doesn't go through my cap path; manager.ts uses a relative import but the leak-debug snapshots still show 0 reactors in CI). This commit moves the cap into manager.ts directly inside the wrapped() callback that wraps renderLinkToElement's reactor — no diagnostic-metadata dependency. Each LinkTo reactor self-destructs after 10,000 lifetime fires regardless of build resolution, which bounds the cost of leaked reactors so they can't saturate the runloop and trigger the testem 1200s browser timeout. Smoke (14 modules / 333 tests) green. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 29062f5 commit 70c2124

1 file changed

Lines changed: 20 additions & 0 deletions

File tree

packages/@ember/-internals/gxt-backend/manager.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9479,9 +9479,29 @@ function renderLinkToElement(instance: any, args: any, fw: any): HTMLAnchorEleme
94799479
let _hasBeenConnected = false;
94809480
let _disconnectedTicks = 0;
94819481
let _preConnectTicks = 0;
9482+
// Hard-cap on lifetime fires for this LinkTo reactor. The
9483+
// _disconnectedTicks heuristic doesn't trip in routing tests that
9484+
// reattach the same <a> element across transitions, so the reactor
9485+
// can leak across tests and saturate the runloop. 10,000 fires is
9486+
// two orders of magnitude above any plausible real-app bound and
9487+
// bounds the cost of leaked reactors so they can't produce the
9488+
// testem 1200s browser timeout. The cap fires inside the wrapped
9489+
// callback (before invoking cb()) to prevent the runaway from
9490+
// accumulating further work on the runloop.
9491+
let _totalFires = 0;
9492+
const HARD_CAP = 10_000;
94829493
const _registerReactor = (cb: () => void) => {
94839494
let unsub: () => void = () => {};
94849495
const wrapped = () => {
9496+
_totalFires++;
9497+
if (_totalFires > HARD_CAP) {
9498+
try {
9499+
unsub();
9500+
} catch {
9501+
/* ignore */
9502+
}
9503+
return;
9504+
}
94859505
if (el.isConnected) {
94869506
_hasBeenConnected = true;
94879507
_disconnectedTicks = 0;

0 commit comments

Comments
 (0)