Context (root-cause fix — larger, scope first)
Send splits a file client-side into separate single-PUT objects (no S3 multipart, per thunderbird/platform-infrastructure#274). Each object is one PUT with no resume: a stall at 28s on a 100MB PUT throws away the entire 100MB and restarts from zero. Over a long-haul path (the Sentry report SEND-SUITE-FRONTEND-24H is a Costa Rica user → B2 eu-central-003) this is the underlying mechanism behind retry exhaustion. Timeout/retry tuning (#1, #3) papers over this; resumable upload attacks it directly.
Where
packages/send/frontend/src/lib/upload.ts — doUpload (L110), batch loop + Promise.all (L299-317)
packages/send/frontend/src/lib/filesync.ts — sendBlob (L132)
packages/send/frontend/src/lib/helpers.ts — uploadWithTracker (L267)
Change (design + spike before implementation)
Implement chunked/resumable upload within an object so a transient stall resumes from the last successful chunk instead of restarting the whole object. Evaluate options:
- S3 multipart upload (B2 is S3-compatible) with per-part retry, or
- Application-level chunking with a resume manifest.
Produce a short design note first (storage cost, B2 multipart compatibility, encryption-boundary implications) before coding.
Do NOT
- Do not reduce
BATCH_SIZE (currently 5) at upload.ts:~304. Bench Phase B (5×100MB concurrent) passed 150/150 with zero retries — concurrency is not implicated; serial uploads are where failures appeared.
Acceptance criteria
Refs
Context (root-cause fix — larger, scope first)
Send splits a file client-side into separate single-PUT objects (no S3 multipart, per thunderbird/platform-infrastructure#274). Each object is one PUT with no resume: a stall at 28s on a 100MB PUT throws away the entire 100MB and restarts from zero. Over a long-haul path (the Sentry report SEND-SUITE-FRONTEND-24H is a Costa Rica user → B2
eu-central-003) this is the underlying mechanism behind retry exhaustion. Timeout/retry tuning (#1, #3) papers over this; resumable upload attacks it directly.Where
packages/send/frontend/src/lib/upload.ts—doUpload(L110), batch loop +Promise.all(L299-317)packages/send/frontend/src/lib/filesync.ts—sendBlob(L132)packages/send/frontend/src/lib/helpers.ts—uploadWithTracker(L267)Change (design + spike before implementation)
Implement chunked/resumable upload within an object so a transient stall resumes from the last successful chunk instead of restarting the whole object. Evaluate options:
Produce a short design note first (storage cost, B2 multipart compatibility, encryption-boundary implications) before coding.
Do NOT
BATCH_SIZE(currently 5) at upload.ts:~304. Bench Phase B (5×100MB concurrent) passed 150/150 with zero retries — concurrency is not implicated; serial uploads are where failures appeared.Acceptance criteria
eu-central-003Refs