fix(process): report no remaining time after a provider deadline fires
Node timers can fire while performance.now() is still a fraction of a millisecond short of the monotonic deadline. ProviderStartupDeadline then reported interruption "timeout" and an aborted signal while remainingMs() returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0 (Hopper and Ghidra startup) could run past an elapsed deadline, and the real-timer test "aborts a bounded wait when its real monotonic deadline expires" failed intermittently in CI (expected 1 to be 0). Once the deadline's own timer has classified a timeout, report zero remaining time. A deterministic regression test fires the timer with the monotonic clock 0.4 ms short of the deadline. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c02fb0ea80
commit
dfdaa0ca74
2 files changed
+20
No files matched your search
@@ -33,3 +33,20 @@ it("aborts a bounded wait when its real monotonic deadline expires", async () =>
|
||||
deadline.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it("reports no remaining time once its timer fires before the monotonic deadline", () => {
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
||||
const now = vi.spyOn(performance, "now").mockReturnValue(1_000);
|
||||
const deadline = new ProviderStartupDeadline(40);
|
||||
try {
|
||||
// Timers may fire while the monotonic clock is still just short of the deadline.
|
||||
now.mockReturnValue(1_039.6);
|
||||
vi.advanceTimersByTime(40);
|
||||
expect(deadline.signal.aborted).toBe(true);
|
||||
expect(deadline.interruption).toBe("timeout");
|
||||
expect(deadline.remainingMs()).toBe(0);
|
||||
} finally {
|
||||
deadline.dispose();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
@@ -85,6 +85,9 @@ export class ProviderStartupDeadline {
|
||||
|
||||
/** Milliseconds still available to the next startup phase. */
|
||||
remainingMs(): number {
|
||||
// Node timers can fire a fraction of a millisecond before the monotonic
|
||||
// deadline, so an elapsed timer must not report time left to spend.
|
||||
if (this.#interruption === "timeout") return 0;
|
||||
return Math.max(0, Math.ceil(this.#deadlineAt - performance.now()));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user