@@ -22,32 +22,33 @@ 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" ) ;
4948 } else {
50- assert_eq ! ( r, 0 ) ;
49+ // According to POSIX, for a properly initialized rwlock this can only
50+ // return EAGAIN or EDEADLK or 0. We rely on that.
51+ debug_assert_eq ! ( r, 0 ) ;
5152 self . num_readers . fetch_add ( 1 , Ordering :: Relaxed ) ;
5253 }
5354 }
@@ -56,6 +57,7 @@ impl RWLock {
5657 let r = libc:: pthread_rwlock_tryrdlock ( self . inner . get ( ) ) ;
5758 if r == 0 {
5859 if * self . write_locked . get ( ) {
60+ // `pthread_rwlock_tryrdlock` succeeded when it should not have.
5961 self . raw_unlock ( ) ;
6062 false
6163 } else {
@@ -69,17 +71,22 @@ impl RWLock {
6971 #[ inline]
7072 pub unsafe fn write ( & self ) {
7173 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 .
74+ // See comments above for why we check for EDEADLK and write_locked. For the same reason,
75+ // we also need to check that there are no readers (tracked in `num_readers`) .
7476 if r == libc:: EDEADLK
75- || * self . write_locked . get ( )
77+ || ( r == 0 && * self . write_locked . get ( ) )
7678 || self . num_readers . load ( Ordering :: Relaxed ) != 0
7779 {
80+ // Above, we make sure to only access `write_locked` when `r == 0` to avoid
81+ // data races.
7882 if r == 0 {
83+ // `pthread_rwlock_wrlock` succeeded when it should not have.
7984 self . raw_unlock ( ) ;
8085 }
8186 panic ! ( "rwlock write lock would result in deadlock" ) ;
8287 } else {
88+ // According to POSIX, for a properly initialized rwlock this can only
89+ // return EDEADLK or 0. We rely on that.
8390 debug_assert_eq ! ( r, 0 ) ;
8491 }
8592 * self . write_locked . get ( ) = true ;
@@ -89,6 +96,7 @@ impl RWLock {
8996 let r = libc:: pthread_rwlock_trywrlock ( self . inner . get ( ) ) ;
9097 if r == 0 {
9198 if * self . write_locked . get ( ) || self . num_readers . load ( Ordering :: Relaxed ) != 0 {
99+ // `pthread_rwlock_trywrlock` succeeded when it should not have.
92100 self . raw_unlock ( ) ;
93101 false
94102 } else {
0 commit comments