@@ -22,32 +22,33 @@ impl RWLock {
22
22
pub unsafe fn read ( & self ) {
23
23
let r = libc:: pthread_rwlock_rdlock ( self . inner . get ( ) ) ;
24
24
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 .
33
33
//
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.
42
38
if r == libc:: EAGAIN {
43
39
panic ! ( "rwlock maximum reader count exceeded" ) ;
44
40
} 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.
45
43
if r == 0 {
44
+ // `pthread_rwlock_rdlock` succeeded when it should not have.
46
45
self . raw_unlock ( ) ;
47
46
}
48
47
panic ! ( "rwlock read lock would result in deadlock" ) ;
49
48
} 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 ) ;
51
52
self . num_readers . fetch_add ( 1 , Ordering :: Relaxed ) ;
52
53
}
53
54
}
@@ -56,6 +57,7 @@ impl RWLock {
56
57
let r = libc:: pthread_rwlock_tryrdlock ( self . inner . get ( ) ) ;
57
58
if r == 0 {
58
59
if * self . write_locked . get ( ) {
60
+ // `pthread_rwlock_tryrdlock` succeeded when it should not have.
59
61
self . raw_unlock ( ) ;
60
62
false
61
63
} else {
@@ -69,17 +71,22 @@ impl RWLock {
69
71
#[ inline]
70
72
pub unsafe fn write ( & self ) {
71
73
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`) .
74
76
if r == libc:: EDEADLK
75
- || * self . write_locked . get ( )
77
+ || ( r == 0 && * self . write_locked . get ( ) )
76
78
|| self . num_readers . load ( Ordering :: Relaxed ) != 0
77
79
{
80
+ // Above, we make sure to only access `write_locked` when `r == 0` to avoid
81
+ // data races.
78
82
if r == 0 {
83
+ // `pthread_rwlock_wrlock` succeeded when it should not have.
79
84
self . raw_unlock ( ) ;
80
85
}
81
86
panic ! ( "rwlock write lock would result in deadlock" ) ;
82
87
} else {
88
+ // According to POSIX, for a properly initialized rwlock this can only
89
+ // return EDEADLK or 0. We rely on that.
83
90
debug_assert_eq ! ( r, 0 ) ;
84
91
}
85
92
* self . write_locked . get ( ) = true ;
@@ -89,6 +96,7 @@ impl RWLock {
89
96
let r = libc:: pthread_rwlock_trywrlock ( self . inner . get ( ) ) ;
90
97
if r == 0 {
91
98
if * self . write_locked . get ( ) || self . num_readers . load ( Ordering :: Relaxed ) != 0 {
99
+ // `pthread_rwlock_trywrlock` succeeded when it should not have.
92
100
self . raw_unlock ( ) ;
93
101
false
94
102
} else {
0 commit comments