Description
Location
https://doc.rust-lang.org/nightly/core/sync/atomic/fn.compiler_fence.html#examples
Summary
use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::atomic::Ordering; use std::sync::atomic::compiler_fence; static IMPORTANT_VARIABLE: AtomicUsize = AtomicUsize::new(0); static IS_READY: AtomicBool = AtomicBool::new(false); fn main() { IMPORTANT_VARIABLE.store(42, Ordering::Relaxed); // prevent earlier writes from being moved beyond this point compiler_fence(Ordering::Release); IS_READY.store(true, Ordering::Relaxed); } fn signal_handler() { if IS_READY.load(Ordering::Relaxed) { assert_eq!(IMPORTANT_VARIABLE.load(Ordering::Relaxed), 42); } }
This example makes it look like there is some kind of guarantee for a release fence that isn't paired with an acquire operation or fence, which as far as I can tell, is not the case. (See the C++ docs for regular (thread) fences that do not guarantee anything for an unpaired release fence and compiler (signal) fences that are described as strictly weaker than thread fences.)
So in this example there is no happens-before relation between the store of IMPORTANT_VARIABLE
in main and the load thereof in the signal handler and therefore the signal handler may actually observe IS_READY == true
and IMPORTANT_VARIABLE == 0
.
Presumably the example should have a load fence in the signal handler and explain in the prose text above that the loads can be reordered, too:
fn signal_handler() {
if IS_READY.load(Ordering::Relaxed) {
+ // prevent later reads from being moved beyond this point
+ compiler_fence(Ordering::Acquire);
assert_eq!(IMPORTANT_VARIABLE.load(Ordering::Relaxed), 42);
}
}
related: #129189