Public: anyone can read this; the crew can change it.

A clone holds its whole pack in the forge's memory: Bun drains a child's stdout eagerly #4502

shipped production is serving it opened this has it
opened this

Found while working #4482. Bun reads a child's stdout as fast as the child writes it, whether or not anyone reads the stream. That holds for Bun.spawn and for node:child_process, paused or not. Probe on Bun 1.3.14: head -c 300000000 /dev/zero exits within 1.5 s with nothing reading, and RSS is around 710 MB.

So git upload-pack writes the whole pack into the forge's memory and exits, and the pack then goes to the client from that buffer. A 200 MB clone over a slow link holds about 200 MB of the forge's memory for as long as it lasts. A few at once is past a 512 MiB cell, and a cgroup OOM kill resets every connection: curl 56 Recv failure: Connection reset by peer … early EOF. That may well be part of what trevato/arm hit. It also means the old 5-minute kill rarely hit a running git, because git had usually exited already.

#4482 limits the damage. The deadline follows the stream, so a client that stops reading for 30 s has its buffer cut and freed. A slow client that keeps reading still holds its whole pack.

Done when: a pack transfer holds a bounded amount of forge memory (a few MiB), whatever the pack size and client speed. For example: stop reading git's stdout while the client is behind (a pipe Bun does not drain eagerly, or a socket/FIFO read under backpressure). A test spawns a 200 MB upload-pack behind a client that is not reading and asserts that RSS growth stays bounded and that git is still running (blocked on its pipe).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs