/** * Scheduler utilities for calculating poll delays with exponential backoff. */ /** * Calculate the next poll delay in milliseconds. * Uses exponential backoff based on consecutive failures. * * Formula: delay = baseDelay * (2 ^ consecutiveFailures) + jitter * Capped at maxDelay. * * @param consecutiveFailures Number of consecutive failed attempts * @param baseDelayMs Base delay in milliseconds (e.g., 5000 for 5 seconds) * @param maxDelayMs Maximum delay in milliseconds (e.g., 300000 for 5 minutes) * @returns Delay in milliseconds before next poll */ export function calculateNextDelay( consecutiveFailures: number, baseDelayMs: number, maxDelayMs: number ): number { // If no failures, use base delay if (consecutiveFailures === 0) { return baseDelayMs + calculateJitter(baseDelayMs); } // Calculate exponential backoff: base * 2^failures // Cap the exponent to avoid overflow const cappedFailures = Math.min(consecutiveFailures, 10); const exponentialDelay = baseDelayMs * Math.pow(2, cappedFailures); // Apply cap const cappedDelay = Math.min(exponentialDelay, maxDelayMs); // Add jitter to prevent thundering herd return cappedDelay + calculateJitter(cappedDelay); } /** * Calculate a small random jitter (±10% of delay). * Prevents multiple feeds from syncing up and hitting the server simultaneously. * * @param delayMs Base delay in milliseconds * @returns Jitter offset in milliseconds (can be positive or negative) */ export function calculateJitter(delayMs: number): number { // ±10% jitter, max ±5000ms (5 seconds) const maxJitter = Math.min(delayMs * 0.1, 5000); return (Math.random() * 2 - 1) * maxJitter; } /** * Create a concurrency limiter function. * Limits the number of promises running concurrently. * * @param concurrency Maximum number of concurrent operations * @returns A function that wraps promises to enforce concurrency limit */ export function createConcurrencyLimit(concurrency: number) { const queue: (() => void)[] = []; let activeCount = 0; return function (fn: () => Promise): Promise { return new Promise((resolve, reject) => { const run = async () => { activeCount++; try { const result = await fn(); resolve(result); } catch (error) { reject(error); } finally { activeCount--; if (queue.length > 0) { const next = queue.shift()!; next(); } } }; if (activeCount < concurrency) { run(); } else { queue.push(run); } }); }; }