Skip to content

Commit

Permalink
Don't allow pthread_rwlock_t to recursively lock itself
Browse files Browse the repository at this point in the history
This is allowed by POSIX and can happen on glibc with processors
that support hardware lock elision.
  • Loading branch information
Amanieu committed Jun 2, 2016
1 parent d73f5e6 commit 960d1b7
Showing 1 changed file with 52 additions and 9 deletions.
61 changes: 52 additions & 9 deletions src/libstd/sys/unix/rwlock.rs
Expand Up @@ -11,14 +11,20 @@
use libc;
use cell::UnsafeCell;

pub struct RWLock { inner: UnsafeCell<libc::pthread_rwlock_t> }
pub struct RWLock {
inner: UnsafeCell<libc::pthread_rwlock_t>,
write_locked: UnsafeCell<bool>,
}

unsafe impl Send for RWLock {}
unsafe impl Sync for RWLock {}

impl RWLock {
pub const fn new() -> RWLock {
RWLock { inner: UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER) }
RWLock {
inner: UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER),
write_locked: UnsafeCell::new(false),
}
}
#[inline]
pub unsafe fn read(&self) {
Expand All @@ -35,37 +41,74 @@ impl RWLock {
//
// We roughly maintain the deadlocking behavior by panicking to ensure
// that this lock acquisition does not succeed.
if r == libc::EDEADLK {
//
// We also check whether there this lock is already write locked. This
// is only possible if it was write locked by the current thread and
// the implementation allows recursive locking. The POSIX standard
// doesn't require recursivly locking a rwlock to deadlock, but we can't
// allow that because it could lead to aliasing issues.
if r == libc::EDEADLK || *self.write_locked.get() {
if r == 0 {
self.raw_unlock();
}
panic!("rwlock read lock would result in deadlock");
} else {
debug_assert_eq!(r, 0);
}
}
#[inline]
pub unsafe fn try_read(&self) -> bool {
libc::pthread_rwlock_tryrdlock(self.inner.get()) == 0
let r = libc::pthread_rwlock_tryrdlock(self.inner.get());
if r == 0 && *self.write_locked.get() {
self.raw_unlock();
false
} else {
r == 0
}
}
#[inline]
pub unsafe fn write(&self) {
let r = libc::pthread_rwlock_wrlock(self.inner.get());
// see comments above for why we check for EDEADLK
if r == libc::EDEADLK {
// see comments above for why we check for EDEADLK and write_locked
if r == libc::EDEADLK || *self.write_locked.get() {
if r == 0 {
self.raw_unlock();
}
panic!("rwlock write lock would result in deadlock");
} else {
debug_assert_eq!(r, 0);
}
*self.write_locked.get() = true;
}
#[inline]
pub unsafe fn try_write(&self) -> bool {
libc::pthread_rwlock_trywrlock(self.inner.get()) == 0
let r = libc::pthread_rwlock_trywrlock(self.inner.get());
if r == 0 && *self.write_locked.get() {
self.raw_unlock();
false
} else if r == 0 {
*self.write_locked.get() = true;
true
} else {
false
}
}
#[inline]
pub unsafe fn read_unlock(&self) {
unsafe fn raw_unlock(&self) {
let r = libc::pthread_rwlock_unlock(self.inner.get());
debug_assert_eq!(r, 0);
}
#[inline]
pub unsafe fn write_unlock(&self) { self.read_unlock() }
pub unsafe fn read_unlock(&self) {
debug_assert!(!*self.write_locked.get());
self.raw_unlock();
}
#[inline]
pub unsafe fn write_unlock(&self) {
debug_assert!(*self.write_locked.get());
*self.write_locked.get() = false;
self.raw_unlock();
}
#[inline]
pub unsafe fn destroy(&self) {
let r = libc::pthread_rwlock_destroy(self.inner.get());
Expand Down

0 comments on commit 960d1b7

Please sign in to comment.