feat: implement exponential backoff for SSE reconnections

This commit is contained in:
Abhimanyu Saharan
2026-02-06 20:04:04 +05:30
parent eeec029ab4
commit e934ab0d76
2 changed files with 139 additions and 13 deletions

View File

@@ -0,0 +1,48 @@
export type ExponentialBackoffOptions = {
baseMs?: number;
factor?: number;
maxMs?: number;
jitter?: number;
};
export type ExponentialBackoff = {
nextDelayMs: () => number;
reset: () => void;
attempt: () => number;
};
const clampMs = (value: number, { min, max }: { min: number; max: number }): number => {
if (Number.isNaN(value) || !Number.isFinite(value)) return min;
return Math.min(max, Math.max(min, Math.trunc(value)));
};
export const createExponentialBackoff = (
options: ExponentialBackoffOptions = {},
): ExponentialBackoff => {
const baseMs = clampMs(options.baseMs ?? 1_000, { min: 50, max: 60_000 });
const factor = options.factor ?? 2;
const maxMs = clampMs(options.maxMs ?? 5 * 60_000, { min: baseMs, max: 60 * 60_000 });
const jitter = options.jitter ?? 0.2;
let attempt = 0;
return {
nextDelayMs: () => {
const raw = baseMs * Math.pow(factor, attempt);
const capped = Math.min(maxMs, raw);
const normalized = clampMs(capped, { min: baseMs, max: maxMs });
// K8s-style jitter: only add extra random delay (no negative jitter),
// which avoids thundering-herd reconnects.
const jitterFactor = Math.max(0, jitter);
const delay = normalized + Math.floor(Math.random() * jitterFactor * normalized);
attempt = Math.min(attempt + 1, 64);
return clampMs(delay, { min: baseMs, max: maxMs });
},
reset: () => {
attempt = 0;
},
attempt: () => attempt,
};
};