// Retry is now waiting for 1000ms. This should not preempt cooldown.
requestHeartbeatNow({ reason: "hook:wake", coalesceMs: 0 });
await vi.advanceTimersByTimeAsync(998);
expect(handler).toHaveBeenCalledTimes(1);
// Runner A registers its handler const disposeA = setHeartbeatWakeHandler(handlerA);
// Runner B registers its handler (replaces A) const disposeB = setHeartbeatWakeHandler(handlerB);
// Runner A's stale cleanup runs — should NOT clear handlerB
disposeA();
expect(hasHeartbeatWakeHandler()).toBe(true);
// handlerB should still work
requestHeartbeatNow({ reason: "interval", coalesceMs: 0 });
await vi.advanceTimersByTimeAsync(1);
expect(handlerB).toHaveBeenCalledTimes(1);
expect(handlerA).not.toHaveBeenCalled();
// Runner B's dispose should work
disposeB();
expect(hasHeartbeatWakeHandler()).toBe(false);
});
it("preempts existing timer when a sooner schedule is requested", async () => {
vi.useFakeTimers(); const handler = vi.fn().mockResolvedValue({ status: "ran", durationMs: 1 });
setHeartbeatWakeHandler(handler);
// Schedule for 5 seconds from now
requestHeartbeatNow({ reason: "slow", coalesceMs: 5000 });
// Schedule for 100ms from now — should preempt the 5s timer
requestHeartbeatNow({ reason: "fast", coalesceMs: 100 });
await vi.advanceTimersByTimeAsync(100);
expect(handler).toHaveBeenCalledTimes(1); // The reason should be "fast" since it was set last
expect(handler).toHaveBeenCalledWith({ reason: "fast" });
});
it("keeps existing timer when later schedule is requested", async () => {
vi.useFakeTimers(); const handler = vi.fn().mockResolvedValue({ status: "ran", durationMs: 1 });
setHeartbeatWakeHandler(handler);
// Schedule for 100ms from now
requestHeartbeatNow({ reason: "fast", coalesceMs: 100 });
// Schedule for 5 seconds from now — should NOT preempt
requestHeartbeatNow({ reason: "slow", coalesceMs: 5000 });
it("resets running/scheduled flags when new handler is registered", async () => {
vi.useFakeTimers();
// Simulate a handler that's mid-execution when SIGUSR1 fires. // We do this by having the handler hang forever (never resolve).
let resolveHang: () => void; const hangPromise = new Promise<void>((r) => {
resolveHang = r;
}); const handlerA = vi
.fn()
.mockReturnValue(hangPromise.then(() => ({ status: "ran" as const, durationMs: 1 })));
setHeartbeatWakeHandler(handlerA);
// Trigger the handler — it starts running but never finishes
requestHeartbeatNow({ reason: "interval", coalesceMs: 0 });
await vi.advanceTimersByTimeAsync(1);
expect(handlerA).toHaveBeenCalledTimes(1);
// Now simulate SIGUSR1: register a new handler while handlerA is still running. // Without the fix, `running` would stay true and handlerB would never fire. const handlerB = vi.fn().mockResolvedValue({ status: "ran", durationMs: 1 });
setHeartbeatWakeHandler(handlerB);
// handlerB should be able to fire (running was reset)
requestHeartbeatNow({ reason: "interval", coalesceMs: 0 });
await vi.advanceTimersByTimeAsync(1);
expect(handlerB).toHaveBeenCalledTimes(1);
// Clean up the hanging promise
resolveHang!();
await Promise.resolve();
});
it("clears stale retry cooldown when a new handler is registered", async () => {
vi.useFakeTimers(); const handlerA = vi.fn().mockResolvedValue({ status: "skipped", reason: "requests-in-flight" });
setHeartbeatWakeHandler(handlerA);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.