No Atomic - Fence synchronization in Rust?

Prompted by a post on r/cpp, I was reading the little nugget by Raymond Chen explaining the necessary memory ordering when writing one's own Arc: Why can you increment a reference count with relaxed semantics, but you have to decrement with release semantics? - The Old New Thing.

static uint32_t __stdcall Release(fast_abi_forwarder* self) noexcept
{
    uint32_t const remaining = self->m_references.
        fetch_sub(1, std::memory_order_release) - 1;

    if (remaining == 0)
    {
        std::atomic_thread_fence(std::memory_order_acquire);
        delete self;
    }

    return remaining;
}

This is using atomic - fence synchronization, one of the 3 synchronization modes available with std::atomic_thread_fence, alongside fence - atomic and fence - fence.

This prompted me to realize that the Rust "equivalent", our own atomic::fence, only documents fence - fence synchronization.

Is this a documentation issue? Or does Rust not support atomic - fence & fence - atomic synchronizations?

I thought that Rust copied C/C++ memory model, so it's somewhat surprising (and unhelpful) to see a divergence.

It uses the C++ model and the same kinds of fences work. If you see a divergence, it's a documentation concern not an intentional difference.

2 Likes

Looking closer, I do think the wording is correct & complete, if a bit buried. In particular, the large diagram for fence - fence synchronization -- which is very useful -- takes on a disproportionately large amount of space.

I'll have to mull on the wording. I think clearly enunciating the 3 usecases at the top and then having one subsection with diagram for each usecase would make it much clearer. It would also ensure that the user understands how to do atomic - fence or fence - atomic synchronization properly, as it's not necessarily obvious how the operations on the "fence" side need to be organized from just reading the doc.

From the docs you linked:

Atomic operations with Release or Acquire semantics can also synchronize with a fence.

There is just no explicit example for this.

Yes, I completely missed it on the first (few) reads as they're buried after the massive diagram which grabs all attention.

@alice @bjorn3 I've attempted to improve the wording in PR #147887, I'd appreciate reviews.

1 Like

The questions are already answered, but do note that the support should also be indicated[1] that Rust's reference counting (Arc) does in fact do the exact same thing (increment Relaxed and decrement Release - the latter using a fence before moving on towards destruction[2]).


  1. it's not conclusive proof on its own that it's implemented this way, as std does sometimes also do things that downstream code isn't allowed to rely on, but at least it can be a strong clue towards Rust being equivalent here ↩︎

  2. there are some extra steps afterwards, but those only exist because there's also a weak pointer, and also operationally, at least calling the drop (glue) for the contained value is the very next thing that happens - the weak pointer-involving extra steps are only coming into okay in order to also - eventually - deallocate the immediate/shallow heap memory for the value behind the Arc ↩︎