Repository navigation
cold-store-reaper: retry S3 requests on transport failures - #972
Conversation
Through the sandbox egress proxy, R2 S3 responses are intermittently truncated mid-body (IncompleteRead at a fixed offset) or dropped (RemoteDisconnected). http_request surfaces those raw, and they escaped _s3_request as tracebacks that killed backup-supabase and evict-scan at the LIST step. _s3_request now retries the whole request up to S3_TRANSPORT_ATTEMPTS (4) times on IncompleteRead, RemoteDisconnected, BadStatusLine, ConnectionResetError, and ColdStoreError (the URLError form a reset takes during the send phase), with a capped linear backoff (self.s3_backoff_seconds x attempt, injectable, zeroed in tests). Requests are re-signed per attempt. LIST/GET/DELETE are idempotent; the retry lives only in the S3 layer, http_request callers elsewhere are untouched. Exhaustion raises a ColdStoreError naming the operation and bucket instead of a raw traceback. s3_get_object's byte-count verification + retries compose on top unchanged.
FakeS3 gains list-failure injection (truncate = full Content-Length, half a body, connection closed; drop = no response at all) plus a list_attempts counter. New TestS3TransportRetry: truncated listing retries and returns all objects across pagination, dropped listing retries, permanently-truncating listing raises the named ColdStoreError after exactly 4 attempts, DELETE retries a transport failure and lands exactly once. S3TestBase zeroes the retry backoff.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Caution Review failedThe pull request is closed. Walkthrough
Merge Risk:
|
Problem
Live-diagnosed 2026-10-26: through this sandbox's egress proxy, urllib responses from R2 S3 are intermittently truncated mid-body (~1 in 3 ListObjectsV2 trials failed with
http.client.IncompleteReadat exactly 1191 of 3710 bytes; occasionallyRemoteDisconnected). The traceback escapedhttp_request'sresp.read()through_s3_request, killingbackup-supabaseandevict-scanat the LIST step; GET objects could also be hit before their byte-count retry ever saw a body.Fix (S3 layer only)
_s3_requestnow retries the whole request up to 4 total attempts on:http.client.IncompleteReadhttp.client.RemoteDisconnectedhttp.client.BadStatusLineConnectionResetErrorColdStoreError— the form a reset takes when urllib wraps a send-phase failure inURLErrorandhttp_requestconverts it (the retry's try covers only thehttp_requestcall, so no otherColdStoreErrorcan be caught)Backoff:
self.s3_backoff_seconds × attempt(default 0.5s → 0.5/1.0/1.5s), capped at 5s; the value is an injectable instance attribute and the test base zeroes it. Each attempt is re-signed (SigV4 timestamps go stale). LIST/GET/DELETE are idempotent, so blind retry is safe; no retries were added tohttp_requestor any Drive path. If all attempts fail, aColdStoreErrornames the operation and bucket (S3 GET supabase-backups failed after 4 attempts: transport error (IncompleteRead: ...)) instead of a raw traceback.s3_get_object's byte-count verification and its own retries are unchanged and compose on top.Tests
44/44 pass (1 pre-existing skip), including 4 new regression tests in
TestS3TransportRetryagainst an extended fake S3 (list-failure injection: truncate-mid-body / drop-connection, plus an attempt counter):ColdStoreErrorafter exactly 4 attemptsExisting backup-supabase and evict-scan happy paths pass unchanged.
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by CodeRabbit