How does parking_lot::Condvar::notify_all differ from std::sync::Condvar in terms of spurious wakeups handling?

Both parking_lot::Condvar and std::sync::Condvar require explicit condition checks in a loop to handle spurious wakeups—neither eliminates spurious wakeups, which are permitted by the underlying OS primitives—but parking_lot::Condvar provides a more ergonomic API through its wait_until method that encapsulates the loop pattern, while std::sync::Condvar requires manual loop construction. The key difference is API ergonomics rather than fundamental behavior: parking_lot acknowledges that spurious wakeups are unavoidable and provides built-in support for the correct wait-loop pattern, whereas std::sync::Condvar leaves the loop implementation entirely to the programmer. Both implementations may wake threads spuriously due to OS-level futex semantics.

Standard Library Condvar Pattern

use std::sync::{Condvar, Mutex};
 
fn std_condvar_wait() {
    let pair = Mutex::new((false, Condvar::new()));
    let (lock, cvar) = &*pair;
    
    // Standard pattern: must use loop to handle spurious wakeups
    let mut guard = lock.lock().unwrap();
    while !guard.0 {
        guard = cvar.wait(guard).unwrap();
    }
    // Condition is now true
}

The standard library requires a manual loop because spurious wakeups can occur.

parking_lot Condvar Pattern

use parking_lot::{Condvar, Mutex};
 
fn parking_lot_wait_until() {
    let mutex = Mutex::new(false);
    let cvar = Condvar::new();
    
    // parking_lot provides wait_until that handles the loop internally
    let mut guard = mutex.lock();
    cvar.wait_until(&mut guard, |state| *state);
    // Condition is now true
}

parking_lot::Condvar::wait_until encapsulates the spurious-wakeup-handling loop.

Both Require Condition Loops

use std::sync::{Condvar as StdCondvar, Mutex as StdMutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
 
fn both_need_loops() {
    // std::sync::Condvar
    let mutex = StdMutex::new(false);
    let cvar = StdCondvar::new();
    let mut guard = mutex.lock().unwrap();
    
    // MUST use loop - spurious wakeups possible
    while !*guard {
        guard = cvar.wait(guard).unwrap();
    }
    
    // parking_lot::Condvar with wait()
    let mutex = PlMutex::new(false);
    let cvar = PlCondvar::new();
    let mut guard = mutex.lock();
    
    // MUST use loop with wait() too
    while !*guard {
        cvar.wait(&mut guard);
    }
    
    // parking_lot::Condvar with wait_until()
    let mut guard = mutex.lock();
    // wait_until handles loop internally
    cvar.wait_until(&mut guard, |state| *state);
}

parking_lot::Condvar::wait() still requires manual loops; wait_until() encapsulates it.

What Are Spurious Wakeups?

use std::sync::{Condvar, Mutex};
 
fn spurious_wakeup_explanation() {
    // A spurious wakeup occurs when:
    // - Thread is waiting on condvar
    // - Thread wakes up WITHOUT being notified
    // - This is allowed by POSIX and common on Linux
    
    // Causes include:
    // - OS-level futex implementation details
    // - Signal handling
    // - Performance optimizations in kernel
    
    // Example of incorrect code (vulnerable to spurious wakeups):
    let mutex = Mutex::new(false);
    let cvar = Condvar::new();
    let mut guard = mutex.lock().unwrap();
    
    // WRONG: if statement instead of while loop
    if !*guard {
        guard = cvar.wait(guard).unwrap();
    }
    // BUG: May proceed even though condition is false
    // if spurious wakeup occurred
    
    // CORRECT: while loop
    while !*guard {
        guard = cvar.wait(guard).unwrap();
    }
    // SAFE: Always re-checks condition after waking
}

Spurious wakeups are a fact of condvar semantics; neither library eliminates them.

parking_lot wait_until Internals

use parking_lot::{Condvar, Mutex};
 
fn wait_until_internals() {
    let mutex = Mutex::new(0);
    let cvar = Condvar::new();
    
    // wait_until is equivalent to:
    fn wait_until_manual<T>(
        cvar: &Condvar,
        guard: &mut parking_lot::MutexGuard<'_, T>,
        condition: impl FnMut(&T) -> bool,
    ) {
        while !condition(&*guard) {
            cvar.wait(guard);
        }
    }
    
    // park_lot's implementation does exactly this
    let mut guard = mutex.lock();
    cvar.wait_until(&mut guard, |value| *value > 0);
}

wait_until is syntactic sugar for the standard loop pattern.

notify_all Behavior

use std::sync::{Condvar as StdCondvar, Mutex as StdMutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
 
fn notify_all_both() {
    // Both libraries have notify_all
    // Both wake ALL waiting threads
    
    // std::sync::Condvar
    let mutex = StdMutex::new(false);
    let cvar = StdCondvar::new();
    {
        let _guard = mutex.lock().unwrap();
        cvar.notify_all();
    }
    
    // parking_lot::Condvar
    let mutex = PlMutex::new(false);
    let cvar = PlCondvar::new();
    {
        let _guard = mutex.lock();
        cvar.notify_all();
    }
    
    // Key insight: notify_all does NOT cause spurious wakeups
    // Spurious wakeups come from the OS, not from notify_all
    // Both notify_all implementations behave the same
}

notify_all itself doesn't cause spurious wakeups; the OS does.

notify_one vs notify_all

use parking_lot::{Condvar, Mutex};
 
fn notify_differences() {
    let mutex = Mutex::new(vec
![]);
    let cvar = Condvar::new();
    
    // notify_one: wakes ONE waiting thread
    cvar.notify_one();
    
    // notify_all: wakes ALL waiting threads
    cvar.notify_all();
    
    // When to use each:
    // - notify_one: When only one thread can make progress
    //   (e.g., single resource becomes available)
    
    // - notify_all: When multiple threads might make progress
    //   (e.g., state change affects many wait conditions)
    
    // Spurious wakeups affect both notify_one and notify_all
    // Both still require condition loops
}

The choice between notify_one and notify_all is about semantics, not spurious wakeups.

Timeout Handling

use std::sync::{Condvar, Mutex};
use std::time::{Duration, Instant};
 
fn timeout_std() {
    let mutex = Mutex::new(false);
    let cvar = Condvar::new();
    let mut guard = mutex.lock().unwrap();
    
    // std::sync::Condvar: wait_timeout returns result
    let result = cvar.wait_timeout(guard, Duration::from_secs(1)).unwrap();
    guard = result.0;
    let timed_out = result.1;
    
    // STILL need loop for spurious wakeups
    let start = Instant::now();
    while !*guard && start.elapsed() < Duration::from_secs(1) {
        let result = cvar.wait_timeout(guard, Duration::from_secs(1)).unwrap();
        guard = result.0;
    }
}
 
fn timeout_parking_lot() {
    use parking_lot::{Condvar, Mutex};
    
    let mutex = Mutex::new(false);
    let cvar = Condvar::new();
    let mut guard = mutex.lock();
    
    // parking_lot: wait_until with timeout
    let result = cvar.wait_until(&mut guard, |state| *state, Duration::from_secs(1));
    // Returns true if condition met, false if timeout
}

parking_lot::Condvar::wait_until with timeout is cleaner than manual timeout loops.

API Comparison

use std::sync::{Condvar as StdC, Mutex as StdM};
use parking_lot::{Condvar as PlC, Mutex as PlM};
 
fn api_comparison() {
    // std::sync::Condvar:
    // - wait(guard) -> LockResult<MutexGuard>
    // - wait_timeout(guard, dur) -> WaitTimeoutResult
    // - notify_one()
    // - notify_all()
    
    // Manual loop required:
    let mutex = StdM::new(0);
    let cvar = StdC::new();
    let mut guard = mutex.lock().unwrap();
    while *guard == 0 {
        guard = cvar.wait(guard).unwrap();
    }
    
    // parking_lot::Condvar:
    // - wait(guard)
    // - wait_until(guard, condition)  <-- Key addition
    // - wait_until(guard, condition, timeout)
    // - notify_one()
    // - notify_all()
    
    // Built-in condition handling:
    let mutex = PlM::new(0);
    let cvar = PlC::new();
    let mut guard = mutex.lock();
    cvar.wait_until(&mut guard, |v| *v > 0);
}

parking_lot adds wait_until for ergonomic condition checking.

Poisoning Behavior Differences

use std::sync::{Condvar, Mutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
 
fn poisoning_differences() {
    // std::sync::Condvar: Handles mutex poisoning
    let mutex = Mutex::new(0);
    let cvar = Condvar::new();
    
    // If mutex is poisoned, wait returns Err
    let guard = mutex.lock().unwrap();
    let result = cvar.wait(guard);
    // Result::LockResult must be handled
    
    // parking_lot::Condvar: No poisoning (mutex is "unpoisonable")
    let mutex = PlMutex::new(0);
    let cvar = PlCondvar::new();
    
    let mut guard = mutex.lock();
    cvar.wait(&mut guard);
    // No Result to handle - parking_lot mutexes don't poison
}

parking_lot eliminates poisoning, simplifying condvar code.

Condition Complexity

use parking_lot::{Condvar, Mutex};
 
fn complex_conditions() {
    struct State {
        queue: Vec<i32>,
        shutdown: bool,
        max_size: usize,
    }
    
    let mutex = Mutex::new(State {
        queue: Vec::new(),
        shutdown: false,
        max_size: 10,
    });
    let cvar = Condvar::new();
    
    // Complex condition with wait_until
    let mut guard = mutex.lock();
    cvar.wait_until(&mut guard, |state| {
        state.queue.len() > 0 || state.shutdown
    });
    
    // Multiple conditions
    cvar.wait_until(&mut guard, |state| {
        state.queue.len() >= state.max_size || state.shutdown
    });
}

wait_until handles arbitrary predicate complexity cleanly.

Spurious Wakeup Frequency

use std::sync::{Condvar, Mutex};
use std::time::Duration;
 
fn spurious_frequency() {
    // How often do spurious wakeups occur?
    // - Rarely on most systems
    // - More common under load
    // - Implementation dependent
    
    // But: "rarely" doesn't mean "never"
    // Correct code MUST handle them
    
    // This loop might work most of the time:
    let mutex = Mutex::new(false);
    let cvar = Condvar::new();
    let mut guard = mutex.lock().unwrap();
    
    // DON'T DO THIS - vulnerable to spurious wakeups
    if !*guard {
        cvar.wait(guard).unwrap();
    }
    
    // This might work 99.9% of the time
    // But that 0.1% is a hard-to-debug race condition
    
    // ALWAYS use while loop or wait_until:
    while !*guard {
        cvar.wait(guard).unwrap();
    }
}

Spurious wakeups are rare but must be handled for correctness.

Implementation Differences

// std::sync::Condvar (simplified view):
// - Uses OS futex (Linux) or CONDITION_VARIABLE (Windows)
// - wait() releases mutex and blocks atomically
// - Wakes reacquire mutex
// - Spurious wakeups from OS semantics
 
// parking_lot::Condvar (simplified view):
// - Also uses OS primitives
// - wait() behavior similar to std
// - wait_until() adds loop around wait()
// - Same spurious wakeup semantics from OS
 
fn implementation_notes() {
    // Both ultimately use OS-level primitives
    // Both are subject to OS-level spurious wakeups
    // Neither can eliminate spurious wakeups
    
    // The difference is only in API ergonomics:
    // - std: Manual loop required
    // - parking_lot: wait_until provides loop
}

The underlying semantics are identical; only the API differs.

Correct Pattern Summary

use std::sync::{Condvar, Mutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
 
fn correct_patterns() {
    // CORRECT with std::sync::Condvar:
    let mutex = Mutex::new(0);
    let cvar = Condvar::new();
    let mut guard = mutex.lock().unwrap();
    while *guard == 0 {
        guard = cvar.wait(guard).unwrap();
    }
    
    // CORRECT with parking_lot::Condvar::wait():
    let mutex = PlMutex::new(0);
    let cvar = PlCondvar::new();
    let mut guard = mutex.lock();
    while *guard == 0 {
        cvar.wait(&mut guard);
    }
    
    // CORRECT with parking_lot::Condvar::wait_until():
    let mutex = PlMutex::new(0);
    let cvar = PlCondvar::new();
    let mut guard = mutex.lock();
    cvar.wait_until(&mut guard, |v| *v > 0);
    
    // All three patterns correctly handle spurious wakeups
    // wait_until() is just more ergonomic
}

All correct patterns use a loop; wait_until encapsulates it.

Common Mistakes

use std::sync::{Condvar, Mutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
 
fn common_mistakes() {
    // MISTAKE 1: if instead of while
    let mutex = Mutex::new(false);
    let cvar = Condvar::new();
    let mut guard = mutex.lock().unwrap();
    
    // WRONG
    if !*guard {
        cvar.wait(guard).unwrap();
    }
    // CORRECT
    while !*guard {
        guard = cvar.wait(guard).unwrap();
    }
    
    // MISTAKE 2: Not re-acquiring guard before check
    // (Both libraries handle this correctly)
    
    // MISTAKE 3: Thinking wait_until eliminates spurious wakeups
    // wait_until HANDLES them with internal loop, doesn't eliminate
    
    // MISTAKE 4: Not holding lock when calling notify
    {
        let mutex = PlMutex::new(0);
        let cvar = PlCondvar::new();
        
        // WRONG (but may work)
        cvar.notify_all();
        *mutex.lock() = 1;
        
        // CORRECT
        let mut guard = mutex.lock();
        *guard = 1;
        cvar.notify_all();
    }
}

Always hold the lock when modifying shared state and notifying.

Synthesis

Spurious wakeup handling:

// Both std::sync::Condvar and parking_lot::Condvar:
// - Are subject to spurious wakeups from OS primitives
// - Require condition checks in loops
// - notify_all wakes all waiting threads
// - notify_one wakes one waiting thread
 
// Neither library eliminates spurious wakeups
// The difference is only in API ergonomics

Key differences:

// std::sync::Condvar:
// - Manual loop required: while !cond { cvar.wait(guard); }
// - Returns Result (poisoning)
// - Standard library, always available
 
// parking_lot::Condvar:
// - wait_until encapsulates loop
// - No poisoning (mutexes don't poison)
// - External dependency
// - Generally faster mutex implementation

When to use each:

// Use std::sync::Condvar when:
// - Standard library only is preferred
// - Code needs to run without dependencies
// - Poisoning is desired behavior
 
// Use parking_lot::Condvar when:
// - wait_until ergonomics are valuable
// - Using parking_lot::Mutex already
// - Poisoning is not wanted
// - Performance of parking_lot is beneficial

Key insight: parking_lot::Condvar::notify_all does not differ from std::sync::Condvar::notify_all in spurious wakeup handling—both are subject to OS-level spurious wakeups because they both use futex-like primitives underneath. The difference is that parking_lot::Condvar provides wait_until which encapsulates the standard condition-check loop pattern, making it harder to write incorrect code that forgets the loop. With std::sync::Condvar, developers must remember to write while !condition { cvar.wait(guard); }, while with parking_lot, cvar.wait_until(&mut guard, |state| condition) handles the loop internally. Both require the loop; parking_lot just makes it a single method call instead of a pattern you must implement correctly every time.