Skip to content
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

Possible memory order issue #9

Closed
AngelicosPhosphoros opened this issue Nov 27, 2023 · 7 comments
Closed

Possible memory order issue #9

AngelicosPhosphoros opened this issue Nov 27, 2023 · 7 comments

Comments

@AngelicosPhosphoros
Copy link

Hi!

I read a code of create and noticed this segment:

            // We need to use a volatile read here because the data may be
            // concurrently modified by a writer. We also use MaybeUninit in
            // case we read the data in the middle of a modification.
            let result = unsafe { ptr::read_volatile(self.data.get() as *mut MaybeUninit<T>) };

            // Make sure the seq2 read occurs after reading the data. What we
            // ideally want is a load(Release), but the Release ordering is not
            // available on loads.
            fence(Ordering::Acquire);

            // If the sequence number is the same then the data wasn't modified
            // while we were reading it, and can be returned.
            let seq2 = self.seq.load(Ordering::Relaxed);
            if seq1 == seq2 {
                return unsafe { result.assume_init() };
            }

Shouldn't fence here use AcqRel? While using Acquire indeed prevents moving second atomic load up, it doesn't prevent moving read_volatile down. So CPU may reorder your code like this:

seq1 = load_atomic;
seq2 = load_atomic;
if seq1 == seq2 {
   return load value; // This is valid for moving down because acquire doesn't prevent moving down.
}
@AngelicosPhosphoros AngelicosPhosphoros changed the title Is that code valid? Possible memory order issue Nov 27, 2023
@AngelicosPhosphoros
Copy link
Author

AngelicosPhosphoros commented Nov 27, 2023

Same thing with a fence here:

fence(Ordering::Release);

It doesn't prevent writes to the underlying memory from moving before updating an atomic.

It may be not noticed so far because x86-64 have a guaranteed store order but other platforms could do such moves.

IMHO, you should use AcqRel fences in both cases.

@Amanieu
Copy link
Owner

Amanieu commented Nov 28, 2023

The code actually works, but it's a bit subtle. Essentially the fence(Acquire) is there to effectively give the volatile load "acquire" semantics. This ensures it always executes before the seq2 load.

@AngelicosPhosphoros
Copy link
Author

But does volatile guarantee that CPU wouldn't reorder instructions? AFAIK, it only affects the compiler.

@Amanieu
Copy link
Owner

Amanieu commented Nov 28, 2023

Volatile on its own doesn't guarantee this, that's what the fence is for.

@AngelicosPhosphoros
Copy link
Author

Do I understand correctly that volatile read followed by a acquire fence hace same effect as acquire load, and therefore it would happen before any reads after fence?

@Amanieu
Copy link
Owner

Amanieu commented Nov 28, 2023

It's a bit of a gray area in the language, but it works in practice. The proper solution is to use something like rust-lang/rfcs#3301, but that doesn't exist in the language yet.

@AngelicosPhosphoros
Copy link
Author

Thank you for answering my questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants