leptos-rs/leptos · error

lock poisoned

Error message

lock poisoned

What it means

or_poisoned's OrPoisoned trait converts the Err(PoisonError) of a RwLock read-lock attempt into a panic via expect("lock poisoned"). This happens when another thread panicked while holding the RwLock write guard, leaving the lock poisoned; instead of propagating PoisonError, the library treats a poisoned lock as an unrecoverable internal invariant and panics.

Source

Thrown at or_poisoned/src/lib.rs:44

pub trait OrPoisoned {
    /// The inner guard type.
    type Inner;

    /// Unwraps the lock.
    ///
    /// ## Panics
    ///
    /// Will panic if the lock is poisoned.
    fn or_poisoned(self) -> Self::Inner;
}

impl<'a, T: ?Sized> OrPoisoned
    for Result<RwLockReadGuard<'a, T>, PoisonError<RwLockReadGuard<'a, T>>>
{
    type Inner = RwLockReadGuard<'a, T>;

    fn or_poisoned(self) -> Self::Inner {
        self.expect("lock poisoned")
    }
}

impl<'a, T: ?Sized> OrPoisoned
    for Result<RwLockWriteGuard<'a, T>, PoisonError<RwLockWriteGuard<'a, T>>>
{
    type Inner = RwLockWriteGuard<'a, T>;

    fn or_poisoned(self) -> Self::Inner {
        self.expect("lock poisoned")
    }
}

impl<'a, T: ?Sized> OrPoisoned for LockResult<MutexGuard<'a, T>> {
    type Inner = MutexGuard<'a, T>;

    fn or_poisoned(self) -> Self::Inner {
        self.expect("lock poisoned")

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Find and fix the original panic that poisoned the lock — the "lock poisoned" panic is secondary; the first panic's backtrace is the root cause.
  2. Catch panics in update/effect closures (std::panic::catch_unwind or Leptos error boundaries) so they never panic while holding a write lock.
  3. Minimize the code running under write locks to reduce panic windows.
  4. If a poisoned state is legitimately recoverable, avoid or_poisoned and handle PoisonError manually (into_inner to recover data).

Example fix

// before
let guard = lock.write().or_poisoned();

// after
let guard = match lock.write() {
    Ok(g) => g,
    Err(poisoned) => poisoned.into_inner(), // recover data despite poison
};
Defensive patterns

Strategy: try-catch

Validate before calling

if lock.is_poisoned() {
    // recover or restart before reading
}

Type guard

fn lock_healthy<T>(lock: &RwLock<T>) -> bool {
    !lock.is_poisoned()
}

Try / catch

let guard = match lock.read() {
    Ok(g) => g,
    Err(p) => p.into_inner(), // recover instead of panicking
};

Prevention

When it happens

Trigger: Calling .or_poisoned() on Result<RwLockReadGuard, PoisonError<...>> — i.e. any .read() on a std::sync::RwLock that was poisoned because a previous lock holder panicked while mutating guarded state.

Common situations: A panic inside a Leptos reactive effect/update closure holding a write lock on shared reactive state; subsequent reads of the same signal/graph node then hit the poisoned lock and panic with "lock poisoned".

Related errors


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/cd410f7f8ee0fe0b. Report an issue: GitHub.