-
Notifications
You must be signed in to change notification settings - Fork 13.3k
std: Return Result from RWLock/Mutex methods #19661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
3f72cff
to
762318b
Compare
We don't necessarily have to add this now, but it seems like you might want some additional flexibility:
What do you think about these cases? As an aside, I still feel slightly uneasy about the division of places where we try to help with exception safety and places where we don't. For example, a function down the stack could panic while using an |
(Otherwise, the PR looks like a fine implementation of what was discussed at the workweek.) |
Thinking more on this, I may be personally leaning more towards removing poisoning altogether or moving it to an opt-in primitive. For example, I can think of a few drawbacks:
|
The more I think about it, the more removing poisoning makes sense to me. |
This is making everyone pay for the fact that some people want to use exception handling. Harming the API for everyone in order to support this niche feature doesn't make sense. |
@sfackler: Poisoning could be removed, but that's the end of restricted exception handling. It would mean catching an exception in the same task would have to be safe too. I think that's saner than pretending Rust doesn't have exception handling. |
427c2bc
to
c16ef0b
Compare
re-r? @aturon I've updated the PR description/commit message to reflect the tweaked state of affairs from our discussion. |
0070659
to
dcc862c
Compare
panicking: bool, | ||
#[allow(missing_copy_implementations)] | ||
pub struct Guard { | ||
panicking: uint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why uint
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I think I forgot to remove this after playing around with it. Whatever reason I had to put this here originally is no longer valid!
All of the current std::sync primitives have poisoning enable which means that when a task fails inside of a write-access lock then all future attempts to acquire the lock will fail. This strategy ensures that stale data whose invariants are possibly not upheld are never viewed by other tasks to help propagate unexpected panics (bugs in a program) among tasks. Currently there is no way to test whether a mutex or rwlock is poisoned. One method would be to duplicate all the methods with a sister foo_catch function, for example. This pattern is, however, against our [error guidelines][errors]. As a result, this commit exposes the fact that a task has failed internally through the return value of a `Result`. [errors]: https://github.com/rust-lang/rfcs/blob/master/text/0236-error-conventions.md#do-not-provide-both-result-and-fail-variants All methods now return a `LockResult<T>` or a `TryLockResult<T>` which communicates whether the lock was poisoned or not. In a `LockResult`, both the `Ok` and `Err` variants contains the `MutexGuard<T>` that is being returned in order to allow access to the data if poisoning is not desired. This also means that the lock is *always* held upon returning from `.lock()`. A new type, `PoisonError`, was added with one method `into_guard` which can consume the assertion that a lock is poisoned to gain access to the underlying data. This is a breaking change because the signatures of these methods have changed, often incompatible ways. One major difference is that the `wait` methods on a condition variable now consume the guard and return it in as a `LockResult` to indicate whether the lock was poisoned while waiting. Most code can be updated by calling `.unwrap()` on the return value of `.lock()`. [breaking-change]
dcc862c
to
76e5ed6
Compare
r=me after the nit. I think this actually came out really nicely! |
I'm very against this change - in <99% of cases this just replaces |
Currently we're not planning on stabilizing the Unfortunately the same logic cannot necessarily be applied to synchronization primitives because you're unable to perform an atomic "is this ready to go, ok let's go" operation. As a result, we must provide functionality as one method, and our conventions dictate that the method must return a The Consequently, this PR chooses to have mutexes return results (and later have channels return results]) due to the relatively uncommon usage of the primitives, and the richness of information that the error type can convey. If we were to ignore our convention in this respect, and still provide both variants, this also generates a very real naming problem. For example channels right now use We definitely do not want to create "needless ergonomic regressions" when stabilizing primitives, we try to always focus on enhancing functionality and having primitives adhere to existing conventions. This change is directly aligning with our error conventions, and it is clearly indicating the semantics of locks which may have been somewhat hidden before. |
I'm sympathetic to the naming issue as well as the racy nature of "is ready" checks around synchronized structures - I just don't particularly like having operations where in the majority of cases the "right" thing to do is unwrap the result. This also reduces the quality of the logged error from something about mutex's panicking to "unwrap on None", but that can be mostly mitigated by setting |
Note that special care is taken here with the messages generated by |
This commit performs a stabilization pass over the sync::{mutex, rwlock, condvar} modules, marking the following items as stable: * Mutex * Mutex::new * Mutex::lock * Mutex::try_lock * MutexGuard * RWLock * RWLock::new * RWLock::read * RWLock::try_read * RWLock::write * RWLock::try_write * RWLockReadGuard * RWLockWriteGuard * Condvar * Condvar::new * Condvar::wait * Condvar::notify_one * Condvar::notify_all * PoisonError * TryLockError * TryLockError::Poisoned * TryLockError::WouldBlock * LockResult * TryLockResult The following items remain unstable to explore future possibilities of unifying the static/non-static variants of the types: * StaticMutex * StaticMutex::new * StaticMutex::lock * StaticMutex::try_lock * StaticMutex::desroy * StaticRWLock * StaticRWLock::new * StaticRWLock::read * StaticRWLock::try_read * StaticRWLock::write * StaticRWLock::try_write * StaticRWLock::destroy The following items were removed in favor of `Guard<'static, ()>` instead. * StaticMutexGuard * StaticRWLockReadGuard * StaticRWLockWriteGuard
re-r? @aturon the last commit? It places the |
All of the current std::sync primitives have poisoning enable which means that when a task fails inside of a write-access lock then all future attempts to acquire the lock will fail. This strategy ensures that stale data whose invariants are possibly not upheld are never viewed by other tasks to help propagate unexpected panics (bugs in a program) among tasks. Currently there is no way to test whether a mutex or rwlock is poisoned. One method would be to duplicate all the methods with a sister foo_catch function, for example. This pattern is, however, against our [error guidelines][errors]. As a result, this commit exposes the fact that a task has failed internally through the return value of a `Result`. [errors]: https://github.com/rust-lang/rfcs/blob/master/text/0236-error-conventions.md#do-not-provide-both-result-and-fail-variants All methods now return a `LockResult<T>` or a `TryLockResult<T>` which communicates whether the lock was poisoned or not. In a `LockResult`, both the `Ok` and `Err` variants contains the `MutexGuard<T>` that is being returned in order to allow access to the data if poisoning is not desired. This also means that the lock is *always* held upon returning from `.lock()`. A new type, `PoisonError`, was added with one method `into_guard` which can consume the assertion that a lock is poisoned to gain access to the underlying data. This is a breaking change because the signatures of these methods have changed, often incompatible ways. One major difference is that the `wait` methods on a condition variable now consume the guard and return it in as a `LockResult` to indicate whether the lock was poisoned while waiting. Most code can be updated by calling `.unwrap()` on the return value of `.lock()`. [breaking-change]
Main thing is unwrapping RWLock return values -- see: rust-lang/rust#19661 The other stuff is moving off deprecated iterator API to avoid warnings. TODO: we may need to pull kiss3d_recording out to a separate cargo project, since it appears Cargo can only produce one lib crate per project.
internal: Upgrade to the next Salsa
All of the current std::sync primitives have poisoning enable which means that
when a task fails inside of a write-access lock then all future attempts to
acquire the lock will fail. This strategy ensures that stale data whose
invariants are possibly not upheld are never viewed by other tasks to help
propagate unexpected panics (bugs in a program) among tasks.
Currently there is no way to test whether a mutex or rwlock is poisoned. One
method would be to duplicate all the methods with a sister foo_catch function,
for example. This pattern is, however, against our error guidelines.
As a result, this commit exposes the fact that a task has failed internally
through the return value of a
Result
.All methods now return a
LockResult<T>
or aTryLockResult<T>
whichcommunicates whether the lock was poisoned or not. In a
LockResult
, both theOk
andErr
variants contains theMutexGuard<T>
that is being returned inorder to allow access to the data if poisoning is not desired. This also means
that the lock is always held upon returning from
.lock()
.A new type,
PoisonError
, was added with one methodinto_guard
which canconsume the assertion that a lock is poisoned to gain access to the underlying
data.
This is a breaking change because the signatures of these methods have changed,
often incompatible ways. One major difference is that the
wait
methods on acondition variable now consume the guard and return it in as a
LockResult
toindicate whether the lock was poisoned while waiting. Most code can be updated
by calling
.unwrap()
on the return value of.lock()
.[breaking-change]