Reentrancy
A function is reentrant if it can be safely interrupted mid-execution and invoked again without corrupting its state.
Non-reentrant functions depend on mutable shared state that another invocation can clobber. Covered in ECE459 L11.
Why does it matter?
Interrupt handlers can be preempted by higher-priority interrupts. If the ISR isn’t reentrant, restarting it mid-execution corrupts state. In multi-user systems, a single copy of reentrant code can be shared across users, saving memory. Thread-safety and reentrancy overlap heavily.
Non-reentrant C example:
int tmp;
void swap(int x, int y) {
tmp = y;
y = x;
x = tmp;
}- The global
tmpis clobbered on every invocation. Movingtmpinside the function makes every invocation independent, and the code becomes thread-safe as well
Reentrant example:
void swap(int x, int y) {
int tmp = y;
y = x;
x = tmp;
}Rust nudges you here
Rust’s ownership rules make this mistake harder to write. Global mutable state is discouraged and mutation must be annotated, so side effects are visible at the call site.
Side effects and purity
- Side effects aren’t always bad:
printhas a side effect and you don’t want it reentrant (interleaved output) - A function is pure if it has no side effects and outputs depend only on inputs
- Pure functions are automatically thread-safe and reentrant, and trivially parallelizable
Reentrant procedure (OS textbook framing)
From Appendix P.3 of the OS book. A reentrant procedure has:
- A permanent part: the instructions shared across invocations
- A temporary part (activation record): pointer back to the caller, memory for local variables
The most convenient way to support reentrant procedures is the stack: each call pushes a new activation record as its stack frame, so local state is automatically per-invocation.
Where I've seen this
Shaheer asked about it as an interview question, and I ran into it as a “Reentrant callback group” in ROS 2. Also taught in CS348.