CONCEPT Cited by 1 source
Read-recursive lock¶
A read-recursive lock is a RwLock acquisition API that
lets a thread acquire a read lock even if it already holds
one, bypassing the writer-preference starvation avoidance
logic that would normally block re-entrant reads when a writer
is waiting.
Rust's parking_lot exposes this
as RwLock::read_recursive().
The primitive exists because writer preference has a cost¶
Most RwLock implementations prefer writers to prevent
starvation — if a reader holds the lock and a writer is
waiting, new readers block rather than piling on (because
otherwise an unlucky writer could wait forever behind a
continuous stream of readers).
Re-entrance breaks under this rule. Consider:
fn process(lock: &RwLock<State>) {
let guard = lock.read(); // reader 1
if guard.needs_deep_lookup() {
inner_lookup(lock); // wants another read
}
}
fn inner_lookup(lock: &RwLock<State>) {
let _ = lock.read(); // blocks if writer queued
}
If a writer is queued between the two reads, inner_lookup
blocks. The current thread is now holding a read lock and
blocked waiting for another read lock from the same thread.
Self-deadlock against the starvation-avoidance rule.
read_recursive gives inner_lookup a way out: "I already
hold a read lock on this very RwLock, let me have another
one regardless of the writer-preference rule."
Ptacek's rhetorical framing¶
"A recursive read lock is an eldritch rite invoked when you need to grab a read lock deep in a call tree where you already grabbed that lock, but can't be arsed to structure the code properly to reflect that." (Source: sources/2025-05-28-flyio-parking-lot-ffffffffffffffff)
The proper fix is normally to restructure the code so a single
read guard covers the whole operation. read_recursive is the
escape hatch for codebases where that restructuring is
expensive or where re-entrance is unavoidable (e.g. callbacks,
trait implementations that take a different path under some
conditions).
Used as a desperation probe¶
In the Fly.io 2025-05-28 parking_lot bug, Fly switched their
Catalog reads to read_recursive not because the code was
re-entrant — it wasn't — but as a probe: maybe some slow
reader was poisoning the lock in a way that writer-preference
was mis-handling. The probe didn't fix the bug (because the
bug wasn't in writer-preference logic — it was a
bitwise double-free in the
clear-bits atomic path), but it did produce a new error
message (RwLock reader count overflow) that was the first
direct evidence of lock-word corruption. See
patterns/read-recursive-as-desperation-probe.
Risks¶
- Writer starvation: aggressive use of
read_recursivereintroduces the starvation risk that writer-preference solves. Each recursive acquire bypasses the starvation- avoidance; a long read-heavy call graph can indefinitely delay waiting writers. - Code-structure debt: using
read_recursiveinstead of hoisting the read guard to cover the whole call graph hides re-entrance from the type system. - Deadlock through writes can still happen: a
re-entrant write lock (not covered by
read_recursive) still self-deadlocks normally.
Seen in¶
- sources/2025-05-28-flyio-parking-lot-ffffffffffffffff —
Canonical wiki instance of both the feature (what
parking_lot'sread_recursivedoes) and the anti-use (deploying it as a diagnostic probe rather than a fix).
Related¶
- systems/parking-lot-rust — The Rust lock library
exposing
read_recursive. - concepts/deadlock-vs-lock-contention — writer-preference policy exists specifically to prevent writer starvation under contention.
- patterns/read-recursive-as-desperation-probe — The pattern of using this primitive not as a fix but as a debugging probe.
- companies/flyio — Fly.io.