-
Notifications
You must be signed in to change notification settings - Fork 20
Add Rwlock
try_upgrade
method
#514
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
Comments
This would mirror the unstable |
@tgross35 Sorry, clicked submit too quickly, have updated my proposal |
Parking lot does have some upgrade support, but it's somewhat limited in that to use upgrade, the read lock guard still has some higher priority. What I mean here is that you cannot have 2 UpgradeableRead guards active at a time, but you can have 1 upgradeable guard + N non-upgradable guards active at the same time. https://docs.rs/parking_lot/latest/parking_lot/type.RwLock.html#method.upgradable_read |
Indeed this is the bottleneck that prevents their use when using them on concurrent B-Trees. Any thread that wishes to potentially upgrade a node to a write lock will need to take an upgrade lock on the node, and the higher up this node is in the tree, the more it limits concurrent access to the nodes below, as only one can exist at a time. The downside to the Also, if this was to be accepted, it should be made clear in the docs that threads shouldn't loop on |
Can we support this on all targets? Should this fail if another writer is already waiting? |
Briefly looking through the other target versions, for teeos it should always succeed in upgrading, similar to the recent
I can't find anything regarding how SOLID works, maybe someone else who knows more about this platform can help? Otherwise, like other functions in the standard library not supported on SOLID, it can panic. It should be supported in
For the sake of simplicity I think the answer should be no. Therefore, priority will be implicitly given to upgraded reads over waiting writers. |
We discussed this in today's @rust-lang/libs-api meeting. Approved, ship it. Please make sure the documentation is very clear that this is opportunistic and code must not loop on it (because that will cause deadlocks). |
I also suggest adding a |
Closing my tracking issue and PR since I don't know what I'm doing. Not sure how to deal with the Someone else should implement this @.@ |
This should still be possible. I think we can leave the tracking issue and just add the relevant help wanted labels so somebody else can pick it up, I'll reopen it. |
I can pick this up! To clarify semantics: In this situation, we also don't care if there are writers waiting (because the writers wouldn't get a chance to write anyways if the current thread still has the read lock). This works out pretty nicely since in the futex impementation (and also the queue impl by default), readers will wait if there are any waiting writers, which means a
|
I'll put this here for visibility: rust-lang/rust#138559 (comment) This seems to be a blocker for this feature, which means either the proposed API needs to change or the feature shouldn't be implemented. CC @devmitch since you opened this ACP, not sure what the protocol is here with the @rust-lang/libs-api team. |
@connortsui20 Happy for you to take over this. Unsure on the correct API solution here, if it requires a different type, then so long as this type behaves in the same way as a typical read guard then I think its acceptable. If there are no other options then I suppose a new proposal can be created. |
It's more that I think a new API and guard is essentially required here to keep soundness, which means I think it has to go back through the libs team? The syntax would also have to be figured out, which is why I would be hesitant to even add this now. Do we need a method on Basically I think some more discussion (or even a new ACP) might be necessary before moving forwards. |
Ir is worth noting that we do have a second chance to design lock API since the version without poison exists unstably. We probably don’t want to deviate too much from the poison versions, but it is something to think about. |
Here is a slightly modified proposal that should deal with the variance problem raised in rust-lang/rust#138559 (comment). Basically, we can introduce a // Ignoring the poison and nonpoison changes for clarity.
impl<T: ?Sized> RwLock<T> {
pub fn read_upgradeable(&self) -> LockResult<RwLockUpgradeableReadGuard<'_, T>> {}
}
/// Essentially identical to `RwLockReadGuard`, the only difference would be making this invariant over `T`.
pub struct RwLockUpgradeableReadGuard<'a, T: ?Sized + 'a> {
// <-- unknown, probably similar to `RwLockReadGuard` -->
}
/// This should have all of the same methods as `RwLockReadGuard`.
impl<'a, T: ?Sized> RwLockUpgradeableReadGuard<'a, T> {
pub fn try_upgrade(orig: Self) -> Result<RwLockWriteGuard<'a, T>, RwLockUpgradeableReadGuard<'a, T>> {}
} I believe having a dedicated upgradeable guard also opens the door for a potential blocking I'm not entirely sure what the exact fields of this new struct should be, but it should probably be similar. Maybe even something as simple as this: pub struct RwLockUpgradeableReadGuard<'a, T: ?Sized + 'a> {
guard: RwLockReadGuard,
_variance: PhantomData<&'a mut T>,
} ^ This would make the methods of the guard pretty easy to implement. I would guess that we would want to follow With the B-tree example from the original post, there is already a situation where you might want multiple upgradeable guards. The case would be when two insertion operations happen at the same time to two sibling leaf pages that share a parent. The insertion algorithm would want both operations to take an upgradeable read latch on the parent (in case it needs to rebalance). This is easy to get around (if you can't get an upgradeable read latch, then fail immediately on the rebalance case), but it is still a case where you would want multiple. |
This restriction only exists because the upgrade on |
The reason I bring this up is because adding a blocking |
maybe there should be a |
I think I like this, the only worry I have it this becomes verbose and complicated to understand (even more than it already is for people new to Rust). If we're confident that the documentation for this can be easy to follow, then I think this is a great idea, and I don't see any other downsides! |
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 aRwLockWriteGuard
. A blockingupgrade
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
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:RwLock
downgrade
method #392What 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):
Second, if there's a concrete solution:
The text was updated successfully, but these errors were encountered: