Skip to content

Retry with backoff + stall timeout for HTTP range requests - #145

Merged
OBrezhniev merged 1 commit into
feature/direct_rw_optimizationfrom
feat/http-retry-stall
Aug 28, 2026
Merged

Retry with backoff + stall timeout for HTTP range requests#145
OBrezhniev merged 1 commit into
feature/direct_rw_optimizationfrom
feat/http-retry-stall

Conversation

@OBrezhniev

Copy link
Copy Markdown
Member

Problem

Fault injection against real browser proving showed the http backend dies on the first hiccup: a single mid-body connection drop or transient 503 fails the whole proof, and a stalled response (headers arrive, bytes stop) hangs it foreverfetch has no timeout of its own. On an unstable connection with a large zkey, some hiccup during the multi-minute transfer is near-certain.

Change (+102 lines in src/httpfile.js, no new API surface)

Range GETs are idempotent and If-Range already guards consistency, so transient failures are safely retryable:

  • Retry loop around the single-attempt reader: 3 retries, 300 ms exponential backoff, run inside the connection-cap slot so a failing origin is not hammered by other readers during backoff.
  • Stall watchdog: one AbortController armed before the fetch and bumped on headers and every body chunk; 10 s of silence aborts the attempt, which the retry loop turns into a bounded delay.
  • Permanent vs transient classification at existing throw sites: 4xx (except 429) and a changed validator fail fast (err.permanent); the degrade-to-full control signal passes through untouched.
  • Tunables exported as httpRetryConfig { retries, backoffMs, stallTimeoutMs } instead of per-call option plumbing.

Measured (authV3 28 MiB, fault every 4th zkey request, single groth16.prove call in Chromium)

fault before after
mid-body connection drops instant failure OK, 3.8 s, verified (survived 24 drops; +25 % wire from re-sent halves)
transient 503s instant failure OK, 3.0 s (24 faults, ~zero overhead)
stalled response hung forever OK, 11.0 s (10 s stall bound + retry)
persistentCache + drops 3 app-level attempts to converge OK on the first attempt

Deliberately out of scope

Retrying the open() probe; resuming a partial range from its break point (a retry re-requests the chunk; the block cache caps the loss when enabled); un-sticking a failed degrade-mode full-body download (Range-less origins without cache still fail on drops).

Tests

4 new cases against a fault-injecting server asserting exact attempt counts: mid-body drop recovery, transient-503 recovery vs fail-fast 404, bounded stall recovery, retry exhaustion. 85 tests pass; lint clean; bundles rebuilt.

🤖 Generated with Claude Code

https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp

Fault injection showed the http backend dies on the first hiccup: one
mid-body connection drop or transient 503 fails the whole proof, and a
stalled response hangs it forever (fetch has no timeout of its own).

Range GETs are idempotent and If-Range already guards consistency, so
transient failures are safely retryable:

- httpReadRangeInto retries up to 3 times with exponential backoff
  (300ms doubling). Retries happen inside the connection-cap slot, so a
  failing origin is not hammered by other readers meanwhile.
- A stall watchdog (AbortController, bumped on headers and on every body
  chunk, default 10s) turns a wedged connection into a bounded, retryable
  failure.
- Errors that retrying cannot fix are classified permanent at their throw
  site and rethrown immediately: 4xx (except 429) and a changed validator.
  The degrade-to-full control-flow signal passes through untouched.
- Tunables exported as httpRetryConfig {retries, backoffMs, stallTimeoutMs}
  rather than plumbed through per-call options.

Out of scope, deliberately: retrying the open() probe, resuming a partial
range from its break point, and un-sticking a failed degrade-mode full-body
download.

4 new tests against a fault-injecting server: mid-body drop recovery,
transient-503 recovery vs fail-fast 404, bounded stall recovery, retry
exhaustion. 82 tests pass; lint clean; bundles rebuilt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
@OBrezhniev
OBrezhniev merged commit 66f5695 into feature/direct_rw_optimization Aug 28, 2026
7 checks passed
@OBrezhniev
OBrezhniev deleted the feat/http-retry-stall branch August 28, 2026 14:19
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