1 use crate::cell::UnsafeCell;
2 use crate::sync::atomic::{AtomicUsize, Ordering};
5 inner: UnsafeCell<libc::pthread_rwlock_t>,
6 write_locked: UnsafeCell<bool>, // guarded by the `inner` RwLock
7 num_readers: AtomicUsize,
10 pub type MovableRWLock = Box<RWLock>;
12 unsafe impl Send for RWLock {}
13 unsafe impl Sync for RWLock {}
16 pub const fn new() -> RWLock {
18 inner: UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER),
19 write_locked: UnsafeCell::new(false),
20 num_readers: AtomicUsize::new(0),
24 pub unsafe fn read(&self) {
25 let r = libc::pthread_rwlock_rdlock(self.inner.get());
27 // According to POSIX, when a thread tries to acquire this read lock
28 // while it already holds the write lock
29 // (or vice versa, or tries to acquire the write lock twice),
30 // "the call shall either deadlock or return [EDEADLK]"
31 // (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html,
32 // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_rdlock.html).
33 // So, in principle, all we have to do here is check `r == 0` to be sure we properly
36 // However, (at least) glibc before version 2.25 does not conform to this spec,
37 // and can return `r == 0` even when this thread already holds the write lock.
38 // We thus check for this situation ourselves and panic when detecting that a thread
39 // got the write lock more than once, or got a read and a write lock.
40 if r == libc::EAGAIN {
41 panic!("rwlock maximum reader count exceeded");
42 } else if r == libc::EDEADLK || (r == 0 && *self.write_locked.get()) {
43 // Above, we make sure to only access `write_locked` when `r == 0` to avoid
46 // `pthread_rwlock_rdlock` succeeded when it should not have.
49 panic!("rwlock read lock would result in deadlock");
51 // POSIX does not make guarantees about all the errors that may be returned.
52 // See issue #94705 for more details.
53 assert_eq!(r, 0, "unexpected error during rwlock read lock: {:?}", r);
54 self.num_readers.fetch_add(1, Ordering::Relaxed);
58 pub unsafe fn try_read(&self) -> bool {
59 let r = libc::pthread_rwlock_tryrdlock(self.inner.get());
61 if *self.write_locked.get() {
62 // `pthread_rwlock_tryrdlock` succeeded when it should not have.
66 self.num_readers.fetch_add(1, Ordering::Relaxed);
74 pub unsafe fn write(&self) {
75 let r = libc::pthread_rwlock_wrlock(self.inner.get());
76 // See comments above for why we check for EDEADLK and write_locked. For the same reason,
77 // we also need to check that there are no readers (tracked in `num_readers`).
79 || (r == 0 && *self.write_locked.get())
80 || self.num_readers.load(Ordering::Relaxed) != 0
82 // Above, we make sure to only access `write_locked` when `r == 0` to avoid
85 // `pthread_rwlock_wrlock` succeeded when it should not have.
88 panic!("rwlock write lock would result in deadlock");
90 // According to POSIX, for a properly initialized rwlock this can only
91 // return EDEADLK or 0. We rely on that.
92 debug_assert_eq!(r, 0);
94 *self.write_locked.get() = true;
97 pub unsafe fn try_write(&self) -> bool {
98 let r = libc::pthread_rwlock_trywrlock(self.inner.get());
100 if *self.write_locked.get() || self.num_readers.load(Ordering::Relaxed) != 0 {
101 // `pthread_rwlock_trywrlock` succeeded when it should not have.
105 *self.write_locked.get() = true;
113 unsafe fn raw_unlock(&self) {
114 let r = libc::pthread_rwlock_unlock(self.inner.get());
115 debug_assert_eq!(r, 0);
118 pub unsafe fn read_unlock(&self) {
119 debug_assert!(!*self.write_locked.get());
120 self.num_readers.fetch_sub(1, Ordering::Relaxed);
124 pub unsafe fn write_unlock(&self) {
125 debug_assert_eq!(self.num_readers.load(Ordering::Relaxed), 0);
126 debug_assert!(*self.write_locked.get());
127 *self.write_locked.get() = false;
131 pub unsafe fn destroy(&self) {
132 let r = libc::pthread_rwlock_destroy(self.inner.get());
133 // On DragonFly pthread_rwlock_destroy() returns EINVAL if called on a
134 // rwlock that was just initialized with
135 // libc::PTHREAD_RWLOCK_INITIALIZER. Once it is used (locked/unlocked)
136 // or pthread_rwlock_init() is called, this behaviour no longer occurs.
137 if cfg!(target_os = "dragonfly") {
138 debug_assert!(r == 0 || r == libc::EINVAL);
140 debug_assert_eq!(r, 0);