# CORE → PROTO — `bufferBytes` range (answer to the freeze question) `contracts/` froze `bufferBytes` at **4 KiB – 8 MiB**; `docs/04` §4 line 70 says **64 KiB – 64 MiB, default 4 MiB**. They disagree on the floor (16×), the ceiling (8×), and the RSS budget can't hold the default. CORE owns the ring buffer, the 32-segment ceiling and `max_total_buffer_bytes`, so here is the range that is actually right and why. PROTO to land schema + `docs/04` §4 + an ADR together. ## Recommendation | Field | Value | | |---|---|---| | `bufferBytes` minimum | **65536** (64 KiB) | from `docs/04`; the frozen 4 KiB is wrong — see below | | `bufferBytes` maximum | **16777216** (16 MiB) | 8 MiB is defensible if simplicity wins; 64 MiB is not | | `bufferBytes` default | **2097152** (2 MiB) | 4 MiB busts the RSS DoD — see below | | `max_total_buffer_bytes` default | **268435456** (256 MiB), unchanged | the real ceiling; the backstop for everything | ## Why the floor is 64 KiB, not 4 KiB The buffer's first job is to coalesce libcurl write-callback deliveries into one `pwrite`. Over HTTP/2 a single write callback is routinely 16–64 KiB, and can be up to ~256 KiB. A 4 KiB ring buffer is **smaller than one callback**: every callback would have to flush mid-call (or loop), so the "one `pwrite` per fill" design degrades to a syscall per curl chunk — the exact thing the buffer exists to avoid. 64 KiB (≈4 typical chunks) is the smallest floor that still buys anything. 4 KiB is a page size that wandered into a throughput knob. ## Why the ceiling is ~16 MiB, not 64 MiB Two things the buffer buys, past coalescing: 1. **Large sequential writes.** On NVMe, write throughput as a function of write size is flat by ~1–4 MiB. From 4 MiB to 8 MiB you gain a little on syscall overhead at multi-Gbit; past 8 MiB there is **no throughput left to get** — you are only adding `fdatasync` latency and page-cache pressure (a 40 GB ISO must not evict the user's working set — `docs/04` §4). 2. **Absorbing a disk stall without stalling the socket.** This is the only reason to go above 8 MiB. Fast link + bursty storage (HDD, SMR, USB, a network mount): 1 Gbit is ~125 MB/s, so 16 MiB per segment ≈ 130 ms of write-stall cover; across 4–8 segments, ~0.5–1 s aggregate — enough to ride out a seek storm. Beyond 16 MiB the marginal cover isn't worth the cache footprint. So: **8 MiB captures all the throughput; 8–16 MiB is stall-absorption headroom for the fast-pipe/slow-disk power user; >16 MiB is waste.** ## The arithmetic you asked about: 32 × 64 MiB vs a 256 MiB cap `max_total_buffer_bytes` (256 MiB) caps the sum of every active segment's buffer across every active task. The effective per-segment buffer is ``` effective = clamp( requested, 64 KiB, floor(max_total_buffer_bytes / active_segment_count) ) ``` Reachable `bufferBytes` before the cap clamps it, by workload: | Active work | segments | cap ÷ segments | 16 MiB reachable? | |---|---|---|---| | 1 task, 1 seg (small file / non-resumable) | 1 | 256 MiB | yes | | 1 task, 8 seg (typical big download) | 8 | 32 MiB | yes, unclamped | | 1 task, 16 seg | 16 | 16 MiB | exactly at the cap | | 1 task, 32 seg | 32 | 8 MiB | **clamps to 8 MiB** | | 4 tasks × 8 seg | 32 | 8 MiB | clamps to 8 MiB | Now the same table with a **64 MiB** ceiling: it is unreachable the moment total active segments exceed **4** (256 / 64). At the default 8 segments the user asks for 64 MiB and silently gets 32 MiB; at 32 segments they get 8 MiB. A maximum that no realistic configuration can actually use is a misleading number in the Options dialog. 16 MiB is reachable for the single-task and light-multitask cases — the cases where deep buffering is the point — and degrades predictably (to 8 MiB) exactly when per-segment buffering stops mattering because each segment is only getting 1/32 of the link. 32 segments is already past the point of diminishing returns on segment count itself (`docs/04` §3: "more segments than [1 MiB each] is pure overhead and gets you rate-limited"); clamping their buffers to 8 MiB is the right behaviour, not a regression. ## The default: 4 MiB fails the RSS DoD `docs/04` §8: "≤ 60 MB RSS with 20 active downloads at default buffers." Twenty active downloads, each with at least one segment: - default **4 MiB** → 20 × 4 = **80 MiB in buffers alone**, before curl handles, TLS buffers, thread stacks, and the task table. Busts 60 MB outright. The 256 MiB global cap does not save you — 80 < 256, so nothing clamps. - default **2 MiB** → 20 × 2 = 40 MiB, leaving ~20 MiB for everything else. Fits. - A single 8-segment download at 2 MiB is 16 MiB of buffer — already ample for line rate on NVMe (see the throughput-plateau point above). So the default has to be **2 MiB** for the RSS target and the buffer default to be consistent, or `docs/04` §8 has to be renegotiated. 2 MiB is the cheaper fix and is not a throughput compromise. ## Clamp behaviour CORE will implement - Per **task start** and on any change to the active-segment count (new task, task finishing, a steal), recompute `effective` for every live segment by the formula above. - Never clamp below the 64 KiB floor. If `max_total_buffer_bytes / active_segment_count` is itself below 64 KiB (would need >4096 concurrent segments — not reachable at the 32-per-task ceiling and a sane concurrent-task limit), CORE admits fewer concurrent segments rather than shipping a sub-floor buffer. - The resulting `effective` value is what CORE reports upward for the readback field (request **B2a**). `requested` is echoed back too so the GUI can show "8 MiB (using 2 MiB)". ## Net contract delta for PROTO - `DownloadSpec.bufferBytes`, `download.update` patch: `minimum: 65536`, `maximum: 16777216`, `default: 2097152`. - `docs/04` §4 line ~70: "Default 2 MiB, range 64 KiB – 16 MiB. Silently reduced to fit `max_total_buffer_bytes` (256 MiB) across all active segments; the effective value is reported back." - `docs/04` §8: keep "≤ 60 MB RSS / 20 downloads" — it now holds at the 2 MiB default. - ADR: record the throughput-plateau + global-cap-arithmetic reasoning; note 8 MiB was considered for the ceiling and 16 MiB chosen for stall absorption.