Skip to content

Speed up and stabilize local file upload - #96

Merged
eliasbakken merged 3 commits into
mainfrom
perf/binary-chunk-upload
Aug 10, 2026
Merged

Speed up and stabilize local file upload#96
eliasbakken merged 3 commits into
mainfrom
perf/binary-chunk-upload

Conversation

@eliasbakken

Copy link
Copy Markdown
Contributor

Summary

  • Chunks are posted as raw binary (application/octet-stream) instead of base64-encoded inside JSON - removes ~33% wire-size inflation plus base64/JSON encode-decode overhead on both ends.
  • uploadLocalFile() now has a 20s per-chunk timeout and retries with exponential backoff (up to 20 attempts, capped at 30s) instead of no timeout and no .catch() at all. Previously a single dropped/hung request silently froze the whole upload forever - see Sometimes the upload process stops during a file upload.  #61.

Investigated but reverted: chunk pipelining

Also tried sending several chunks concurrently (offset-based WriteAt server-side) to hide this link's real RTT. Reverted after it caused two separate live incidents:

  1. A disk-write pileup (dozens of kernel threads stuck in D-state) requiring a board reboot to clear.
  2. After serializing the writes, persistent connection resets (NET_RESET).

Root cause: this board's USB hub (WiFi NIC + storage) shares a single Transaction Translator (confirmed via the CY7C65634 hub's datasheet), so it can't reliably handle simultaneous network-receive and disk-write transactions. Chunks stay serial, one at a time.

Testing

Closes #75

eliasbakken and others added 3 commits August 9, 2026 21:42
uploadChunk() was opening, writing, and closing the destination file
on every single chunk - real per-chunk overhead (open/close syscalls,
plus whatever flush happens on close), especially costly against the
FAT-formatted USB target this writes to. For a ~1.2GB image at 3MB
chunks, that's ~400 chunks each paying this cost.

uploadMagicChunk already gets this right - it opens the file handle
once (lazily, on the first chunk) and reuses it for the rest. This
brings the regular upload path in line with that: uploadStart opens
the file once and stores the handle on state.File, uploadChunk just
writes to it, and uploadFinish (uploadCancel already did) closes it.

Also fixes a pre-existing bug found along the way: the base64 decode
error in uploadChunk was captured but never actually checked.

One piece of a larger investigation into #75/#61 (slow/stalling
uploads) - this addresses the server-side per-chunk overhead
specifically; the client-side serialized, non-pipelined chunk loop
and the base64/JSON wire overhead are separate, larger changes not
included here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drives upload_start -> ~22 upload_chunk calls -> upload_finish through
the real handlers (not mocked), and checks the resulting file on disk
matches the original payload byte-for-byte. Exercises the state.File
handle kept open across chunks, so this would actually catch
truncation, overwriting, or interleaving bugs - a build-only check
can't.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
base64-encoding each chunk inflated the wire size by ~33% on an
already WiFi-constrained upload path, and cost real CPU/JSON overhead
on both ends. Chunks are now posted as raw octet-stream bodies
instead - the client posts the File.slice() Blob directly, the server
reads it straight off the request body.

Separately, uploadLocalFile()'s request chain had no .catch() and no
timeout at all, so a single dropped/hung request (this link has real
multi-hundred-ms latency spikes and occasional dead spells) silently
froze the whole upload forever with no feedback - see #61. Added a
20s per-chunk timeout and retry with exponential backoff (capped at
30s, up to 20 attempts) so a bad stretch gets ridden out instead.

Chunk pipelining/concurrency was also tried as a further speed
improvement, but reverted: this board's WiFi NIC and USB storage
share a single USB hub with a single Transaction Translator
(confirmed via the hub's own datasheet), so concurrent chunks caused
real instability (a disk-write pileup requiring a board reboot, then
persistent connection resets) rather than just being faster. Chunks
stay serial, one at a time.

Live-tested end to end: a full ~1.2GB image upload completed in 12m48s
with no errors, faster than the pre-fix baseline (~20min) and with no
hangs, on top of #91/#92/#93/#94.

Closes #75
@eliasbakken
eliasbakken merged commit 9b68924 into main Aug 10, 2026
2 checks passed
@eliasbakken
eliasbakken deleted the perf/binary-chunk-upload branch August 10, 2026 18:30
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.

File upload via Wifi is very slow

1 participant