-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
compiler_fence example is misleading #133014
Comments
This looks logical, but it's been a while since I read up on memory orderings. @RalfJung want to take a look? |
Good catch, the example indeed seems wrong. However, the fence is not defined in terms of "reordering", it is defined in terms of happens-before and other relationships, so that's also what the comment should refer to. Cc @rust-lang/opsem @chorman0773 |
The regular https://doc.rust-lang.org/nightly/core/sync/atomic/fn.fence.html#examples |
But I agree that the example would be better written with an explicit acquire edge, avoiding implying logic of “I observed the (relaxed) flag write after the fence, therefore this is okay.”1 Footnotes
|
I don't see how that would fix this example. If we assume an implicit acquire at the start of the signal handler here: 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() {
+ compiler_fence(Ordering::Acquire);
if IS_READY.load(Ordering::Relaxed) {
assert_eq!(IMPORTANT_VARIABLE.load(Ordering::Relaxed), 42);
}
} To me it looks like there is still no happens-before relation between
In this example, "B" is
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::atomic::{compiler_fence, fence};
#[no_mangle]
static mut IMPORTANT_VARIABLE: usize = 0;
static IS_READY: AtomicBool = AtomicBool::new(false);
#[no_mangle]
fn main() {
unsafe { IMPORTANT_VARIABLE = 42; }
// prevent earlier writes from being moved beyond this point
fence(Ordering::Release);
IS_READY.store(true, Ordering::Relaxed);
}
// SAFETY: `unknown` must be 0
#[no_mangle]
fn signal_handler(unknown: usize) -> usize {
let mut r = 0;
for _ in 0..unknown {
// Trick LLVM into reading IMPORTANT_VARIABLE early.
// By our safety condition, this read does not actually happen,
// so we don't have a data race here.
r += unsafe { IMPORTANT_VARIABLE };
}
if IS_READY.load(Ordering::Relaxed) {
// prevent later reads from being moved beyond this point
fence(Ordering::Acquire);
if unsafe { IMPORTANT_VARIABLE } == 42 {
return 42;
}
}
r
} |
Ah, whoops, you're correct, of course. I had mentally adjusted the loop outside the signal hook, because the loop is just useless in this example; the So this example really is just busted currently. It seems to be trying to use a compiler fence to ensure the store to |
I proposed a fix for the example in #136079, please have a look. |
…=jhpratt compiler_fence: fix example The old example was wrong, an acquire fence is required in the signal handler. To make the point more clear, I changed the "data" variable to use non-atomic accesses. Fixes rust-lang#133014
Location
https://doc.rust-lang.org/nightly/core/sync/atomic/fn.compiler_fence.html#examples
Summary
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 observeIS_READY == true
andIMPORTANT_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:
related: #129189
The text was updated successfully, but these errors were encountered: