How bad is the Potential deadlock mentioned in RwLock's document?

My point exactly

@jbe, @psionic12 , ah, now I get it!

For my speculation, why it's not done, see my P.S. above.

I guess the standard library is just a wapper for pthread_rwlock_rdlock. Not sure if POSIX is explicit on how that function must behave when the same thread calls it twice (but FreeBSD is, see below). But even if that's specified, Rust might use different implementations for other platforms.

As for FreeBSD, the behavior of that pthread function is specified in FreeBSD's corresponding manpage as follows:

A thread may hold multiple concurrent read locks. If so, pthread_rwlock_unlock() must be called once for each lock obtained.

The results of acquiring a read lock while the calling thread holds a write lock are undefined.

[…]

To prevent writer starvation, writers are favored over readers.

Linux may behave differently, and other platforms might as well.

It would be great if somebody who actually have confirmed information on that subject could speak here and explain it.
The thing is that when I hear somebody saying, "I guess", "my speculation" etc, this is OK when we wanna have a chat while having a beer, but to get actual correct information, it simply won't cut the mustard.

This information is from the official docs:

Of course that doesn't say anything about reentrancy.

P.S.: But the warning about the deadlock when read-locking twice in the same thread is also from the official docs. So I guess that's the official statement here.

P.P.S.: See the source (particularly lines 24 and 25), showing that my guess seems right for the Unix platforms, at least. (Sorry to say "seem" here, I'm not developing Rust, and I prefer to be cautious with my statements :sweat_smile:.)

P.P.P.S.: Actually in the above linked source, if you read further, you'll find some more code that indeed does extend the functionality of the OS a tiny bit. Apart from error handling, in line 42 and following, there is an additional check on *self.write_locked.get() (but only if the call to the POSIX interface was successful). This will lead to a panic if you successfully gain a read-lock on an already write-locked lock. Thus, there is a bit of extra stuff (and it's not "just a wrapper"), but the actual locking mechanism does use pthread_rwlock_rdlock.

1 Like

But that FreeBSD documentation is talking about the same thread taking a write lock after getting a read lock. The Rust documentation is showing the case of one thread taking a read lock twice, and a different thread executes and takes a write lock between the two read lock calls.

I'm a bit confused. Isn't the point of a reader-writer lock supposed to allow unlimited readers but one writer? That is, isn't it supposed to allow multiple read locks, so long as the lock is released the same number of times that it was acquired? I would think that, if this is the case, an attempt to acquire a write lock would simply block the thread trying to acquire the writer lock until the readers had released their locks. The write would then acquire the lock, readers would block, the writer would do its thing and when it was done, the writer would release the lock and the readers would acquire the lock and continue on like that.

1 Like

Honestly speak I use rwlock so many years and have the same understanding with you, this doc totally blows my mind...

1 Like

The FreeBSD documentation says:

To prevent writer starvation, writers are favored over readers.

Say Thread 1 acquired a read-lock, then if later Thread 2 tries to acquire a write-lock, Thread 2 will be blocked while nobody else should be able to acquire another read lock.

The FreeBSD documentation says further:

A thread may hold multiple concurrent read locks.

Thus, if Thread 1 tries to acquire another read-lock, this might block if writers are generally favored over readers.

Thread 1 can't acquire a second read-lock (but won't release its first read-lock until it has acquired the second. Thread 2 will wait for Thread 1 to release its first read-lock (which won't happen). A deadlock situation (as described in the Rust documentation).

In FreeBSD, if a writer waits, then a reader can't acquire a read-lock (even if there are other read-locks that could be held at the same time as a new read-lock). This is to give writers a chance at some time ("to prevent writer starvation").

To be honest, not sure if the FreeBSD implementation also favors write-locks in those cases where the read-lock acquiring thread already holds another read-lock. (I didn't look into the source.) The manual doesn't say anything about it.

If writers are favored over readers, then while a writer is blocked because of readers that are still holding a lock, no new read locks can be acquired (but trying to acquire another read-lock will also block until the write-lock has been acquired and released).

At least that's my understanding of priorizing writers over readers (and which would explain the deadlock problem as outlined in Rust's docs). Anyone correct me here, please, if I'm wrong.

You may want to check out the priority section on Wikipedia.

Write-preferring RW locks avoid the problem of writer starvation by preventing any new readers from acquiring the lock if there is a writer queued and waiting for the lock; the writer will acquire the lock as soon as all readers which were already holding the lock have completed. The downside is that write-preferring locks allows for less concurrency in the presence of writer threads, compared to read-preferring RW locks. Also the lock is less performant because each operation, taking or releasing the lock for either read or write, is more complex, internally requiring taking and releasing two mutexes instead of one. This variation is sometimes also known as "write-biased" readers–writer lock.

3 Likes

I added the example to drive home the potential deadlock that the sentence above it warns about. The sentence itself was added by @matklad in clarify RW lock's priority gotcha by matklad · Pull Request #82596 · rust-lang/rust · GitHub which contains a complete example that deadlocks under macOS but not Linux.

In summary, it is not portable to rely on that for RWLock.

1 Like

Yeah, that's what I described (or tried to describe :sweat_smile:) (and which also applies to FreeBSD then).

Using two locks is what I also did in past (before using parking_lot) and which I explained in the third post in this thread. (Maybe you don't need an RwLock though but two ordinary Mutexes are enough? Haven't thought about it further.)

Not a surprise, I guess, because macOS uses a lot of FreeBSD's code.

I still think it's good what FreeBSD does, but as the advantages of FreeBSD's/macOS's behavior aren't guaranteed either, you can neither rely on the advantages under Linux nor on the (different) advantages under FreeBSD (at least not, if you want to be portable).

Hence I guess it's a good idea to use parking_lot.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.