Skip to main content

Crate yo_alloc

Crate yo_alloc 

Source
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 f with 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::Report collected, 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 is off, report or abort.