Loading page…
Rust walkthroughs
Loading page…
parking_lot::RwLock::read_recursive enable reentrant read locking for nested calls?read_recursive allows a thread that already holds a read lock to acquire additional read locks without deadlocking, implementing reentrant (or recursive) read locking. Unlike standard read() which would deadlock if called while holding a read lock, read_recursive tracks lock ownership per-thread and increments a count rather than blocking. This enables patterns where nested function calls each need read access to the same RwLock—the lock count is tracked internally and released only when the final recursive call completes.
use std::sync::RwLock;
fn main() {
let lock = RwLock::new(42);
let _read1 = lock.read().unwrap();
// This would deadlock with std::sync::RwLock!
// let _read2 = lock.read().unwrap();
// The thread holds a read lock and tries to acquire another
// std RwLock doesn't support reentrant locking
// Result: thread blocks waiting for itself = deadlock
}Standard std::sync::RwLock doesn't support reentrant locking—acquiring twice from the same thread deadlocks.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(42);
let _read1 = lock.read();
// parking_lot is more efficient, but same rule:
// read() from same thread would deadlock
// However, parking_lot provides read_recursive()
let _read2 = lock.read_recursive();
// This works! The thread now has 2 read locks (counted)
// Both must be dropped to release the read lock
}parking_lot::RwLock provides read_recursive specifically for reentrant scenarios.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(vec![1, 2, 3]);
// First read lock
let _guard1 = lock.read();
println!("Acquired first read lock");
// read() would block here, waiting for itself
// But read_recursive() works:
let _guard2 = lock.read_recursive();
println!("Acquired second read lock (recursive)");
// The lock tracks: this thread has 2 read locks
// Only when both guards drop does the read lock release
// We can nest further:
{
let _guard3 = lock.read_recursive();
println!("Acquired third read lock (recursive)");
// Thread now has 3 read locks
}
// Guard3 dropped, thread now has 2 read locks
println!("Guard 2 still held");
// Guard2 dropped here
}read_recursive tracks lock count per-thread; only releases when count reaches zero.
use parking_lot::RwLock;
struct Config {
values: RwLock<Vec<String>>,
}
impl Config {
fn outer_operation(&self) -> String {
let guard = self.values.read();
// Process some data
let result = self.inner_operation(&guard);
result
}
fn inner_operation(&self, _outer_guard: &parking_lot::RwLockReadGuard<'_, Vec<String>>) -> String {
// Need read access again - outer holds a read lock
// Using read() would deadlock here!
// Solution 1: Pass the guard (what we're doing)
// But this requires threading guards through all functions
// Solution 2: Use read_recursive
let guard = self.values.read_recursive();
guard.first().cloned().unwrap_or_default()
}
}
fn main() {
let config = Config {
values: RwLock::new(vec!["hello".to_string()]),
};
// This works because inner uses read_recursive
let result = config.outer_operation();
println!("Result: {}", result);
}read_recursive enables nested functions without passing guards through every call.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(42);
// read(): Standard read lock behavior
// - Blocks if any write lock is held
// - Blocks if trying to acquire from thread that already has read lock
// - May be "fair" (prevents writer starvation)
// read_recursive(): Reentrant read lock
// - Blocks if any write lock is held
// - Does NOT block if same thread already has read lock
// - Increments lock count for this thread
// read() is safer and should be default choice
// read_recursive() is for specific nested call patterns
// Example showing the difference:
let _guard1 = lock.read(); // Standard read lock
// This would block forever:
// let _guard2 = lock.read(); // DEADLOCK
// This works:
let _guard2 = lock.read_recursive(); // OK - reentrant
// Thread now has 2 read locks
}Use read() by default; only use read_recursive() when you specifically need reentrancy.
use parking_lot::RwLock;
use std::thread;
fn main() {
let lock = RwLock::new(42);
let lock_ref = &lock;
// Thread 1 holds recursive read lock
let _guard1 = lock.read_recursive();
let _guard2 = lock.read_recursive(); // OK - same thread
// Thread 2 can still acquire read locks
thread::spawn(move || {
let guard = lock_ref.read(); // OK - different thread
println!("Thread 2 read: {}", *guard);
}).join().unwrap();
// But write locks must wait for ALL read locks
// Including recursive ones
// Thread 1 still holds 2 read locks
// A write lock would block until both are dropped
}Reentrant locks are thread-local—other threads see the total read count, not per-thread counts.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(42);
// If a write lock is held:
{
let _write_guard = lock.write();
// No one can read - even recursive
// lock.read_recursive() would block here
}
// If a thread has read locks, write locks block
{
let _read_guard = lock.read_recursive();
// This would block:
// let write_guard = lock.write(); // BLOCKS
// Even with read_recursive:
// let _read2 = lock.read_recursive(); // OK
// let write_guard = lock.write(); // BLOCKS
}
// After all read guards dropped:
let write_guard = lock.write(); // OK now
*write_guard = 100;
}Write locks block until all read locks (including recursive) are released.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(42);
// The lock maintains:
// 1. A count of total readers
// 2. Per-thread ownership for recursive tracking
// Each read_recursive increments a thread-local count
let g1 = lock.read_recursive();
// Thread-local count: 1
let g2 = lock.read_recursive();
// Thread-local count: 2
let g3 = lock.read_recursive();
// Thread-local count: 3
// Dropping guards decrements the count
drop(g3);
// Thread-local count: 2
drop(g2);
// Thread-local count: 1
// Read lock still held until final guard drops
drop(g1);
// Thread-local count: 0
// Read lock fully released
// Now writers can acquire
let write_guard = lock.write();
}Lock counting is per-thread; the lock fully releases when the count reaches zero.
use parking_lot::RwLock;
struct DeepProcessor {
data: RwLock<Vec<i32>>,
}
impl DeepProcessor {
fn level_1(&self) -> i32 {
let _guard = self.data.read_recursive();
self.level_2()
}
fn level_2(&self) -> i32 {
let _guard = self.data.read_recursive();
self.level_3()
}
fn level_3(&self) -> i32 {
let _guard = self.data.read_recursive();
self.level_4()
}
fn level_4(&self) -> i32 {
let _guard = self.data.read_recursive();
self.data.read_recursive(); // Just counting
*self.data.read_recursive() // Another read
}
// At level_4, this thread holds 6 read locks!
// All must be dropped before write lock can be acquired
}
fn main() {
let processor = DeepProcessor {
data: RwLock::new(vec![1, 2, 3]),
};
let result = processor.level_1();
println!("Result: {}", result);
// Deep nesting creates many held locks
// Be aware of this when designing recursive patterns
}Deep nesting can hold many locks; be mindful of lock count in recursive designs.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(42);
// try_read: Non-blocking read attempt
match lock.try_read() {
Some(guard) => println!("Got read lock: {}", *guard),
None => println!("Write lock held, can't read"),
}
// try_read_recursive: Non-blocking reentrant read
let guard1 = lock.read_recursive();
match lock.try_read_recursive() {
Some(guard2) => {
println!("Got recursive read lock");
drop(guard2);
}
None => println!("Can't get recursive read lock"),
}
// try_read would fail while holding read lock:
match lock.try_read() {
Some(_) => println!("This won't print - would block"),
None => println!("try_read returns None - would block"),
}
// Note: try_read_recursive succeeds because we already hold a read lock
}try_read_recursive attempts to acquire without blocking; succeeds if thread already holds read lock.
use parking_lot::RwLock;
// parking_lot RwLock has fairness options:
// By default, parking_lot uses a "fair" policy:
// - Writers get priority after current readers finish
// - New readers wait for pending writers
fn main() {
let lock = RwLock::new(42);
// With fair locking:
let _read1 = lock.read();
// If a writer is waiting:
// - New readers (even recursive) may wait
// - Prevents writer starvation
// parking_lot's behavior:
// - Readers can acquire while other readers hold
// - But if a writer is waiting, new readers might block
// - read_recursive may still succeed for same thread
// This is configurable via RwLock::new_const with options
}parking_lot prevents writer starvation by giving writers priority after current readers finish.
use parking_lot::RwLock;
use std::collections::HashMap;
struct SharedCache {
data: RwLock<HashMap<String, Vec<String>>>,
}
impl SharedCache {
fn process(&self, key: &str) -> Option<String> {
let guard = self.data.read_recursive();
// Get data from cache
let entry = guard.get(key)?;
// Process might need to read other keys
self.process_entry(entry)
}
fn process_entry(&self, entry: &[String]) -> Option<String> {
// Need to check another key? read_recursive handles it
let guard = self.data.read_recursive();
// Maybe the entry references other keys
for reference in entry {
if let Some(other_data) = guard.get(reference) {
// Process recursively - read_recursive keeps working
return self.process_entry(other_data);
}
}
entry.first().cloned()
}
fn update(&self, key: &str, value: Vec<String>) {
// Update needs write lock
let mut guard = self.data.write();
guard.insert(key.to_string(), value);
}
}
fn main() {
let cache = SharedCache {
data: RwLock::new(HashMap::new()),
};
// Initialize
cache.update("root", vec!["child1".to_string()]);
cache.update("child1", vec!["child2".to_string()]);
cache.update("child2", vec!["result".to_string()]);
// Process recursively - read_recursive enables this
let result = cache.process("root");
println!("Result: {:?}", result);
}Recursive data structures naturally use read_recursive for nested traversal.
use parking_lot::RwLock as PlRwLock;
use std::sync::RwLock as StdRwLock;
fn main() {
// std::sync::RwLock:
// - No reentrant locking support
// - read() twice from same thread = deadlock
// - Must use different pattern (Arc<RwLock>, pass guards, etc.)
let std_lock = StdRwLock::new(42);
let _g1 = std_lock.read().unwrap();
// let _g2 = std_lock.read().unwrap(); // DEADLOCK
// parking_lot::RwLock:
// - read_recursive() for reentrant locking
// - Better performance (smaller, faster)
// - No poisoning (guards don't return Result)
let pl_lock = PlRwLock::new(42);
let _g1 = pl_lock.read();
let _g2 = pl_lock.read_recursive(); // OK
// parking_lot advantages:
// 1. Reentrant read locking
// 2. Smaller memory footprint
// 3. Faster lock/unlock operations
// 4. No poisoning (simpler API)
// 5. Configurable fairness
}parking_lot::RwLock is smaller, faster, and supports reentrant locking.
use parking_lot::RwLock;
// read_recursive is NOT always the right choice:
// Problem 1: Hides architectural issues
// If you need read_recursive, maybe your code structure is wrong
// Consider passing guards instead of re-acquiring
struct BadDesign {
data: RwLock<Vec<i32>>,
}
impl BadDesign {
fn outer(&self) -> i32 {
let _guard = self.data.read_recursive();
self.inner() // Could pass guard as parameter
}
fn inner(&self) -> i32 {
let _guard = self.data.read_recursive(); // Could use passed guard
42
}
}
// Better design:
struct BetterDesign {
data: RwLock<Vec<i32>>,
}
impl BetterDesign {
fn outer(&self) -> i32 {
let guard = self.data.read();
self.inner(&guard)
}
fn inner(&self, _guard: &parking_lot::RwLockReadGuard<'_, Vec<i32>>) -> i32 {
42 // Use passed guard
}
}
// Problem 2: Long-lived recursive locks block writers
// Each recursive call extends the read lock durationConsider passing guards instead of using read_recursive when possible.
use parking_lot::RwLock;
fn main() {
let lock = RwLock::new(42);
// parking_lot also supports upgradable read locks
// These can be upgraded to write locks
let upgradable = lock.upgradable_read();
// Read while upgradable
let value = *upgradable;
if value < 100 {
// Upgrade to write lock
let mut write_guard = RwLock::upgradable_read_guard::upgrade(upgradable);
*write_guard = 100;
}
// Note: Upgradable read locks are exclusive
// Only one thread can hold an upgradable read lock
// But they don't block regular read locks
// This is another alternative to recursive patterns
// when you might need to write
}Upgradable read locks provide another pattern for read-then-maybe-write scenarios.
read_recursive enables reentrant locking:
| Aspect | Behavior | |--------|----------| | Same thread, first call | Acquires read lock | | Same thread, subsequent calls | Increments lock count | | Different thread | Sees total reader count | | Lock release | When count reaches zero | | Write lock acquisition | Blocked while any read lock held |
read() vs read_recursive():
| Method | Reentrant? | Use Case |
|--------|------------|----------|
| read() | No - deadlocks | Default choice |
| read_recursive() | Yes - increments count | Nested function calls |
When to use read_recursive:
When to avoid read_recursive:
Key insight: read_recursive solves a specific problem—same-thread reentrancy—by tracking lock ownership per-thread. It's a tool for specific patterns, not a replacement for good guard-passing design. The lock internally maintains thread-local counts, and only releases when all recursive acquisitions are dropped. This enables natural recursive patterns without restructuring code to pass guards through every function call.