Skip to content

ADFA-4894 feat(dashboard): resilient content downloads — books/kiwix hardening + reconnect - #451

Merged
luisguzman-adfa merged 2 commits into
mainfrom
feat/ADFA-4894-rest-download-hardening
Aug 21, 2026
Merged

luisguzman-adfa merged 2 commits into
mainfrom
feat/ADFA-4894-rest-download-hardening

Conversation

@luisguzman-adfa

Copy link
Copy Markdown
Collaborator

What

Harden the REST job runners so a network drop mid-download recovers or fails cleanly,
instead of failing the whole batch at the first blip. Area 1 of the ADFA-4893 contract,
on top of the existing durable jobs.ts substrate.

Why

On real, intermittent links a single drop killed a whole content job, cancel could not
interrupt an in-flight fetch, and a canceled kiwix download left its partial behind.

How

  • net-retry.ts (new, pure, unit-tested): withRetry(fn) with exponential backoff + jitter
    that bails on cancel / AbortSignal.
  • jobs.ts: each job carries an AbortController; cancel() aborts it and the runner gets
    ctx.signal — so cancel interrupts an in-flight fetch, not just child processes.
  • books.exec.ts: per-attempt timeout + ctx.signal on both fetches, per-item retry with
    backoff (retries network / timeout / 5xx, not 4xx); a dropped EPUB no longer fails the
    batch; Aborted -> CanceledError so a canceled job lands on 'canceled'.
  • kiwix.exec.ts: prune the partial .zim + .aria2 on cancel (clean-on-cancel), plus aria2
    reconnect flags (--max-tries=5 / --retry-wait=5 / --timeout=60). The divergence from the
    app's Aria2Manager (--max-tries=1) is deliberate and documented: the app has an outer
    retry loop, the REST engine does not, so aria2 owns the reconnect here.

Out of scope

  • maps per-layer checkpoint: needs the on-box tile-extract.py output layout (outside this
    repo) — deferred.
  • The pause / resume / retry control surface (paused phase, /pause /resume /retry): a
    separate commit/PR, the REST mirror of the APK-side ADFA-5119.

Verification

tsc --noEmit clean; 79/79 dashboard tests pass (incl. 6 new net-retry). Device: a mid-job
network drop recovers (books/kiwix); cancel leaves no kiwix partial.

…hardening + reconnect

Area 1 of the ADFA-4893 download contract, on the REST job engine. A network drop
mid-job now recovers or fails cleanly instead of failing the whole batch at the first
blip.

- net-retry.ts: a shared withRetry(fn) with exponential backoff + jitter that bails on
  cancel / AbortSignal; pure and unit-tested (net-retry.test.ts).
- jobs.ts: each job carries an AbortController; cancel() aborts it and the runner gets
  ctx.signal — so cancel now interrupts an in-flight fetch, not just child processes (a
  fetch has no process to SIGKILL).
- books.exec.ts: per-attempt timeout + ctx.signal on both fetches, per-item retry with
  backoff (retries network / timeout / 5xx, not 4xx), Aborted -> CanceledError so a
  canceled job lands on 'canceled'. A dropped EPUB no longer fails the whole batch.
- kiwix.exec.ts: prune the partial .zim + .aria2 on cancel (clean-on-cancel), and add
  aria2 reconnect flags (--max-tries=5 / --retry-wait=5 / --timeout=60). The divergence
  from the app's Aria2Manager (--max-tries=1) is deliberate and documented: the app has
  an outer retry loop, the REST engine does not, so aria2 owns the reconnect here.

maps per-layer checkpoint is deferred — it needs the on-box tile-extract.py output
layout, which lives outside this repo. Layer B (the paused / pause / resume / retry
control surface) is a separate commit.

Version 1.2.3-dev.1 (stays -dev.N through the PR; final 1.2.3 at merge) + CHANGELOG.
Verified: tsc --noEmit clean, 79/79 dashboard tests pass (incl. 6 new net-retry).
…books budget, canonical aria2 flags

Addresses the two-pass review + the on-device findings on 1.2.3-dev.1.

- kiwix: an outer withRetry loop around aria2 recovers a FULL interface loss — aria2 exits
  19/DNS and can't retry a name it can't resolve — by re-running (resuming via --continue) on
  transient exit codes (1/2/6/7/19/29); terminal codes (not found / no space / exists) are not
  retried. Cancel still cleans the partial; a real error keeps it for a later --continue.
- books: retry budget widened (6 tries, 1/2/4/8/15s ≈ 30s) so a real Wi-Fi drop + reassociation
  survives, not just a sub-4s blip.
- aria2 flags: the 4832 sync note is now a canonical checklist (the three mirror sites + the two
  documented divergences from Aria2Manager), so a silent drift is visible in review.

Version 1.2.3-dev.2. tsc --noEmit clean; 79/79 dashboard tests pass.
@luisguzman-adfa
luisguzman-adfa merged commit 2fe444a into main Aug 21, 2026
4 checks passed
@luisguzman-adfa
luisguzman-adfa deleted the feat/ADFA-4894-rest-download-hardening branch August 21, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant