Skip to content

[finding] A fifth ref'd-timer pin scores a process-global count across an await — service-automation's engine.test.ts, outside #10685's site list #10783

Description

@claude

Found while implementing #10685, which named three sites in @objectstack/core. This is a fifth site, in a different package, carrying the same shape — and it was not on that card's list.

packages/services/service-automation/src/engine.test.ts:

constrefdTimers=()=>process.getActiveResourcesInfo().filter(r=>r==='Timeout').length;it("leaves no ref'd timer behind when the nodes win the race",async()=>{// ...constbefore=refdTimers();constresult=awaitengine.execute('guarded_flow');expect(result.success).toBe(true);expect(calls.count).toBe(3);expect(refdTimers()).toBe(before);// <- absolute count, across an await});

Why it is unsound

getActiveResourcesInfo() reports the whole process, so the absolute 'Timeout' count is ambient — it belongs to every co-tenant test file in the worker, not to the test reading it. Scoring a subject against it with toBe is sound only while the window between the two samples crosses no event-loop turn, because timer callbacks run in the timers phase and a microtask drain never reaches it.

Here the window is await engine.execute('guarded_flow'). It is green today only because registerInstantScript resolves on a microtask, so nothing in the path awaits a real timer. That is a property of code the test does not own, and it is written down nowhere — exactly the #10685 argument, one package over.

Measured, on the sibling sites

Under synthetic ambient noise (4000 ref'd timers armed at 1 ms spacing via a scratch setupFiles) the three @objectstack/core sites stayed green, and went red the moment a real-timer await was introduced into the measured window — a simulated retry backoff in bootstrap() and a debounce in reloadPlugin():

AssertionError: expected 3744 to be 3755 hot-reload.test.ts
AssertionError: expected 2037 to be 2047 kernel.test.ts
AssertionError: expected 2021 to be 2037 kernel.test.ts

The count goes down — foreign timers expiring mid-window — with a message pointing at the timer count rather than at the change that caused it. The same is true of this site the day anything in engine.execute()'s path awaits a real timer (a node retry, a queue drain, a rate limiter).

Remedy

The one PR #10786 landed for the three core sites: record the Timeout handles the subject arms — told apart by the timeout they were configured with — and ask how many of those are still holding the loop open, across a window that is synchronous by construction. In core that is packages/core/src/refd-timer-probe.testkit.ts; this package would need its own copy or a shared home, which is the only reason this was not fixed in the same PR (a different package, a different test surface, and #10685's brief named three sites).

Not urgent — nothing is red today.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions