feat(gateway): add backoff calculation utilities

This commit is contained in:
Trevin Chow 2026-01-29 11:38:18 -08:00 committed by Trevin Chow
parent 4583f88626
commit 5a3f915641
2 changed files with 78 additions and 0 deletions

View File

@ -0,0 +1,48 @@
// src/cli/gateway-cli/backoff.test.ts
import { describe, it, expect } from "vitest";
import { calculateBackoffMs, applyJitter } from "./backoff.js";
describe("calculateBackoffMs", () => {
it("returns 0 for zero consecutive failures", () => {
expect(calculateBackoffMs(0)).toBe(0);
});
it("returns 2000ms for first failure", () => {
expect(calculateBackoffMs(1)).toBe(2000);
});
it("returns 4000ms for second failure", () => {
expect(calculateBackoffMs(2)).toBe(4000);
});
it("returns 32000ms for fifth failure", () => {
expect(calculateBackoffMs(5)).toBe(32000);
});
it("caps at 60000ms for high failure counts", () => {
expect(calculateBackoffMs(10)).toBe(60000);
expect(calculateBackoffMs(100)).toBe(60000);
});
});
describe("applyJitter", () => {
it("returns 0 for 0 input", () => {
expect(applyJitter(0)).toBe(0);
});
it("returns value within +/- 10% of input", () => {
const input = 10000;
const minExpected = Math.floor(input * 0.9);
const maxExpected = Math.ceil(input * 1.1);
for (let i = 0; i < 100; i++) {
const result = applyJitter(input);
expect(result).toBeGreaterThanOrEqual(minExpected);
expect(result).toBeLessThanOrEqual(maxExpected);
}
});
it("returns an integer", () => {
const result = applyJitter(2000);
expect(Number.isInteger(result)).toBe(true);
});
});

View File

@ -0,0 +1,30 @@
// src/cli/gateway-cli/backoff.ts
const BASE_BACKOFF_MS = 2000;
const MAX_BACKOFF_MS = 60_000;
const JITTER_FACTOR = 0.1; // +/- 10%
/**
* Calculate exponential backoff based on consecutive failures.
* Formula: min(BASE * 2^(failures-1), MAX)
*
* @param consecutiveFailures - Number of consecutive failures (0 = no backoff)
* @returns Backoff duration in milliseconds
*/
export function calculateBackoffMs(consecutiveFailures: number): number {
if (consecutiveFailures <= 0) return 0;
const exponential = BASE_BACKOFF_MS * Math.pow(2, consecutiveFailures - 1);
return Math.min(exponential, MAX_BACKOFF_MS);
}
/**
* Apply random jitter to a backoff value to prevent thundering herd.
*
* @param baseMs - Base backoff in milliseconds
* @returns Backoff with +/- 10% jitter applied, rounded to integer
*/
export function applyJitter(baseMs: number): number {
if (baseMs <= 0) return 0;
const jitter = (Math.random() - 0.5) * 2 * JITTER_FACTOR * baseMs;
return Math.round(baseMs + jitter);
}