Add a RwLock
downgrade
method
#392
Labels
ACP-accepted
API Change Proposal is accepted (seconded with no objections)
api-change-proposal
A proposal to add or alter unstable APIs in the standard libraries
T-libs-api
Proposal
This proposal advocates for adding a
downgrade
function onRwLock
, specifically a method onRwLockWriteGuard
that transforms it into aRwLockReadGuard
.Problem statement
With the current
RwLock
API, there is no way to atomically change the lock mode from an exclusive writer lock to a shared reader lock. The best you can do is take aRwLock
in write mode, release the lock, and then retake theRwLock
in read mode. This solution, however, is highly prone to race conditions, since another writer can get in between releasing the write lock and retaking the lock in read mode.Motivating examples or use cases
The high-level example for
downgrade
is solving a lock-unlock-relock pattern.Suppose you have some value protected by a
RwLock
. You want many threads observing the value, but once every thread has seen the value, you want to update it quickly. For example, you could have aRwLock<bool>
. Let's say that the inner value starts asfalse
, and we want a single thread to set the inner value totrue
while holding theRwLock
in write mode. Once that thread has set it totrue
, we want lots of other threads to observe that this value istrue
in read mode. In this isolated situation, we can just drop the write lock and allow readers to execute.Now suppose that there is another thread, let's call it an eviction thread, that will constantly attempt to write-lock the
RwLock
and set the inner value tofalse
. Ideally, we want reading threads to observe thetrue
before this eviction thread gets a hold of the write lock. However, there is no way to do this without adowngrade
method.The happy path is:
false
totrue
true
The sad path is:
false
totrue
true
tofalse
false
Note that since the current
RwLock
implementation prioritizes writers over readers, it is possible that readers are completely starved of observing atrue
value.This might seem like a very strange example, but imagine that instead of
false
andtrue
, the inner structure is a cache object pointer, where we store eitherNone
orSome
of a pointer to the cached object. We want lots of threads to observe the object in read mode when it is cached, but at the same time we want to make sure that the cache is not stale and evict old data (this example is boiled down from a buffer pool manager / cache, which is commonly used to manage database pages between storage and memory in a DBMS).The main problem is when we want to read data that is not already in the cache. We need to access the cache in write/exclusive mode to bring in the data. Only once the data is present can we relax the permissions to read in shared mode. But if there is no
downgrade
function, how do we do this? While holding the write lock, we must first unlock it and then retake the lock in read mode. It is then possible that in between releasing the write lock and retaking the read lock, an eviction thread comes along and evicts the object before anything can read it. Even the thread that initially brought it into the cache might not be able to read it, and that is definitely a problem.As a side note, there might be a nice feature that may come as a side effect of including a
downgrade
method: With the currentRwLock
futex implementation always preferring writers, there is no method onRwLock
that will always make readers wake up before writers if there are any writers waiting (by design). Adowngrade
method intentionally changes the lock mode from writer to readers, so it is reasonable to assume the caller wants readers to execute before writers. Including adowngrade
method could provide "reader-before-writer" functionality that is not available right now.Solution sketch
The
downgrade
method would need to exist onRwLockWriteGuard
, to prove that we have the lock in write mode. It will take full ownership of theRwLockWriteGuard
and return back aRwLockReadGuard
.src/sync/rwlock.rs
For the
futex
implementation ofRwLock
,downgrade
should be as simple as this algorithm:futex
call)I think something like the following should work, but I have not tested it myself:
src/sys/sync/rwlock/futex.rs
For the
queue
implementation, I think that you can just increment the reader counter on the last node without unlocking, but I may be wrong about that. If that is true though, it shouldn't be too hard to wake up other reader threads. The other implementations shouldn't be that hard either.Alternatives
I cannot think of any good alternatives within the standard library. The easy alternative outside of the standard library is to use a separate crate that has this functionality.
parking_lot
is a common example.This is reasonable, but as of now I believe people would slightly prefer to use the standard library locks than rely on 3rd-party crates if they can. It seems like the number of reasons to choose
parking_lot
over the standard library are also getting smaller, so it is not ideal to have to switch toparking_lot
for this one single feature.Links and related work
parking_lot
's version ofdowngrade
Something important to note is that this ACP is not proposing an
upgrade
method to go from read-locked to write-locked. A method like that would likely need to be discussed heavily, as it is not immediately obvious what sort of protocol is should follow (for example, should theupgrade
method wait for other writers or go first?).However, with a
downgrade
method is pretty straightforward: We already have exclusive access, and we are giving up exclusive access in order to share our access with other threads. We definitely don't want to keep the readers waiting, since the writers are waiting as well and there is already a single reader afterdowngrade
is called.The main discussion around this in that second link is on the topic of compatibility with pthreads. Since the main implementation now seems to be the
futex
implementation, I think it is reasonable to add this feature on top ofRwLock
now.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):
Second, if there's a concrete solution:
The text was updated successfully, but these errors were encountered: