Skip to content

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

Closed
@briansmith

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.

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