pub const PER_CLASS_CAP: u32 = 16_777_216;Expand description
Spans one class may hold at once, per heap — a runaway guard, not a policy. At 64 KiB a span, this bounds one class at roughly 4 GiB per shard, which no correct program reaches by accident.
torajs-mmalloc shipped without any cap and paid for it (c2970b6d):
a legal program exhausted a class, the allocator returned None, and
the null propagated into a write — a SIGSEGV on correct code. What
actually protects a Rust program is that a null from alloc becomes
handle_alloc_error and a clean abort; the cap only makes runaway
growth arrive there sooner.
The inherited value was 64 spans — 4 MiB a class — and it was wrong by three orders of magnitude for this engine. The standard library found it on the first run that put real work through the allocator: a test holding tens of thousands of buffers of one size hit the ceiling and aborted with “memory allocation of 6152 bytes failed” while the machine had gigabytes free. A number that is right for a JavaScript runtime’s object churn is not right for a data engine, and copying it across was the mistake.
Heap::with_class_cap takes a tighter bound where one is wanted —
which is how the exhaustion path stays testable now that the default
is out of reach.
The same lesson, second verse: the raise above was silently
pinned by its own u16 — 65,535 spans × 64 KiB is a hidden 4 GiB
ceiling per class, and the first hour-long soak found it: a
3-byte-value storm filled the 16 B class and the process aborted
with “memory allocation of 3 bytes failed” on a box with 48 GiB
free. The counter is now u32 and the guard sits at 1 TiB per
class — memory governance belongs to maxmemory and the tier
budget, never to an invisible allocator constant.