Skip to content

HTTP: retry transient failures of cat_file and HTTPFile block reads - #2124

Open
lfoppiano wants to merge 2 commits into
fsspec:masterfrom
lfoppiano:http-retry
Open

HTTP: retry transient failures of cat_file and HTTPFile block reads#2124
lfoppiano wants to merge 2 commits into
fsspec:masterfrom
lfoppiano:http-retry

Conversation

@lfoppiano

@lfoppiano lfoppiano commented Sep 9, 2026

Copy link
Copy Markdown

HTTPFileSystem does not seems to have a mechanism for retry: every cat_file and every HTTPFile block read was one session.get plus one body read, so a single 503, a throttling 429 or a connection dropped mid-body killed the whole read.

With this PR we added a small loop around those two paths that retries 408/425/429/5xx, dropped or reset connections, timeouts and truncated bodies, honouring Retry-After and otherwise waiting retry_wait * 2**n (capped, jittered).
404, 401/403 and other 4xx are never retried, so subclasses that map them in _raise_not_found_for_status keep working. The loop wraps request and body read together on purpose: a mid-body ClientPayloadError surfaces from r.read(), which a request-level retry client never sees.

The HTTP error handling the retry can be altered using retry_statuses option to HTTPFileSystem (default: 408/425/429/500/502/503/504) and an overridable _is_retryable(exc) method, so a caller can opt extra codes in (a CDN answering 403 under load) or opt default codes out without patching the module.

Two silent truncations become (retried) errors: a 206 body shorter than the requested range while the file size says more exists, and a 416 for a range inside the file, the CloudFront behaviour reported in #1895.

New HTTPFileSystem options retries=3 and retry_wait=1.0, overridable per open(); retries=0 restores the previous behaviour. The shared test server gains header-driven fault injection (fail_status, fail_times, retry_after, truncate_body, short_body) and a reset_faults fixture.

Refs #550, #1895.

This PR was derived from one issue raised and fixed here.

HTTPFileSystem never retried anything: every cat_file and every HTTPFile
block read was one session.get plus one body read, so a single 503, a
throttling 429 or a connection dropped mid-body killed the whole read.

Add a small loop around those two paths that retries 408/425/429/5xx,
dropped or reset connections, timeouts and truncated bodies, honouring
Retry-After and otherwise waiting retry_wait * 2**n (capped, jittered).
404, 401/403 and other 4xx are never retried, so subclasses that map them
in _raise_not_found_for_status keep working. The loop wraps request and
body read together on purpose: a mid-body ClientPayloadError surfaces
from r.read(), which a request-level retry client never sees.

Two silent truncations become (retried) errors: a 206 body shorter than
the requested range while the file size says more exists, and a 416 for
a range inside the file, the CloudFront behaviour reported in fsspec#1895.

New HTTPFileSystem options retries=3 and retry_wait=1.0, overridable per
open(); retries=0 restores the previous behaviour. The shared test server
gains header-driven fault injection (fail_status, fail_times, retry_after,
truncate_body, short_body) and a reset_faults fixture.

Refs fsspec#550, fsspec#1895.
Add a ``retry_statuses`` option to HTTPFileSystem (default unchanged:
408/425/429/500/502/503/504) and an overridable ``_is_retryable(exc)``
method, so a caller can opt extra codes in (a CDN answering 403 under
load) or opt default codes out without patching the module. Dropped
connections, timeouts and truncated bodies are still always retried.
HTTPFile picks the predicate up from its filesystem.
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