[Second Review] SE-0410: Atomics

These objections match the thinking that led to swift-atomics originally marking its constructs conditionally Sendable.

It is indeed quite possible that this was the right choice.

I'm raising this as an issue though, if only to force us to clarify what Sendable means in this context. In the post that introduced the bad AccessCounted implementation, I pretended this question was already settled:

This code has a race condition by most definitions of this term. However, it never engages in undefined behavior: while it can produce incorrect results, it still operates entirely within Swift's abstract memory model. Sendable checking does not protect against logic bugs like this -- atomic operations can be tricky to use correctly, even in the "simplest" use cases.

However, it isn't quite clear to me that this has been previously covered. SE-0302 talks a lot about "the safety of passing data between concurrency domains", but I can't remember if I saw a clear definition of precisely what that means.

Do we consider it safe to pass borrows of AtomicCountBy2 between concurrency domains, even if doing so can lead to (high-level) race conditions?

The construct does avoid undefined behavior, which is already a quite strong guarantee -- is it okay to consider that our official litmus test, then?

If we do consider this safe, then we can allow the Sendable conformance to be automatically inferred, as usual, and so it seems technically okay to allow struct Atomic to be conditionally Sendable.

If we consider such defects to violate sendability, then we'd need to force developers to spell @unchecked Sendable whenever they want to use atomics. But that would still reintroduce the same potential issue one level higher on the stack! In effect, we'd just be training developers to mindlessly use @unchecked Sendable without addressing the core issue, which is that conventional assumptions about composition are fundamentally incompatible with concurrency. This does seem unpalatable.

There is a third possibility though -- that we accept the idea that Sendable is "only" supposed to prevent undefined behavior, but we still refrain from declaring struct Atomic conditionally Sendable, as a judgment call.

In particular, do we really want to allow types that rely on memory orderings other than .sequentiallyConsistent to be implicitly inferred as Sendable? (Keep in mind that it is difficult to make good use of the semantics of acquiring/releasing ordering in such implicitly sendable contexts (as mutating access to other components is disallowed), and, worse, as I understand it, the meaning of relaxed ordering isn't currently well-defined, and that makes it difficult to formally reason about the behavior of code that uses it.)

1 Like