Skip to content

Commit 40a6b8c

Browse files
committed
explain our rwlock implementation (and fix a potential data race)
1 parent 61fdd3e commit 40a6b8c

File tree

1 file changed

+24
-20
lines changed

1 file changed

+24
-20
lines changed

src/libstd/sys/unix/rwlock.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,26 @@ impl RWLock {
2222
pub unsafe fn read(&self) {
2323
let r = libc::pthread_rwlock_rdlock(self.inner.get());
2424

25-
// According to the pthread_rwlock_rdlock spec, this function **may**
26-
// fail with EDEADLK if a deadlock is detected. On the other hand
27-
// pthread mutexes will *never* return EDEADLK if they are initialized
28-
// as the "fast" kind (which ours always are). As a result, a deadlock
29-
// situation may actually return from the call to pthread_rwlock_rdlock
30-
// instead of blocking forever (as mutexes and Windows rwlocks do). Note
31-
// that not all unix implementations, however, will return EDEADLK for
32-
// their rwlocks.
25+
// According to POSIX, when a thread tries to acquire this read lock
26+
// while it already holds the write lock
27+
// (or vice versa, or tries to acquire the write lock twice),
28+
// "the call shall either deadlock or return [EDEADLK]"
29+
// (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html,
30+
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_rdlock.html).
31+
// So, in principle, all we have to do here is check `r == 0` to be sure we properly
32+
// got the lock.
3333
//
34-
// We roughly maintain the deadlocking behavior by panicking to ensure
35-
// that this lock acquisition does not succeed.
36-
//
37-
// We also check whether this lock is already write locked. This
38-
// is only possible if it was write locked by the current thread and
39-
// the implementation allows recursive locking. The POSIX standard
40-
// doesn't require recursively locking a rwlock to deadlock, but we can't
41-
// allow that because it could lead to aliasing issues.
34+
// However, (at least) glibc before version 2.25 does not conform to this spec,
35+
// and can return `r == 0` even when this thread already holds the write lock.
36+
// We thus check for this situation ourselves and panic when detecting that a thread
37+
// got the write lock more than once, or got a read and a write lock.
4238
if r == libc::EAGAIN {
4339
panic!("rwlock maximum reader count exceeded");
4440
} else if r == libc::EDEADLK || (r == 0 && *self.write_locked.get()) {
41+
// Above, we make sure to only access `write_locked` when `r == 0` to avoid
42+
// data races.
4543
if r == 0 {
44+
// `pthread_rwlock_rdlock` succeeded when it should not have.
4645
self.raw_unlock();
4746
}
4847
panic!("rwlock read lock would result in deadlock");
@@ -56,6 +55,7 @@ impl RWLock {
5655
let r = libc::pthread_rwlock_tryrdlock(self.inner.get());
5756
if r == 0 {
5857
if *self.write_locked.get() {
58+
// `pthread_rwlock_tryrdlock` succeeded when it should not have.
5959
self.raw_unlock();
6060
false
6161
} else {
@@ -69,18 +69,21 @@ impl RWLock {
6969
#[inline]
7070
pub unsafe fn write(&self) {
7171
let r = libc::pthread_rwlock_wrlock(self.inner.get());
72-
// See comments above for why we check for EDEADLK and write_locked. We
73-
// also need to check that num_readers is 0.
72+
// See comments above for why we check for EDEADLK and write_locked. For the same reason,
73+
// we also need to check that there are no readers (tracked in `num_readers`).
7474
if r == libc::EDEADLK
75-
|| *self.write_locked.get()
75+
|| (r == 0 && *self.write_locked.get())
7676
|| self.num_readers.load(Ordering::Relaxed) != 0
7777
{
78+
// Above, we make sure to only access `write_locked` when `r == 0` to avoid
79+
// data races.
7880
if r == 0 {
81+
// `pthread_rwlock_wrlock` succeeded when it should not have.
7982
self.raw_unlock();
8083
}
8184
panic!("rwlock write lock would result in deadlock");
8285
} else {
83-
debug_assert_eq!(r, 0);
86+
assert_eq!(r, 0);
8487
}
8588
*self.write_locked.get() = true;
8689
}
@@ -89,6 +92,7 @@ impl RWLock {
8992
let r = libc::pthread_rwlock_trywrlock(self.inner.get());
9093
if r == 0 {
9194
if *self.write_locked.get() || self.num_readers.load(Ordering::Relaxed) != 0 {
95+
// `pthread_rwlock_trywrlock` succeeded when it should not have.
9296
self.raw_unlock();
9397
false
9498
} else {

0 commit comments

Comments
 (0)