You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed from PR #10661 (issue #10604) as an out-of-scope observation. Nothing is red today — this is about an unstated invariant, not a live failure. No fix is proposed here; that PR's brief scoped it to its own CI failure and it stayed there.
The idiom
Four @objectstack/core test files count ref'd timers with the same process-wide probe:
getActiveResourcesInfo() reports the whole process, and in CI a Test Core shard runs ~37 core test files in one worker. So the absolute count is ambient: it belongs to every co-tenant file, not to the test reading it. Scoring a subject against it with toBe is only sound while nothing else can move it between the two samples — which is true exactly when the window contains no event-loop turn, because timer callbacks run in the timers phase and a microtask drain never reaches it.
Who relies on that, and who says so
packages/core/src/health-monitor.test.ts:194 is the one that states the requirement and holds itself to it:
// Everything from here to the last assertion runs in one uninterrupted
// synchronous turn, so each difference is attributable.
Three others depend on the same property silently, with an await inside the measured window:
site
window
packages/core/src/kernel.test.ts — Startup timeout guards do not outlive the race (#4813) > leaves no ref'd timer behind after the plugin wins the race
await kernel.bootstrap()
packages/core/src/kernel.test.ts — same describe > reclaims one guard per lifecycle hook, for every plugin
They pass because those awaits resolve on microtasks (the plugin hooks are async () => {}, nothing in the path awaits a real timer), so the reading cannot move under them. That is a property of code they do not own, and it is written down nowhere.
Measured
PR #10661's fourth pin had the same shape plus a leg that waits 10 ms of real time, and that leg is the one CI reddened: expected 2 to be 4 at timeout-guard.test.ts:156:24 — the count went down, two foreign timers expiring mid-test. Reproduced synthetically by arming ambient ref'd timers at 1 ms spacing via a scratch setupFiles (same test, same line, expected 2872 to be 2885).
Run the three sites above under that same ambient noise and they stay green — 4 runs, 54 passed (54) each time for kernel.test.ts + health-monitor.test.ts + hot-reload.test.ts. So the exposure is real but currently unrealised.
Why it is worth recording
The invariant is invisible at the point where it would be broken. Adding anything that awaits a real timer inside one of those three windows — a retry backoff in bootstrap(), a debounce in reloadPlugin() — turns a green pin into an intermittent shard-only red whose message points at timers and not at the change that caused it. That is the same class as #10511 / PR #10626: a fixture scored against an ambient value it does not own.
Two shapes resolve it, both cheap:
assert non-increase (toBeLessThanOrEqual) — a leak is a growth, so the direction that matters survives and foreign expiry stops being a signal. This is what PR fix(core): reclaim both halves of the kernel's two timeout guards #10661 landed for its own pin, ablation-checked to still red on a real leak.
or keep the window synchronous and say so, as health-monitor.test.ts does.
Not urgent, and deliberately not labelled pm:queue.
Filed from PR #10661 (issue #10604) as an out-of-scope observation. Nothing is red today — this is about an unstated invariant, not a live failure. No fix is proposed here; that PR's brief scoped it to its own CI failure and it stayed there.
The idiom
Four
@objectstack/coretest files count ref'd timers with the same process-wide probe:getActiveResourcesInfo()reports the whole process, and in CI aTest Coreshard runs ~37 core test files in one worker. So the absolute count is ambient: it belongs to every co-tenant file, not to the test reading it. Scoring a subject against it withtoBeis only sound while nothing else can move it between the two samples — which is true exactly when the window contains no event-loop turn, because timer callbacks run in the timers phase and a microtask drain never reaches it.Who relies on that, and who says so
packages/core/src/health-monitor.test.ts:194is the one that states the requirement and holds itself to it:Three others depend on the same property silently, with an
awaitinside the measured window:packages/core/src/kernel.test.ts—Startup timeout guards do not outlive the race (#4813)>leaves no ref'd timer behind after the plugin wins the raceawait kernel.bootstrap()packages/core/src/kernel.test.ts— same describe >reclaims one guard per lifecycle hook, for every pluginawait kernel.bootstrap()packages/core/src/hot-reload.test.ts:80—expect(refdTimers()).toBe(before)await manager.reloadPlugin(...)They pass because those awaits resolve on microtasks (the plugin hooks are
async () => {}, nothing in the path awaits a real timer), so the reading cannot move under them. That is a property of code they do not own, and it is written down nowhere.Measured
PR #10661's fourth pin had the same shape plus a leg that waits 10 ms of real time, and that leg is the one CI reddened:
expected 2 to be 4attimeout-guard.test.ts:156:24— the count went down, two foreign timers expiring mid-test. Reproduced synthetically by arming ambient ref'd timers at 1 ms spacing via a scratchsetupFiles(same test, same line,expected 2872 to be 2885).Run the three sites above under that same ambient noise and they stay green — 4 runs,
54 passed (54)each time forkernel.test.ts+health-monitor.test.ts+hot-reload.test.ts. So the exposure is real but currently unrealised.Why it is worth recording
The invariant is invisible at the point where it would be broken. Adding anything that awaits a real timer inside one of those three windows — a retry backoff in
bootstrap(), a debounce inreloadPlugin()— turns a green pin into an intermittent shard-only red whose message points at timers and not at the change that caused it. That is the same class as #10511 / PR #10626: a fixture scored against an ambient value it does not own.Two shapes resolve it, both cheap:
toBeLessThanOrEqual) — a leak is a growth, so the direction that matters survives and foreign expiry stops being a signal. This is what PR fix(core): reclaim both halves of the kernel's two timeout guards #10661 landed for its own pin, ablation-checked to still red on a real leak.health-monitor.test.tsdoes.Not urgent, and deliberately not labelled
pm:queue.