Skip to content

Commit dd20f16

Browse files
author
Jethro Beekman
committed
Don't poison the Mutex when panicking inside Condvar::wait
1 parent 4772dc8 commit dd20f16

File tree

3 files changed

+83
-34
lines changed

3 files changed

+83
-34
lines changed

src/libstd/sync/condvar.rs

+25-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use fmt;
22
use sync::atomic::{AtomicUsize, Ordering};
3-
use sync::{mutex, MutexGuard, PoisonError};
4-
use sys_common::condvar as sys;
3+
use sync::{MutexGuard, PoisonError};
4+
use sys_common::{condvar as sys, AsInner};
55
use sys_common::mutex as sys_mutex;
66
use sys_common::poison::{self, LockResult};
77
use time::{Duration, Instant};
@@ -198,16 +198,11 @@ impl Condvar {
198198
#[stable(feature = "rust1", since = "1.0.0")]
199199
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>)
200200
-> LockResult<MutexGuard<'a, T>> {
201-
let poisoned = unsafe {
202-
let lock = mutex::guard_lock(&guard);
203-
self.verify(lock);
204-
self.inner.wait(lock);
205-
mutex::guard_poison(&guard).get()
206-
};
207-
if poisoned {
208-
Err(PoisonError::new(guard))
209-
} else {
210-
Ok(guard)
201+
unsafe {
202+
let lock = MutexGuard::into_mutex(guard);
203+
self.verify(lock.as_inner());
204+
self.inner.wait(lock.as_inner());
205+
MutexGuard::new(lock)
211206
}
212207
}
213208

