Live data from Hacker News

Inside Rust's std and parking_lot mutexes – who wins?

blog.cuongle.dev

71–80 of 134 posts

Re: Inside Rust's std and parking_lot mutexes – who wins?

#71

Earlier quoted context omitted.

Yeah, I'm looking forward to it! While we're at it, another thing that'd be nice to get rid of is `AssertUnwindSafe`, which I find even more pointless.

Speaking only for myself (though several other people have expressed the same sentiment), I wish we could get rid of unwinding. That would be a massive challenge to do while preserving capabilities people care about, such as the ability to handle panics in http request handlers without exiting. I think it would be possible, though.

That sounds really interesting, whether it is done in Rust, some Rust 2.0, or a successor or experimental language. I do not know whether it is possible, though. If one does not unwind, what should actually happen instead? How would for instance partial computations, and resources on the stack, be handled? Some partial or constrained unwinding? I have not given it a lot of thought, though. How do languages without exceptions handle it? How does C handle it? Error codes all the way? Maybe something with arenas or regions?

I do not have a good grasp on panics in Rust, but panics in Rust being able to either unwind or abort dependent on configuration, seems complex, and that design happened for historical reasons, from what I have read elsewhere.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#72

Earlier quoted context omitted.

Yeah, I'm looking forward to it! While we're at it, another thing that'd be nice to get rid of is `AssertUnwindSafe`, which I find even more pointless.

Speaking only for myself (though several other people have expressed the same sentiment), I wish we could get rid of unwinding. That would be a massive challenge to do while preserving capabilities people care about, such as the ability to handle panics in http request handlers without exiting. I think it would be possible, though.

(If unwinding goes away then, sure, mutex poisoning becomes moot.)

Re: Inside Rust's std and parking_lot mutexes – who wins?

#73

The C++ example given in the article is not correct. In C++ a mutex can wrap the object being protected.

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. { const std::lock_guard lock(mtx); account.balance -= amount; account.transaction_count++;…

> In C++, a class like std::lock_guard also provides "Automatic unlock". AFAICT, the article argues that only Rust's API provides that.

The issue isn't automatic unlocking. From the article:

> The problem? Nothing stops you from accessing account without locking the mutex first. The compiler won’t catch this bug.

i.e., a C++ compiler will happily compile code that modifies `account` without taking the lock first. Your lock_guard example suffers from this same issue.

Nothing in the C++ stdlib provides an API that makes it impossible to access `account` without first taking the lock, and while you can write C++ classes that approximate the Rust API you can't quite reach the same level of robustness without external help.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#74

Earlier quoted context omitted.

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. { const std::lock_guard lock(mtx); account.balance -= amount; account.transaction_count++;…

> In C++, a class like std::lock_guard also provides "Automatic unlock". AFAICT, the article argues that only Rust's API provides that. The issue isn't automatic unlocking. From the article: > The problem? Nothing stops you from accessing account without locking the mutex first. The compiler won’t catch this bug. i.e., a C++ compiler will happily compile code that modifies `account` without taking the lock first. You…

That is a different topic from what I wrote about.

The article wrote:

> Automatic unlock: When you lock, you receive a guard. When the guard goes out of scope, it automatically unlocks. No manual cleanup needed.

And presented Rust as being different from C++ regarding that, and the C++ example was not idiomatic, since it did not use something like std::lock_guard.

I have not addressed the rest of your comment, since it is a different topic, sorry.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#75

Earlier quoted context omitted.

> In C++, a class like std::lock_guard also provides "Automatic unlock". AFAICT, the article argues that only Rust's API provides that. The issue isn't automatic unlocking. From the article: > The problem? Nothing stops you from accessing account without locking the mutex first. The compiler won’t catch this bug. i.e., a C++ compiler will happily compile code that modifies `account` without taking the lock first. You…

That is a different topic from what I wrote about. The article wrote: > Automatic unlock: When you lock, you receive a guard. When the guard goes out of scope, it automatically unlocks. No manual cleanup needed. And presented Rust as being different from C++ regarding that, and the C++ example was not idiomatic, since it did not use something like std::lock_guard. I have not addressed the rest of your comment, since…

Fair point with respect to the separate topic. My apologies.

As for the automatic cleanup bit, perhaps the article is trying to focus purely on the mutex types themselves? Or maybe they included the "when you lock" bit to emphasize that you can't forget to unlock the mutex (i.e., no reliance on unenforced idioms). Hard to say given the brevity/nature of the section, and in the end I think it's not that much of a problem given the general topic of the blogpost.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#76

Earlier quoted context omitted.

I'm very disappointed at this. The path of least resistance ought to be the right thing to do.

In the entire history of the standard library, we have never once seen a single report of anyone attempting to recover from poison.

Is that not because there is not much to do, and therefore people use .unwrap() — because crashing is actually quite sane?

Correctness trumps ergonomics, and the default should definitely be poisoning/panicking unless handled. There could definitely be an optional poison-eating mutex, but I argue the current Mutex does the right thing.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#77

Earlier quoted context omitted.

In the entire history of the standard library, we have never once seen a single report of anyone attempting to recover from poison.

Oh, I don't think recovery from poison is why poisoning is good. The reason poisoning is good is that at the moment you've acquired a lock on a mutex, you should be able to assume that the invariants guarded by the mutex are upheld (and panic if not).

Mutex doesn't promise to uphold any more invariants than `&mut T` does. If the state can be corrupted by a panic while holding `&mut T`, I don't think there's any good reason to expect that obtaining it through `MutexGuard` should make any difference.

Panic propagation is typically handled much better at thread `join()` boundaries.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#78

Earlier quoted context omitted.

Oh, I don't think recovery from poison is why poisoning is good. The reason poisoning is good is that at the moment you've acquired a lock on a mutex, you should be able to assume that the invariants guarded by the mutex are upheld (and panic if not).

Mutex doesn't promise to uphold any more invariants than `&mut T` does. If the state can be corrupted by a panic while holding `&mut T`, I don't think there's any good reason to expect that obtaining it through `MutexGuard` should make any difference. Panic propagation is typically handled much better at thread `join()` boundaries.

A panic in single-threaded, non-parallel code will either terminate the program or be recovered cleanly, so the potential for side effects to be silently observed in a way that breaks invariants is unique to Mutex. This is the reason for mutex poisoning,

Re: Inside Rust's std and parking_lot mutexes – who wins?

#79

> Poisoning: Panic Safety in Mutexes 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 M…

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.

Re: Inside Rust's std and parking_lot mutexes – who wins?

#80

> Poisoning: Panic Safety in Mutexes 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 M…

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.

Post reply on HN