Skip to content

atomics documentation for conflicting access uses undefined term "write" leading to ambiguity about data races #136669

Description

@briansmith

Location

https://doc.rust-lang.org/nightly/core/sync/atomic/index.html

Summary

At https://doc.rust-lang.org/nightly/core/sync/atomic/index.html we have:

A data race is defined as conflicting non-synchronized accesses where at least one of the accesses is non-atomic. Here, accesses are conflicting if they affect overlapping regions of memory and at least one of them is a write. They are non-synchronized if neither of them happens-before the other, according to the happens-before order of the memory model.

I propose we change this to:

A data race is defined as conflicting non-synchronized accesses where at least one of the accesses is non-atomic. Here, accesses are conflicting if they affect overlapping regions of memory and at least one modifies part of the overlapping region. (A compare_exchange or compare_exchange_weak that doesn't succeed does not modify a part of the overlapping region.) They are non-synchronized if neither of them happens-before the other, according to the happens-before order of the memory model.

With the existing wording, the term "write" is not defined and isn't used in the reference C++ spec.

For motivation, consider these two operations happening concurrently in separate threads:

thread 1:

let _ = my_atomic_usize.load(Ordering::Acquire); // Synchronize [1]
// ...
let p = my_atomic_usize.as_ptr(); 
let value = unsafe { p.read() };  // [2]

thread 2:

let _ = my_atomic_usize.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire);

Obviously, if thread 2 succeeds in modifying my_atomic_usize (its value was zero) then its operation must be considered a "write". But, what if it doesn't succeed (the value was non-zero)? Is that compare_exchange still considered a "write"?

The real-world use case is once_cell::OnceNonZeroUsize, a one-time initialize type where a nonzero value indicates that initialization has already been done. When the application has already read a non-zero value ([1] above), it would like to use non-synchronized reads ([2] above) for subsequent loads, because the optimizer can then eliminate redundant loads. We think this is safe but only this presumes that compare_exchange is only considered a write if it succeeds, for the purpose of the quoted statement.

I understand compare_exchange that fails is still considered an "attempt to write" write when it is done on read-only memory, and thus leads to UB, according to a later section in same documentation.

But, "write" and "attempt to write" are not necessarily the same thing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-atomicArea: Atomics, barriers, and sync primitivesA-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsT-libsRelevant to the library team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions