Skip to content

FUG-74: large-map upload — arena fix + sharded, flash-streamed uploads + HITL test - #46

Merged
fughilli merged 3 commits into
mainfrom
agent/fug-74-topology-editor-failed-upload-wi
Aug 6, 2026
Merged

fughilli merged 3 commits into
mainfrom
agent/fug-74-topology-editor-failed-upload-wi

Conversation

@issuefleet

@issuefleet issuefleet Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Uploading a real ~150-LED map failed at two ceilings, one after the other:

  1. map_too_large — the 16 KB decode arena overflowed because the growable topology lists doubled-and-leaked while growing (a bump arena can't free).
  2. Send failed: socket closed — a whole submit_map is one ~15 KB TLS record, and the C6 can't allocate that contiguous block on its fragmented heap (min free ~7.7 KB), so the wss connection dropped mid-transfer (alloc(15011) failed).

Fix (three parts)

  • Arena (already here, 407a648): ArenaVec grows its region in place when it still owns the arena tail — no copy, no leak. Peak drops ~19.5 KB → ~11.7 KB, inside the 16 KB arena.
  • Shard the upload: new UploadChunk/ChunkAck arms carry the encoded frame as ≤4 KB windows; the client (sendChunked) sends one per awaited chunk_ack, so the browser flushes one small TLS record at a time instead of re-coalescing into a big one. The device never needs more than ~4 KB for a record. Wire is opaque byte-slices → the store decoder is unchanged.
  • Stream, don't buffer: each window's bytes append to /lfs/upload.tmp; the last one decodes straight off flash via a block-streaming reader (BlockReader / lm_decode_upload_stream) and is renamed into place — so no whole frame is ever resident and persistence is free (the file is the frame). rx shrank 32 KB → 12 KB. Both transports shard; boot replay streams the map/topology off flash.

Verified on an ESP32-C6

  • The exact payload that OOMed now uploads and round-trips (map + topology), twice, no dropped socket.
  • Min free heap 7.7 KB → 42–55 KB (~5.5×) — the wss TLS handshake (~28 KB) now fits with comfortable margin, not by luck.
  • Reboot restores map + topology streamed off flash; no rename failed (LittleFS rename works).
  • New HITL on-hardware test (//pi/hitl/harness:map_upload): shards a 256-LED map + topology, asserts chunk_ack/result_ready, and round-trips via get_stored_map (led_count / segments / associations). Pure-logic window-plan + fixtures unit-tested in //pi/hitl/tests.

Tests

store BlockReader + sharded reassembly (map + topology) + parse_upload_chunk guards; ffi/player arm coverage; web proto round-trip + a wss sharding client test; HITL parity test. Firmware builds -c opt; all host + web suites green.

🤖 Generated with Claude Code

…logy

A topology upload for a ~150-LED scan failed with map_too_large. That error
is ArenaFull from the firmware's 16 KB static decode arena (player_app/ffi.rs
ARENA_BYTES) — not a transport limit (the C++ rx buffer is 32 KB and the store
streams via ChunkedReader without buffering the whole upload).

The live map+topology footprint for 150 LEDs is only ~10 KB; it overflowed
because the growable topology lists (associations, per-segment polylines,
branch points, segments) grew by doubling and LEAKED the old region each time
(a bump arena cannot free), peaking at ~19.5 KB of churn. The arena is
deliberately trimmed to 16 KB for TLS-handshake heap headroom, so enlarging it
isn't viable.

Fix: ArenaVec now extends its region IN PLACE when it still owns the arena tail
(its end sits on the bump cursor) via Arena::try_grow_tail_in_place — a cursor
bump, no copy, no leak. The big topology lists are each built at the tail, so
this removes essentially all churn: peak drops to ~11.7 KB, comfortably inside
16 KB. Growth only falls back to realloc-and-leak when a later allocation sits
after the vec. No proto / web / transport changes; arena budget preserved.

Tests:
- arena: in-place growth doesn't move the base or churn; non-tail and
  would-overflow regions correctly refuse in-place growth (fall back / ArenaFull).
- store: a realistic ~150-LED topology (150 associations, 12 segments x 20
  polyline points, 12 branch points) decodes into a 16 KB arena beside its map.
  Verified to fail with ArenaFull on the old leak path and pass with the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
FUG-74 Topology Editor: failed upload with map_too_large

When trying to upload a topology map for a larger scan (~150 leds), I get:

Upload failed: map_too_large: upload exceeds this player's storage arena

This should not happen. If necessary, the upload should be batched (append-mode proto) such that it can all be uploaded into the storage. My guess is that it's not too big to fit into memory at actual runtime, it's just that the buffer for storing it at transport time when loading into flash is not big enough (and it could be made smaller by doing batched uploading).

Review in Linear

@github-actions

github-actions Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-06 03:33 UTC

fughilli and others added 2 commits August 6, 2026 03:16
A ~150-LED submit_map is one ~15 KB TLS record, and the C6 can't allocate that
contiguous block on its fragmented heap (min free ~7.7 KB) — so the wss upload
dropped mid-transfer ("Send failed: socket closed"; alloc(15011) failed).

Shard it. New UploadChunk/ChunkAck arms carry the encoded submit_map /
submit_topology frame as <=4 KB windows; the client (sendChunked) sends one per
awaited chunk_ack, so the browser flushes one small TLS record at a time instead
of re-coalescing into a big one. The device never needs more than ~4 KB for a
record. The wire is opaque byte-slices, decoded by the unchanged store decoder.

Then stream, don't buffer: each window's bytes append to /lfs/upload.tmp; the
last one decodes straight off flash via a block-streaming reader (store
BlockReader / lm_decode_upload_stream) and is renamed into place — so no whole
frame is ever resident and persistence is free (the file IS the frame). rx
shrank 32 KB -> 12 KB, lifting min free heap from ~7.7 KB to ~42-55 KB (~5.5x).
Both transports shard; boot replay streams the map/topology off flash.

Verified on an ESP32-C6: the exact payload that OOMed now uploads and
round-trips (map + topology), with min heap ~5.5x higher and persistence intact.

Tests: store BlockReader + sharded reassembly (map + topology) +
parse_upload_chunk guards; ffi/player arm coverage; web proto round-trip + a wss
sharding client test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reserve a rig (or --device-ws a reachable board), shard a 256-LED submit_map
(~15 KB, the firmware cap and the OOM size) plus a multi-KB submit_topology into
UploadChunk windows, and assert a chunk_ack per non-final window and a
result_ready for the last. Then get_stored_map round-trips the MappingBundle and
checks the led_count / segment / association counts survive the whole path
(upload -> streamed decode -> arena -> dump) — catching a mis-reassembled or
truncated upload, not just a dropped socket. The regression guard for the wss
"socket closed" OOM.

The window-plan + fixtures (map_upload_core) are unit-tested with no hardware in
//pi/hitl/tests, pinning the chunking contract to the client + firmware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fughilli fughilli changed the title FUG-74: grow ArenaVec in place to stop map_too_large on ~150-LED topology FUG-74: large-map upload — arena fix + sharded, flash-streamed uploads + HITL test Aug 6, 2026
@fughilli
fughilli merged commit a64d988 into main Aug 6, 2026
9 checks passed
issuefleet Bot pushed a commit that referenced this pull request Aug 6, 2026
…ploads)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
issuefleet Bot pushed a commit that referenced this pull request Aug 7, 2026
…ploads)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
issuefleet Bot pushed a commit that referenced this pull request Aug 9, 2026
…ploads)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
HITL — dc5b57b3 Deployed Aug 6, 2026 by fughilli via hitl-e2e #60
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