Expand description
A global allocator that turns an accidental heap allocation on a shard path into a crash.
Y7 says there is no global allocator call on a command path. That is easy to
write down and impossible to keep by review once more than one person is in
the codebase, because the allocating constructs in Rust are the comfortable
ones: format!, to_vec, collect, Box::new, a Vec that grows, a
String built to make an error message. Each costs tens of nanoseconds
against a 150 ns budget, and none of them looks wrong in a diff.
So the rule is enforced instead of reviewed. A shard thread marks itself
enter_no_alloc before the command loop, and from that point any
allocation aborts the process with a message naming the size. Setup, arena
growth and anything else that legitimately needs the heap wraps itself in
allow, which is a visible, greppable, deliberate act.
§Cost when it is off
The check is one thread local load and a branch, on a path that already
calls into the system allocator. It is not measurable next to malloc.
Non shard threads never set the flag and pay the same single branch.
§Three modes, and why an abort is not the only one
An abort tells you about one violation per run, which is the wrong tool for finding out how many there are. Nothing in this project had been checked against Y7 since the rule was written down, so the first question is not “stop on the first one” but “what is the list”.
Mode::Report answers that. It suspends the check, captures a backtrace,
prints each distinct site once and counts the repeats, and lets the
allocation through. It allocates while it does this, on purpose and with the
check turned off around it, because a debugging mode that cannot use the heap
cannot tell you where you are.
Mode::Abort is the rule as written, for a build that is expected to be
clean. Mode::Off is the default, so installing the allocator does not
change what a shipped binary does until somebody asks for it.
Off costs nothing rather than costing a branch, because nothing arms the
thread: guard is where the mode is read, and when it is off the thread
flag is never set and the allocator’s check is the same false it would be on
any other thread.
§What it found the first time it was armed
yodb installs this and pump wraps its dispatch in guard, so
YO_ALLOC=report yodb serve answers the question. Driven with about seventy
commands covering every type, it reported 31 distinct sites, and they are not
one problem:
Most of them are the first touch of a key. Creating a set, hash, list or
zset allocates the body, and the slab that holds bodies of that type doubles
when it fills. That is real allocation on a command path and it is also the
only sensible place for it, so those sites want a claim written down and an
allow around them rather than a fix.
The rest are the ones worth having: a to_vec of the value in APPEND,
SETRANGE, EXPIRE and DUMP, a Vec built per call to hold the operands
of a set operation, a boxed dyn FnMut in the intersection, and a number
rendered into a fresh Vec in LMOVE and in ZADD. Every one of those is
per command and in steady state, which is exactly what Y7 is about.
So the order is: report first, sort the list into the two piles, fix the
second pile and annotate the first, and only then turn Mode::Abort on for
a build that has to stay clean.
§Using it
#[global_allocator]
static ALLOC: YoAlloc = YoAlloc::new();The engine installs this in yodb. A library consumer of yodb does not get
it, because choosing a global allocator is the application’s call and never a
library’s.
Structs§
- Guard
- The mark from
guard, undone when it goes out of scope. - YoAlloc
- The allocator. Delegates to the system allocator and checks the flag first.
Enums§
- Mode
- What happens when a marked thread allocates.
Functions§
- allow
- Run
fwith allocation permitted, then restore the previous state. - enter_
no_ alloc - Mark this thread as a shard thread: from here on, allocating aborts.
- exit_
no_ alloc - Undo one
enter_no_alloc. - first_
touch - Run
f, which is a key coming into existence for the first time. - guard
- Mark this thread for the length of the returned value, if the mode says so.
- is_
forbidden - Whether allocation is currently forbidden on this thread.
- mode
- What a marked thread does when it allocates.
- seen
- Everything
Mode::Reportcollected, as(sites, allocations). - set_
mode - Set what a marked thread does when it allocates.
- set_
mode_ from_ env - Set the mode from
YO_ALLOC, which isoff,reportorabort.