Closed ghost closed 8 years ago
RwLock and Mutex are both wrappers for operating system primitives. Unfortunately neither Windows nor Linux seem to support upgrading a read lock to a write lock without releasing the former. If you don't release the read lock first, the thread will deadlock when you try to acquire a write lock.
(sorry about the open/close).
Sure, that's why I'm using the extra mutex. It didn't seem to me like the wrapper was that thin. If it is, then it's definitely not worth doing what I'm suggesting.
/cc @rust-lang/libs , is this even possible to implement? I share @cybergeek94 's concern.
What I'm doing right now is
Readers take the read access to the RwLock, writers take the mutex (but not the RwLock). Whenever a writer needs to "upgrade", it takes the write access to RwLock.
Guarantees:
However, this is moderately nice, because all my code needs to be careful with this.
Thanks for the report @pijul! As @cybergeek94 mentioned, right now we simply bind the OS primitives rather than build a suite of fancier synchronization utilities on top of them. This sort of functionality can always be done through crates.io!
That, plus an atomic rwlock upgrade is actually generally impossible. There must be a way to handle two readers upgrading simultaneously. I've heard that some other libraries handle this by readers acquiring the lock with an indicator they might upgrade, so at most one upgrade-able reader is allowed through at any one point in time.
Overall, however, this primitive wouldn't quite fit in libstd as-is today and would probably want to exist on crates.io first.
@ghost
What I'm doing right now is
- 1 Mutex for "read access"
- 1 RwLock.
Readers take the read access to the RwLock, writers take the mutex (but not the RwLock). Whenever a writer needs to "upgrade", it takes the write access to RwLock.
Guarantees:
- No race condition: because of the mutex, there is always at most one writer. in particular, during the "upgrade", no other writer can be started.
- No deadlock: in readers, there is a single resource. In the writer, the mutex is always taken before the write lock.
However, this is moderately nice, because all my code needs to be careful with this.
Wouldn't a reader and a writer be able to access the data in this case?
It works better than you say, but not great. The writer taking the mutex is a potential writer. A reader that might want to write. To become a writer, it takes the rwlock for write.
I am curious, how would upgrading a read to a write operate differently than first releasing the readlock then acquiring a writelock? Is there a situation where being able to upgrade allows more functionality?
I am curious, how would upgrading a read to a write operate differently than first releasing the readlock then acquiring a writelock? Is there a situation where being able to upgrade allows more functionality?
@d4mr The data might get modified between the drop call and aquiring the write lock, and we don't want that.
This is an interesting topic I did some research and implementation.
There is no "perfect" solution.
One good solution, I think was, internally you have the "real" lock and a "writeIntent" lock.
LockExclusive:
writeIntent.LockExclusive()
real.LockExclusive()
writeIntent.Unlock()
TryConvertSharedToExclusive:
if !writeIntent.TryLockExclusive()
return false
real.Unlock()
// no writer can enter because we have writeIntent
// readers can enter, but they will not mutate
real.LockExclusive()
writeIntent.Unlock()
return true
The catch is that "upgrade" can fail. If it fails, then the caller has to, like, release its read lock, and try its larger process again. Allowing for mutations in the mean time. But the solution is good, compared to other attempts, because no mutation can occur between taking the read lock and upgrading it to write.
I'm writing a library implementing transactions on an memory-mapped file. Transactions can be mutable and non-mutable, with rules slightly different from the usual Rust model:
Right now, I'm using a mutex to prevent two mutable transactions at the same time, and an RwLock for the step of mutable transactions.
Using just an RwLock would require mutable transactions to first drop their ReadGuard before acquiring a WriteGuard, which means that another mutable transaction could be started at the same time.
Being able to atomically upgrade a ReadGuard into a WriteGuard would achieve the same effect with just one RwLock instead of RwLock + Mutex.