/** * Time, as a parameter. * * Two things live here, and they are the same thing seen from two sides. * * For `index.ts` it is timer hygiene. Every timer in this extension must be * unref'd — a background loop that keeps the event loop referenced stops pi * from exiting, and the failure is silent until someone notices the process * will not die. That was sixteen hand-written `.unref?.()` lines; `after` * made it a property of how a timer is made. Two rules were left over and * still had to be remembered: clear it *and* null it, and cancel before * re-arming. A `TimerSlot` makes those properties too. * * For the modules that own a state machine it is the seam their tests come * in through. One `Timers` object supplies scheduling *and* the clock, and * that is deliberate: a module that took `now()` from one injection point * and its timers from another can be handed a fake clock that advances * without ever firing a callback, which is a test that passes while * asserting nothing. * * Two verbs, not one, and the distinction is load-bearing: * * arm() the latest request wins — cancel what was pending, schedule * anew. Debounce and watchdog: every new message restarts the * quiet window, every new dispatch restarts the timeout. * armIfIdle() the earliest request wins — already scheduled, leave it. * Coalescing a burst of writes into one, and any backoff whose * whole purpose is that the deadline does *not* move. * * They read almost identically at the call site and mean opposite things. * `schedulePersist()` is `armIfIdle` — turning it into `arm` makes the state * file a sliding debounce that never writes during a burst, and the symptom * only appears as a lost queue after a SIGKILL. * * This module deliberately imports nothing. Not from the project, not from * node. That is what keeps it loadable under node's type stripper, and it is * also the reason a slot *cannot* learn about `instanceDead`: reaching the * channel's state would require an import this file does not have, and adding * one would break the tests in the same commit. It matters because * `stopWorker()` schedules its SIGTERM fallback from inside * `retireInstance()`, with `instanceDead` already true — a slot that skipped * that callback would leave an orphan worker polling the account. * * Fire-and-forget stays fire-and-forget: `after()` returns nothing. Nobody * can cancel the SIGTERM fallback or a crash-restart, which is the point — * a handle is something a future "cancel everything on retirement" sweep * could reach. */ /** A timer that can be re-armed, and knows whether it is pending. */ export interface TimerSlot { /** Pending (one-shot) or running (repeating). Never stale. */ readonly armed: boolean; /** Latest request wins: cancel whatever was pending, schedule this. */ arm(ms: number, fn: () => void): void; /** Earliest request wins: already armed, keep the existing deadline. */ armIfIdle(ms: number, fn: () => void): void; /** Cancel. A no-op on an idle slot, and it never throws. */ cancel(): void; } /** * Everything time-shaped a module is allowed to reach for. * * `now` and `random` are here rather than beside the code that calls them so * that one fake drives all of it: the ownership lease reads a clock *and* * jitters a verify delay *and* renews on an interval, and those three have to * move together or the test is lying. */ export interface Timers { now(): number; /** 0..1. The lease's read-back jitter, so two instances do not read in lockstep. */ random(): number; /** Fire and forget. No handle, so nothing can cancel it — see the header. */ after(ms: number, fn: () => void): void; /** A slot that clears itself before running its callback. */ oneShot(): TimerSlot; /** A slot that stays armed until cancelled. */ repeating(): TimerSlot; } /** The real thing. Every handle it makes is unref'd. */ export declare function nodeTimers(): Timers; //# sourceMappingURL=timers.d.ts.map