rt: read join waker conditionally to avoid data race (#2096)

The previous implementation would perform a load that might be part of a
data race. The value read would be used only when race did not occur.
This would be well defined in a memory model where a load that is a part
of race merely returns an undefined value, the Rust memory model on the
other hand defines it to be undefined behaviour.

Perform read conditionally to avoid data race.

Covered by existing loom tests after changing casualty check to be
immediate rather than deferred.

Fixes: #2087
This commit is contained in:
Tomasz Miąsko
2020-01-29 11:44:15 -08:00
committed by GitHub
parent 6232c74724
commit 560d0fa548
+19 -16
View File
@@ -1,5 +1,4 @@
use crate::loom::alloc::Track; use crate::loom::alloc::Track;
use crate::loom::cell::CausalCheck;
use crate::task::core::{Cell, Core, Header, Trailer}; use crate::task::core::{Cell, Core, Header, Trailer};
use crate::task::state::Snapshot; use crate::task::state::Snapshot;
use crate::task::{JoinError, Schedule, Task}; use crate::task::{JoinError, Schedule, Task};
@@ -151,8 +150,12 @@ where
pub(super) unsafe fn drop_task(mut self) { pub(super) unsafe fn drop_task(mut self) {
let might_drop_join_waker_on_release = self.might_drop_join_waker_on_release(); let might_drop_join_waker_on_release = self.might_drop_join_waker_on_release();
// Read the join waker cell just to have it let join_waker = if might_drop_join_waker_on_release {
let (join_waker, check) = self.read_join_waker(); // Read the join waker cell just to have it
self.read_join_waker()
} else {
MaybeUninit::uninit()
};
// transition the task to released // transition the task to released
let res = self.header().state.release_task(); let res = self.header().state.release_task();
@@ -163,7 +166,6 @@ where
debug_assert!(res.has_join_waker()); debug_assert!(res.has_join_waker());
// Its our responsibility to drop the waker // Its our responsibility to drop the waker
check.check();
let _ = join_waker.assume_init(); let _ = join_waker.assume_init();
} }
@@ -201,14 +203,13 @@ where
// possible that, after the transition, we are responsible for dropping // possible that, after the transition, we are responsible for dropping
// the waker but before the waker can be read from the struct, the // the waker but before the waker can be read from the struct, the
// struct is deallocated. // struct is deallocated.
let (waker, check) = self.read_join_waker(); let waker = self.read_join_waker();
// The operation counts as dropping the join handle // The operation counts as dropping the join handle
let res = self.header().state.complete_join_handle(); let res = self.header().state.complete_join_handle();
if res.is_released() { if res.is_released() {
// We are responsible for freeing the waker handle // We are responsible for freeing the waker handle
check.check();
drop(waker.assume_init()); drop(waker.assume_init());
} }
@@ -264,7 +265,7 @@ where
// possible that, after the transition, we are responsible for dropping // possible that, after the transition, we are responsible for dropping
// the waker but before the waker can be read from the struct, the // the waker but before the waker can be read from the struct, the
// struct is deallocated. // struct is deallocated.
let (waker, check) = self.read_join_waker(); let waker = self.read_join_waker();
// The operation counts as dropping the join handle // The operation counts as dropping the join handle
let res = match self.header().state.drop_join_handle_slow() { let res = match self.header().state.drop_join_handle_slow() {
@@ -283,7 +284,6 @@ where
if !(res.is_complete() | res.is_canceled()) || res.is_released() { if !(res.is_complete() | res.is_canceled()) || res.is_released() {
// We are responsible for freeing the waker handle // We are responsible for freeing the waker handle
check.check();
drop(waker.assume_init()); drop(waker.assume_init());
} }
@@ -466,11 +466,15 @@ where
if join_interest { if join_interest {
let res1 = self.transition_to_complete(join_interest); let res1 = self.transition_to_complete(join_interest);
// At this point, the join waker may not be changed. Once we perform let join_waker = if res1.has_join_waker() {
// `release_task` we may no longer read from the struct but we // At this point, the join waker may not be changed. Once we perform
// **may** be responsible for dropping the waker. We do an // `release_task` we may no longer read from the struct but we
// optimistic read here. // **may** be responsible for dropping the waker. We do an
let (join_waker, check) = unsafe { self.read_join_waker() }; // optimistic read here.
unsafe { self.read_join_waker() }
} else {
MaybeUninit::uninit()
};
let res2 = self.header().state.release_task(); let res2 = self.header().state.release_task();
@@ -478,7 +482,6 @@ where
debug_assert!(res2.has_join_waker()); debug_assert!(res2.has_join_waker());
// Its our responsibility to drop the waker // Its our responsibility to drop the waker
check.check();
unsafe { unsafe {
drop(join_waker.assume_init()); drop(join_waker.assume_init());
} }
@@ -544,8 +547,8 @@ where
}); });
} }
unsafe fn read_join_waker(&mut self) -> (MaybeUninit<Option<Waker>>, CausalCheck) { unsafe fn read_join_waker(&mut self) -> MaybeUninit<Option<Waker>> {
self.trailer().waker.with_deferred(|ptr| ptr.read()) self.trailer().waker.with(|ptr| ptr.read())
} }
unsafe fn to_task(&self) -> Task<S> { unsafe fn to_task(&self) -> Task<S> {