Skip to content

builder: copy pack bytes into blocks as they arrive, not keep the network chunks - #14

Merged
azank1 merged 1 commit into
mainfrom
builder/stream-pack
Oct 13, 2026
Merged

azank1 merged 1 commit into
mainfrom
builder/stream-pack

Conversation

@azank1

@azank1 azank1 commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

After #13, every buildable repo builds, but instances keep memory they no longer use. After google/site-kit-wp and google/filament were refused as too big, a production instance logged 22 MB live and 1,171 MB resident, even after V8's last-resort collection. Resident memory is what the busy check and the platform's out-of-memory kill go by. anomalyco/opencode needed one busy retry in the rebuild.

What this changes

The receive loop kept every network chunk until a pack was whole. That's tens of thousands of 16–64 KB allocations, freed together at the end among allocations that live on, which leaves a heap glibc can't give back. Pack bytes now go from each chunk into blocks that double from 64 KB to 64 MB, joined once at the end. Each chunk is dropped as soon as it's read. The pkt-line parser keeps its place across chunks instead of joining them, so a length, side band or text line split anywhere still parses.

Measured on real responses

I recorded GitHub's responses once, then replayed them from disk through main and through this branch. The repos ran one after another in one process, as a warm instance runs them, each turn opened with the last-resort collection, on Node 22.

Repo main: peak / after this branch: peak / after
google/site-kit-wp (too big, 400 MB budget) 781 / 436 MB 705 / 364 MB
google/dawn, 90 MB of packs 1,129 / 467 MB 1,021 / 371 MB
anomalyco/opencode, 202 MB of packs 1,082 / 480 MB 994 / 372 MB
  • A second run of the branch left the same memory resident to within 2 MB, with peaks within 20 MB.
  • The records are byte for byte the same on main and here: dawn e56157187d4b3706, opencode 3b24937b68fd684e (SHA-1 of the record JSON). site-kit-wp stops at the same point in both.
  • Replay times are the same, 11–37 s on both.
  • Peak live memory is up to 25 MB higher, because a block counts in full while it fills.

#13 dropped a block rewrite because its peak measured the same. Peak was the wrong measure, and that benchmark also kept each round's result in scope, which hid the difference.

Proposed, not applied: MALLOC_MMAP_THRESHOLD_=131072

Most of what's still resident is glibc's main heap, at about 286 MB. glibc raises its mmap threshold each time it frees a mapped block, up to 32 MB. After that, blocks and 1 MB tree slabs land in the heap too. Setting the threshold pins it at 128 KB and turns that adjustment off. Same recordings:

Repo main + setting this branch + setting
google/site-kit-wp 771 / 428 MB 688 / 186 MB
google/dawn 1,106 / 431 MB 825 / 192 MB
anomalyco/opencode 1,033 / 432 MB 850 / 189 MB

On main the setting barely helps, because kept chunks are under 128 KB. With this branch, what stays resident drops by more than half, and peaks drop by 230–300 MB on dawn and opencode. It's a project environment variable that glibc reads at process start, so it needs azank's go. It's not part of this PR.

Tests

  • Four new receive tests: a response one byte at a time, a side-band error split across chunks, too big aborting the download, and ERR or no pack. They pass on main and on this branch, so they pin down behaviour that's unchanged.
  • Live golden tests with LIVE=1: all 5 pass on this branch. They download over HTTPS and check every commit against a full clone.
  • Suite: 62 pass, 6 live tests skipped. Typecheck is clean.

After deploy: read the big replay's turn after … collected: … rss X -> Y MB log line on the next big rebuild.

…work chunks

The receive loop kept every network chunk until a pack was whole: tens of thousands of
16-64 KB allocations, freed together at the end among allocations that live on, a heap
glibc cannot give back. It is part of why a production instance sat at 1,171 MB
resident with 22 MB live after google/site-kit-wp and google/filament were refused as
too big.

Pack bytes are now copied out of each chunk into blocks that double from 64 KB to
64 MB, joined once at the end, and the chunk goes as soon as it is read. The pkt-line
parser keeps its place across chunks instead of joining them, so a length, side band
or text line split anywhere still parses.

Replayed locally from the same recorded GitHub responses, one repo after another in one
process as a warm instance runs them, each turn opened with the last-resort collection
(peak / after, MB):

  google/site-kit-wp (too big)   781 / 436 on main    705 / 364 here
  google/dawn                  1,129 / 467          1,021 / 371
  anomalyco/opencode           1,082 / 480            994 / 372

The records are byte for byte the same on main and here, and the times are the same.
@vercel

vercel Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
commit-city Ready Ready Preview Oct 13, 2026 3:44pm UTC

@azank1
azank1 marked this pull request as ready for review October 13, 2026 16:54
@azank1
azank1 merged commit 1f5a669 into main Oct 13, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — 5714995c Deployed Oct 13, 2026 by vercel[bot]
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