RCU Read-Side Critical Sections and Grace Periods
Read-copy-update (RCU) is a concurrency technique in which readers access a published version of shared data without taking a lock, while writers build a replacement, publish it, and delay freeing the old version until no reader can still be using it.
The key ideas are the read-side critical section and the grace period. A read-side critical section marks the interval in which a reader may hold a reference to RCU-protected data. A grace period is an interval long enough for every reader that was already in such a section to leave it.
The problem RCU solves
Suppose many threads need to read a routing table, configuration object, or cached lookup structure, while updates are relatively rare. A conventional solution puts a mutex or reader-writer lock around the data:
- Readers acquire a lock before looking up an entry.
- Writers acquire an exclusive lock before changing the structure.
- Readers and writers wait when another operation holds the lock.
This is simple, but a busy read path can spend significant time acquiring locks, even when readers only need to inspect data. A reader-writer lock allows multiple readers, but readers can still block each other indirectly, wait behind a writer, or add contention through the lock's bookkeeping.
RCU changes the trade-off. The reader does a small amount of bookkeeping, then reads the data directly. The writer takes responsibility for making a new version and postponing destruction of the old one.
RCU removes waiting from the usual read path by moving coordination and reclamation work to writers.
What a reader does
A reader enters an RCU read-side critical section before loading the shared pointer and leaves it after its final access to the pointed-to object. In Linux kernel code, the usual primitives are rcu_read_lock() and rcu_read_unlock():
rcu_read_lock();
entry = rcu_dereference(routing_table);
route = lookup(entry, destination);
use_route(route);
rcu_read_unlock();
The section does not usually lock the table or wait for an update. Its purpose is to tell the RCU implementation: this reader might still be using an object that an updater is about to replace.
The reader must finish all accesses before leaving the section. It must not return a pointer to the caller and then use that pointer after unlocking unless some other lifetime mechanism keeps the object alive. RCU protects the object's lifetime during the section; it does not make an escaped pointer safe.
The pointer load also needs the appropriate publication or dereference operation. In Linux, rcu_dereference() supplies the compiler and processor ordering needed to observe a properly published object. In other RCU implementations, the equivalent operation may have a different name.
What a writer does
An RCU update normally follows four steps:
- Create a replacement. Copy the old data or construct a new object separately. The writer applies its changes to this private version.
- Publish the replacement. Atomically replace the shared pointer, using the RCU-aware publication operation.
- Wait for a grace period. Ensure every reader that could have loaded the old pointer has left its read-side critical section.
- Reclaim the old object. Free it or otherwise return its resources to the allocator.
In Linux kernel code, the outline can look like this:
old = rcu_dereference_protected(routing_table, lock_held);
new = copy_and_modify(old);
rcu_assign_pointer(routing_table, new);
synchronize_rcu();
free_table(old);
The exact protection check and allocation details depend on the surrounding code. The important ordering is that publication happens before waiting, and reclamation happens after the grace period. Linux code can also arrange for reclamation through an RCU callback, commonly using call_rcu(), so the updater does not wait synchronously.
Why the old object is safe
Imagine a reader loads the old table just before the writer publishes the new one. That reader continues using the old table. A reader that starts after publication loads the new table. Neither reader needs to block:
reader A: enter ─ load old ─ use old ─ exit
writer: publish new ─ wait for grace period ─ free old
reader B: enter ─ load new ─ use new ─ exit
The grace period begins after the writer publishes the new pointer. It completes only after all readers that were active before the relevant update have passed through a quiescent state, meaning a point at which they are no longer in an RCU read-side critical section. A reader that starts after the replacement cannot have loaded the old pointer, so it does not need to delay reclamation of that object.
This is the safety property RCU provides: replacement can happen immediately, but destruction is delayed until old users are gone.
What RCU does not provide
RCU is not a general replacement for locks. It does not automatically make in-place mutation safe. If a writer modifies fields inside an object while readers inspect those fields, the program still needs suitable synchronization or an immutable-data design. The common RCU pattern is to treat a published version as immutable and replace it wholesale.
RCU also does not promise that all readers see the newest version. One reader may observe the old version while another observes the new version. Applications must tolerate that short-lived inconsistency, or add a separate mechanism such as a sequence counter, version check, or lock.
A grace period is not necessarily a fixed time delay. Its duration depends on the implementation and on whether readers can be preempted, descheduled, or blocked inside their critical sections. A reader that holds an RCU read-side section for a long time can delay reclamation, even though it does not block other readers.
Recognizing RCU in practice
When an incident mentions an RCU stall, delayed callback, or use-after-free after an RCU update, it is usually pointing at one of these boundaries. A stall often means a reader failed to leave its critical section promptly. A use-after-free often means the code reclaimed the old object without waiting for a grace period, or retained a pointer beyond the section that protected it.
The mental model is simple: readers announce their lifetime briefly; writers publish complete replacements; reclamation waits until the old readers are definitely finished. That division lets read-heavy operating-system paths avoid blocking while preserving safe object lifetimes.