Author of the original WTF::ParkingLot here (what rust’s parking_lot is based on).
I’m surprised that this only compared to std on one platform (Linux).
The main benefit of parking lot is that it makes locks very small, which then encourages the use of fine grained locking. For example, in JavaScriptCore (ParkingLot’s first customer), we stuff a 2-bit lock into every object header - so if there is ever a need to do some locking for internal VM reasons on any object we can do that without increasing the size of the object
I do the same in my toy JVM (to implement the reentrant mutex+condition variable that every Java object has), except I've got a rare deadlock somewhere because, as it turns out, writing complicated low level concurrency primitives is kinda hard :p
There was a giant super-long GitHub issue about improving Rust std mutexes a few years back. Prior to that issue Rust was using something much worse, pthread_mutex_t. It explained the main reason why the standard library could not just adopt parking_lot mutexes:
> One of the problems with replacing std's lock implementations by parking_lot is that parking_lot allocates memory for its global hash table. A Rust program can define its own custom allocator, and such a custom allocator will likely use the standard library's locks, creating a cyclic dependency problem where you can't allocate memory without locking, but you can't lock without first allocating the hash table.
> After some discussion, the consensus was to providing the locks as 'thinnest possible wrapper' around the native lock APIs as long as they are still small, efficient, and const constructible. This means SRW locks on Windows, and futex-based locks on Linux, some BSDs, and Wasm.
> This means that on platforms like Linux and Windows, the operating system will be responsible for managing the waiting queues of the locks, such that any kernel improvements and features like debugging facilities in this area are directly available for Rust programs.
I dunno, it seems to me that the standard mutex performs very well on all scenarios, and doesn't have any significant downsides, except for the hogging case, which could be fixed by assigning the non-hogging threads a higher priority.
Whereas parking_lot has a ton of problematic scenarios, where after the spinlock times out, and it yields the thread to the OS, which has no idea to wake up the thread after the resource is unblocked.
It could be even argued that preventing starvation is outside the design scope of the Mutex as a construct, as it only guarantees mutual exclusion and that the highest priority waiting thread should get access to it.
This is one of the biggest design flaws in Rust's std, in my opinion.
Poisoning mutexes can have its use, but it's very rare in practice. Usually it's a huge misfeature that only introduces problems. More often than not panicking in a critical section is fine[1], but on the other hand poisoning a Mutex is a very convenient avenue for a denial-of-service attack, since a poisoned Mutex will just completely brick a given critical section.
I'm not saying such a project doesn't exist, but I don't think I've ever seen a project which does anything sensible with Mutex's `Poisoned` error besides ignoring it. It's always either an `unwrap` (and we know how well that can go [2]), or do the sensible thing and do this ridiculous song-and-dance:
let guard = match mutex {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner()
};
Suffice to say, it's a pain.
So in a lot of projects when I need a mutex I just add `parking_lot`, because its performance is stellar, and it doesn't have the poisoning insanity to deal with.
[1] -- obviously it depends on a case-by-case basis, but if you're using such a low level primitive you should know what you're doing
Questions for anyone who is an expert on poisoning in Rust:
Is it safe to ignore poisoned mutexes if and only if the relevant pieces of code are unwind-safe, similar to exception safety in C++? As in, if a panic happens, the relevant pieces of code handles the unwinding safely, thus data is not corrupted, and thus ignoring the poison is fine?
> poisoning a Mutex is a very convenient avenue for a denial-of-service attack, since a poisoned Mutex will just completely brick a given critical section.
There's a tension between making DoS hard and avoiding RCE vulnerabilities, since the way to avoid an unplanned/bad code state becoming an RCE vulnerability is to crash as quickly and thoroughly as possible when you get into that state.
I've dug into this topic in the past and my takeaway for this entire thing was “cool idea, but don't use it practice ”.
I.e. just unrwap the lock call's result. If a worker thread panics you should assume your applications done for. Some people even recommend setting panic=abort for release builds, in which case you won't even be able to catch those panics to begin with.
I mean, think about the actual use cases here. On of my threads just panicked. Does it make sense to continue running the application?
And if you answer yes, this is an error condition that can occur, then it shouldn't panick to begin with and instead handle errors gracefully, leaving the mutex unpoisoned.
I disagree, lock poisoning is a good way of improving correctness of concurrent code in case of fatal errors. As demonstrated by the benchmarks in this article, it's not very expensive for typical use cases.
In 99% of the cases where one thread has panic'd while holding a lock, you want to panic the thread that attempts to grab the lock. The contents of anything inside the lock is very much undefined and continuing will lead to unpredictable results. So most of the time you just want:
let guard = mutex.lock().expect("poisoned");
The last 1% is when you want to clean up something even if a panic has occured. This is usually in a impl Drop situation. It's not much more verbose either, just:
let guard = mutex.lock().unwrap_or_else(|poison| poison.into_inner());
What is painful is trying to propagate the poison value as an error using `?`. In that case you're probably better off using a match expression because the usual `.into()` will not play nice with common error handling crates (thiserror, anyhow) or need to implement `From` manually for the error types and drop the contents of the poison error before propagating.
This might be the case for long running server processes where you have n:m threading with long running threads and want to keep processing other requests even if one request fails. Although in that case you probably want (or your framework provides) some kind of robustness with `catch_unwind` that will log the errors, respond with HTTP 500 or whatever and then resume. Because that's needed to catch panics from non-mutex related code.
I will personally recommend that unless you are writing performance sensitive code*, don’t use mutexes at all because they are too low-level an abstraction. Use MPSC queues for example, or something like RCU. I find these abstractions much more developer friendly.
Although there are owning mutexes for C++ the C++ standard library does not provide such a thing. So the std::mutex used in the example is not an owning mutex and that example works and does what was described.
One reason not to provide the owning mutex in C++ is that it isn't able to deliver similar guarantees to Rust because its type system isn't strong enough. Rust won't let you accidentally keep a reference to the protected object after unlocking, C++ will for example.
I am not very familiar with C++'s API, but I believe that you are right that the C++ example in the article is incorrect, though for a different reason, namely that RAII is supported also in C++.
In C++, a class like std::lock_guard also provides "Automatic unlock". AFAICT, the article argues that only Rust's API provides that.
20 comments
[ 4.3 ms ] story [ 18.3 ms ] threadI’m surprised that this only compared to std on one platform (Linux).
The main benefit of parking lot is that it makes locks very small, which then encourages the use of fine grained locking. For example, in JavaScriptCore (ParkingLot’s first customer), we stuff a 2-bit lock into every object header - so if there is ever a need to do some locking for internal VM reasons on any object we can do that without increasing the size of the object
From https://github.com/rust-lang/rust/issues/93740
> One of the problems with replacing std's lock implementations by parking_lot is that parking_lot allocates memory for its global hash table. A Rust program can define its own custom allocator, and such a custom allocator will likely use the standard library's locks, creating a cyclic dependency problem where you can't allocate memory without locking, but you can't lock without first allocating the hash table.
> After some discussion, the consensus was to providing the locks as 'thinnest possible wrapper' around the native lock APIs as long as they are still small, efficient, and const constructible. This means SRW locks on Windows, and futex-based locks on Linux, some BSDs, and Wasm.
> This means that on platforms like Linux and Windows, the operating system will be responsible for managing the waiting queues of the locks, such that any kernel improvements and features like debugging facilities in this area are directly available for Rust programs.
Whereas parking_lot has a ton of problematic scenarios, where after the spinlock times out, and it yields the thread to the OS, which has no idea to wake up the thread after the resource is unblocked.
It could be even argued that preventing starvation is outside the design scope of the Mutex as a construct, as it only guarantees mutual exclusion and that the highest priority waiting thread should get access to it.
This is one of the biggest design flaws in Rust's std, in my opinion.
Poisoning mutexes can have its use, but it's very rare in practice. Usually it's a huge misfeature that only introduces problems. More often than not panicking in a critical section is fine[1], but on the other hand poisoning a Mutex is a very convenient avenue for a denial-of-service attack, since a poisoned Mutex will just completely brick a given critical section.
I'm not saying such a project doesn't exist, but I don't think I've ever seen a project which does anything sensible with Mutex's `Poisoned` error besides ignoring it. It's always either an `unwrap` (and we know how well that can go [2]), or do the sensible thing and do this ridiculous song-and-dance:
Suffice to say, it's a pain.So in a lot of projects when I need a mutex I just add `parking_lot`, because its performance is stellar, and it doesn't have the poisoning insanity to deal with.
[1] -- obviously it depends on a case-by-case basis, but if you're using such a low level primitive you should know what you're doing
[2] -- https://blog.cloudflare.com/18-november-2025-outage/#memory-...
Is it safe to ignore poisoned mutexes if and only if the relevant pieces of code are unwind-safe, similar to exception safety in C++? As in, if a panic happens, the relevant pieces of code handles the unwinding safely, thus data is not corrupted, and thus ignoring the poison is fine?
There's a tension between making DoS hard and avoiding RCE vulnerabilities, since the way to avoid an unplanned/bad code state becoming an RCE vulnerability is to crash as quickly and thoroughly as possible when you get into that state.
I mean, think about the actual use cases here. On of my threads just panicked. Does it make sense to continue running the application? And if you answer yes, this is an error condition that can occur, then it shouldn't panick to begin with and instead handle errors gracefully, leaving the mutex unpoisoned.
In 99% of the cases where one thread has panic'd while holding a lock, you want to panic the thread that attempts to grab the lock. The contents of anything inside the lock is very much undefined and continuing will lead to unpredictable results. So most of the time you just want:
The last 1% is when you want to clean up something even if a panic has occured. This is usually in a impl Drop situation. It's not much more verbose either, just: What is painful is trying to propagate the poison value as an error using `?`. In that case you're probably better off using a match expression because the usual `.into()` will not play nice with common error handling crates (thiserror, anyhow) or need to implement `From` manually for the error types and drop the contents of the poison error before propagating.This might be the case for long running server processes where you have n:m threading with long running threads and want to keep processing other requests even if one request fails. Although in that case you probably want (or your framework provides) some kind of robustness with `catch_unwind` that will log the errors, respond with HTTP 500 or whatever and then resume. Because that's needed to catch panics from non-mutex related code.
*: You may be, since you are using Rust.
Nothing too surprising.
Sourcing VS, documentation indicates Python, C/C++, GitHubCopilot, and an Extension Pack for Java in top extensions.
[1]: https://code.visualstudio.com/docs
Unfortunately, you sacrifice the priority inversion avoidance you would otherwise get with os_unfair_lock.
In C++ a mutex can wrap the object being protected.
One reason not to provide the owning mutex in C++ is that it isn't able to deliver similar guarantees to Rust because its type system isn't strong enough. Rust won't let you accidentally keep a reference to the protected object after unlocking, C++ will for example.
In C++, a class like std::lock_guard also provides "Automatic unlock". AFAICT, the article argues that only Rust's API provides that.