rust-lang / rust-lang/libs-team

Simpler and ergonomic propagation of poisoned Mutex and RwLock errors

Open
#874 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

api-change-proposal
Dominant language
Rust
Stars
178
Forks
28
Avg merge
15m
Merged PRs (30d)
1

Description

Proposal

Problem statement

When using Mutex or RwLock, a poisoned lock returns a PoisonError containing the guard.

For example:

RwLock::read()
    -> Result<RwLockReadGuard<'_, T>, PoisonError<RwLockReadGuard<'_, T>>>

This is useful if the caller wants to recover the guard with into_inner().

But in many cases I don't want to recover from the poisoned lock. I just want to propagate it as a normal error with ?.

For example, I would like something as simple as:

let guard = lock.read()?;

inside a function returning something like anyhow::Result.

Because the PoisonError contains the guard and its lifetime, this is not always possible directly.

Motivating examples or use cases

I ran into this while working on a Rust application using RwLock.

My local solution was an extension trait:

#[derive(Debug)]
pub enum RwLockExtError {
    ReadPoisoned(String),
    WritePoisoned(String),
}

impl std::fmt::Display for RwLockExtError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ReadPoisoned(e) => write!(f, "Failed to acquire read lock: {e}"),
            Self::WritePoisoned(e) => write!(f, "Failed to acquire write lock: {e}"),
        }
    }
}

impl std::error::Error for RwLockExtError {}

pub trait RwLockToError<T: ?Sized> {
    fn read_or_error(
        &self,
    ) -> Result<RwLockReadGuard<'_, T>, RwLockExtError>;

    fn write_or_error(
        &self,
    ) -> Result<RwLockWriteGuard<'_, T>, RwLockExtError>;
}

Then I can simply write:

let guard = lock.read_or_error()?;

This is only a proof of concept, not necessarily the API I am proposing.

Solution sketch

One possibility could be adding methods for callers that want poisoning to remain an error but don't need to recover the guard.

For example:

impl<T: ?Sized> RwLock<T> {
    pub fn read_or_error(
        &self,
    ) -> Result<RwLockReadGuard<'_, T>, PoisonError<()>>;

    pub fn write_or_error(
        &self,
    ) -> Result<RwLockWriteGuard<'_, T>, PoisonError<()>>;
}

And similarly for Mutex:

impl<T: ?Sized> Mutex<T> {
    pub fn lock_or_error(
        &self,
    ) -> Result<MutexGuard<'_, T>, PoisonError<()>>;
}

The existing read(), write() and lock() methods would remain unchanged for callers that want recovery through into_inner().

The names above are only examples.

Alternatives

I am open to any alternative model or API that would make poisoned lock errors easier to propagate into other error types while keeping the existing recovery behavior available.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing Mutex, RwLock, and PoisonError APIs described in the proposal, then compare the recovery behavior with the requested propagation use case. The work is done when a concrete API model and naming approach is agreed that simplifies propagation while preserving access to the poisoned guard for recovery.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.