@@ -399,16 +394,15 @@ impl Condvar {
399394
pub fn wait_timeout<'a, T>(&self, guard: MutexGuard<'a, T>,
400395
dur: Duration)
401396
-> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)> {
402-
let (poisoned, result) = unsafe {
403-
let lock = mutex::guard_lock(&guard);
404-
self.verify(lock);
405-
let success = self.inner.wait_timeout(lock, dur);
406-
(mutex::guard_poison(&guard).get(), WaitTimeoutResult(!success))
397+
let (guard, result) = unsafe {
398+
let lock = MutexGuard::into_mutex(guard);
399+
self.verify(lock.as_inner());
400+
let success = self.inner.wait_timeout(lock.as_inner(), dur);
401+
(MutexGuard::new(lock), WaitTimeoutResult(!success))
407402
};
408-
if poisoned {
409-
Err(PoisonError::new((guard, result)))
410-
} else {
411-
Ok((guard, result))
403+
match guard {
404+
Ok(g) => Ok((g, result)),
405+
Err(p) => Err(PoisonError::new((p.into_inner(), result)))
412406
}
413407
}
414408

@@ -568,7 +562,10 @@ impl Condvar {
568562
unsafe { self.inner.notify_all() }
569563
}
570564

571-
fn verify(&self, mutex: &sys_mutex::Mutex) {
565+
/// # Safety
566+
///
567+
/// The mutex must be locked when passed to this function.
568+
unsafe fn verify(&self, mutex: &sys_mutex::Mutex) {
572569
let addr = mutex as *const _ as usize;
573570
match self.mutex.compare_and_swap(0, addr, Ordering::SeqCst) {
574571
// If we got out 0, then we have successfully bound the mutex to
@@ -581,8 +578,12 @@ impl Condvar {
581578

582579
// Anything else and we're using more than one mutex on this cvar,
583580
// which is currently disallowed.
584-
_ => panic!("attempted to use a condition variable with two \
585-
mutexes"),
581+
_ => {
582+
// unlock the mutex before panicking
583+
mutex.raw_unlock();
584+
panic!("attempted to use a condition variable with two \
585+
mutexes")
586+
}
586587
}
587588
}
588589
}

src/libstd/sync/mutex.rs

+19-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use fmt;
33
use mem;
44
use ops::{Deref, DerefMut};
55
use ptr;
6-
use sys_common::mutex as sys;
6+
use sys_common::{mutex as sys, AsInner};
77
use sys_common::poison::{self, TryLockError, TryLockResult, LockResult};
88

99
/// A mutual exclusion primitive useful for protecting shared data
@@ -360,6 +360,12 @@ impl<T: ?Sized> Mutex<T> {
360360
}
361361
}
362362

363+
impl<T: ?Sized> AsInner<sys::Mutex> for Mutex<T> {
364+
fn as_inner(&self) -> &sys::Mutex {
365+
&self.inner
366+
}
367+
}
368+
363369
#[stable(feature = "rust1", since = "1.0.0")]
364370
unsafe impl<#[may_dangle] T: ?Sized> Drop for Mutex<T> {
365371
fn drop(&mut self) {
@@ -410,14 +416,25 @@ impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
410416
}
411417

412418
impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> {
413-
unsafe fn new(lock: &'mutex Mutex<T>) -> LockResult<MutexGuard<'mutex, T>> {
419+
/// # Safety
420+
///
421+
/// The mutex must be locked when passed to this function.
422+
pub(super) unsafe fn new(lock: &'mutex Mutex<T>) -> LockResult<MutexGuard<'mutex, T>> {
414423
poison::map_result(lock.poison.borrow(), |guard| {
415424
MutexGuard {
416425
__lock: lock,
417426
__poison: guard,
418427
}
419428
})
420429
}
430+
431+
/// Removes the poison guard, but keeps the mutex locked.
432+
pub(super) fn into_mutex(guard: MutexGuard<'mutex, T>) -> &'mutex Mutex<T> {
433+
guard.__lock.poison.done(&guard.__poison);
434+
let lock = guard.__lock;
435+
mem::forget(guard);
436+
lock
437+
}
421438
}
422439

423440
#[stable(feature = "rust1", since = "1.0.0")]
@@ -461,14 +478,6 @@ impl<'a, T: ?Sized + fmt::Display> fmt::Display for MutexGuard<'a, T> {
461478
}
462479
}
463480

464-
pub fn guard_lock<'a, T: ?Sized>(guard: &MutexGuard<'a, T>) -> &'a sys::Mutex {
465-
&guard.__lock.inner
466-
}
467-
468-
pub fn guard_poison<'a, T: ?Sized>(guard: &MutexGuard<'a, T>) -> &'a poison::Flag {
469-
&guard.__lock.poison
470-
}
471-
472481
#[cfg(all(test, not(target_os = "emscripten")))]
473482
mod tests {
474483
use sync::mpsc::channel;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Test that panicking inside `Condvar::wait` doesn't poison the `Mutex`.
2+
//
3+
// Various platforms may trigger a panic while a thread is blocked, due to an
4+
// error condition. It can be tricky to trigger such a panic. The test here
5+
// shims `pthread_cond_timedwait` on Unix-like systems to trigger an assertion.
6+
// If at some point in the future, the assertion is changed or removed so that
7+
// the panic no longer happens, that doesn't mean this test should be removed.
8+
// Instead, another way should be found to trigger a panic inside
9+
// `Condvar::wait`.
10+
11+
// only-unix
12+
13+
#![feature(rustc_private)]
14+
15+
extern crate libc;
16+
17+
#[no_mangle]
18+
pub unsafe extern "C" fn pthread_cond_timedwait(
19+
_cond: *mut libc::pthread_cond_t,
20+
_mutex: *mut libc::pthread_mutex_t,
21+
_abstime: *const libc::timespec
22+
) -> libc::c_int {
23+
// Linux `man pthread_cond_timedwait` says EINTR may be returned
24+
*libc::__errno_location() = libc::EINTR;
25+
return 1;
26+
}
27+
28+
use std::sync::{Condvar, Mutex};
29+
30+
fn main() {
31+
let m = Mutex::new(());
32+
33+
std::panic::catch_unwind(|| {
34+
let one_ms = std::time::Duration::from_millis(2000);
35+
Condvar::new().wait_timeout(m.lock().unwrap(), one_ms).unwrap();
36+
}).expect_err("Condvar::wait should panic");
37+
38+
let _ = m.lock().expect("Mutex mustn't be poisoned");
39+
}

0 commit comments

Comments
 (0)