From 73d63b6304fe304ceb2f64a21eb2ddd272d38035 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 27 Apr 2026 00:14:23 -0300 Subject: [PATCH] fix(prekey): align bounded-retry ttl with outer UPLOAD_TIMEOUT race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug found while reviewing pre-key auto-replenishment behavior with the user. uploadPreKeys is wrapped in: Promise.race([uploadLogic(), 30s timeout]) ← UPLOAD_TIMEOUT Inside uploadLogic, my previous commit configured bounded-retry with ttlMs=60_000 (60s). Result: outer race always wins at 30s, bounded-retry never reaches its natural give-up. We lose the structured logs / metrics emitted on TTL exhaustion, AND if a retry was about to succeed at second 29, it gets killed by the outer race. Fix: - ttlMs reduced to UPLOAD_TIMEOUT - 2_000 = 28_000ms (safety margin so bounded-retry always reaches give-up before the outer race). - perAttemptTimeoutMs reduced 20s -> 8s so multiple attempts fit in 28s. - delays trimmed to [1000, 2000, 4000, 8000] (4 attempts, ≈15s with delays plus per-attempt time = comfortably within 28s budget). - query(node) timeout aligned with PER_ATTEMPT_TIMEOUT_MS=8s. Pre-key replenishment system (existed before this PR, untouched): - MIN_PREKEY_COUNT=200 (replenishment threshold) - INITIAL_PREKEY_COUNT=800 (top-up target — matches WA Business CDP capture: prekey-store=812 on registration) - Triggered on: CB:success login, every 6h auto-sync, Bad MAC recovery, specific peer prekey miss. Bounded-retry only handles HOW the upload retries on transient failure, not WHEN. The two layers are orthogonal. Build clean, 35/35 suites, 809/809 tests pass. --- src/Socket/socket.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index 8cc5e565..229a3ed9 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -645,7 +645,14 @@ export const makeSocket = (config: SocketConfig) => { // Upload to server. Bounded-retry handles backoff (replaces both // the previous circuit breaker AND the manual exponential backoff // retry loop with maxRetries=3). - const PER_ATTEMPT_TIMEOUT_MS = 20_000 + // + // IMPORTANT: bounded-retry's ttlMs MUST be < UPLOAD_TIMEOUT (the + // outer Promise.race below). Otherwise the outer race fires first + // and bounded-retry never reaches its natural give-up — losing + // structured logs / metrics. Use UPLOAD_TIMEOUT - 2s as a safety + // margin so bounded-retry always wins. + const PER_ATTEMPT_TIMEOUT_MS = 8_000 + const RETRY_TTL_MS = UPLOAD_TIMEOUT - 2_000 // Pass the matching timeoutMs to query() so a stale attempt does // not keep an iq listener registered after bounded-retry has moved // on to the next attempt (Copilot review on PR #393). @@ -658,9 +665,10 @@ export const makeSocket = (config: SocketConfig) => { try { await withBoundedRetry(uploadToServer, { name: 'uploadPreKeys', - // 1s -> 2s -> 4s -> 8s -> 10s (cap matches old MAX backoff) - delays: [1000, 2000, 4000, 8000, 10000], - ttlMs: 60_000, + // 1s -> 2s -> 4s -> 8s (cap). Max 4 attempts fit comfortably + // within RETRY_TTL_MS (28s) given perAttemptTimeoutMs=8s. + delays: [1000, 2000, 4000, 8000], + ttlMs: RETRY_TTL_MS, perAttemptTimeoutMs: PER_ATTEMPT_TIMEOUT_MS, logger })