58 lines
1.6 KiB
TypeScript
58 lines
1.6 KiB
TypeScript
/** A timer owned by one process capture or replay endpoint. */
|
|
export interface ProcessTimer {
|
|
cancel(): void;
|
|
}
|
|
|
|
/** Injectable timer primitive for deterministic long-delay tests. */
|
|
export type ProcessTimerScheduler = (
|
|
callback: () => void,
|
|
delayMs: number,
|
|
) => ProcessTimer;
|
|
|
|
const systemTimerScheduler: ProcessTimerScheduler = (callback, delayMs) => {
|
|
const timer = setTimeout(callback, delayMs);
|
|
return { cancel: () => clearTimeout(timer) };
|
|
};
|
|
|
|
/** Schedule a delay using consecutive Node timers without shortening long waits. */
|
|
export const scheduleProcessDelay = (
|
|
delayMs: number,
|
|
callback: () => void,
|
|
schedule: ProcessTimerScheduler = systemTimerScheduler,
|
|
): ProcessTimer => {
|
|
if (!Number.isSafeInteger(delayMs) || delayMs < 0)
|
|
throw new RangeError("Process delay must be a nonnegative safe integer");
|
|
const maximumTimerDelay = 2_147_483_647;
|
|
let remaining = delayMs;
|
|
let timer: ProcessTimer | undefined;
|
|
let cancelled = false;
|
|
|
|
const scheduleNext = (): void => {
|
|
if (cancelled) return;
|
|
const interval = Math.min(remaining, maximumTimerDelay);
|
|
timer = schedule(() => {
|
|
if (cancelled) return;
|
|
remaining -= interval;
|
|
if (remaining === 0) callback();
|
|
else scheduleNext();
|
|
}, interval);
|
|
};
|
|
|
|
scheduleNext();
|
|
return {
|
|
cancel: () => {
|
|
cancelled = true;
|
|
timer?.cancel();
|
|
},
|
|
};
|
|
};
|
|
|
|
/** Create an owned interval that shares process-capture cancellation handling. */
|
|
export const scheduleProcessInterval = (
|
|
callback: () => void,
|
|
intervalMs: number,
|
|
): ProcessTimer => {
|
|
const timer = setInterval(callback, intervalMs);
|
|
return { cancel: () => clearInterval(timer) };
|
|
};
|