Description
Proposal
This proposal suggests to add a try_upgrade
method on RwLockReadGuard that transforms it into a RwLockWriteGuard if it is immediately possible (ie if there are no read guards except for this one at this time)
Problem statement
There is currently no way to atomically change a RwLockReadGuard
into a RwLockWriteGuard
. A blocking upgrade
method is problematic, since if there are two threads blocking on the upgrade at the same time, only one of them can succeed and the other will fail and must drop their read lock. There are a few ways to handle a blocking API.
However, it is much easier and more straightfoward to implement the non-blocking try_upgrade
. If the read guard is the only one in existence (write guards cannot exist due to the existence of the read guard), we can immediately and atomically convert the read guard into a write guard. Otherwise, if there are other readers, the method will immediately fail, and the thread can continue with the read guard.
Motivating examples or use cases
Some B-Tree data structures use algorithms that optimistically acquire write locks on nodes (by upgrading read locks) if it is currently possible, and rebalance the tree, such as the Foster B-Tree. Currently this is not possible with the Rust standard library locks.
Solution sketch
// src/sync/rwlock.rs
impl<'a, T: ?Sized> RwLockReadGuard<'a, T> {
// note: this function takes ownership of the read guard
pub fn try_upgrade(orig: Self) -> Result<RwLockWriteGuard<'a, T>, RwLockReadGuard<'a, T>> {
// call non-blocking `try_upgrade` on inner sys::RwLock
// if successful, construct the resulting RwLockWriteGuard and return wrapped in Ok()
// otherwise, re-construct the RwLockReadGuard that this function was called on, and return it in Err()
}
}
// src/sys/sync/rwlock/futex.rs
impl RwLock {
pub fn try_upgrade(&self) -> bool {
// I assume we use strong CAS since this function is not in a loop (though the guard caller could be...?)
self.state.compare_exchange(READ_LOCKED, WRITE_LOCKED, Acquire, Relaxed).is_ok()
}
Alternatives
I couldn't find any crates that implement this design.
Dropping the read guard and re-acquiring a write guard can lead to threads sneaking in after the read guard drop and before the write guard acquire, changing the protected data, and possibly invalidating any conclusions made about the protected data before the read guard was dropped.
Links and related work
RwLock
downgrade was recently implemented:
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.