fix(prekey): align bounded-retry ttl with outer UPLOAD_TIMEOUT race

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.
This commit is contained in:
Renato Alcara
2026-04-27 00:14:23 -03:00
parent 0700c44d59
commit 73d63b6304
+12 -4
View File
@@ -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
})