OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface-android / lib / host / retry-policy.js

40 lines · javascript ref:
// How long to wait before trying a FAILED fetcher construction again.
//
// ⛔ WHY THIS IS NOT THE INTERVAL ota.js REFUSES. Its comment is right and stands:
//
//     ⛔ NO INTERVAL. The watch is the signal; a timer beside it would be an unconditional sweep,
//        and hyper-social #68 §2 removed exactly that from delivery because a sweep CONCEALS a dead
//        hook — the update appears to work, so nobody notices nothing fires on the event.
//
// A SWEEP polls for news and hides a dead watch. This is not that. The watch has ALREADY fired and
// handed us a release; we failed to build a fetcher for it. Retrying that one known-pending entry is
// recovery from a named failure, and it cannot conceal a dead hook because it never runs unless the
// hook fired. The guard that keeps the distinction honest is in ota.js: no pending entry, no retry.
//
// ⛔ MEASURED, WHICH IS WHY THIS EXISTS. Handset, 2026-09-15:
//     15:58:12  ota: no swarm fetcher (NetworkFetcher: no peer within 120000ms)
//     …ten minutes of nothing…
//     16:08:39  ota: downloading 0.0.40      ← only because a release was published
//     16:08:59  ota: staged …
// The DHT miss was transient and the fetcher worked first try once retried. But nothing retried: a
// transient miss disabled OTA for the whole session, and the only cure was somebody publishing again.
//
// Backoff, not a tight loop: the failure mode being recovered from is a 120-SECOND timeout, so an
// eager retry would spend the battery holding a swarm open for a DHT that is not answering.

export const RETRY_MS = [15_000, 30_000, 60_000, 120_000, 300_000]

/**
 * Delay before attempt `n` (0-based), capped at the last step.
 *
 * ⚠ CAPPED, NOT EXHAUSTED. There is deliberately no attempt limit: a phone that gave up for good
 * would need a manual restart to ever update again, which is the state this whole change exists to
 * end. Five minutes apart is cheap enough to keep trying indefinitely.
 */
export function nextRetryDelay (n) {
  const i = Number.isFinite(n) && n > 0 ? Math.floor(n) : 0
  return RETRY_MS[Math.min(i, RETRY_MS.length - 1)]
}

export default { RETRY_MS, nextRetryDelay }