We recently tripped over the
Sync requirement for the argument passed to hyper’s
Body::wrap_stream constructor: normally, there’s no reason for
Stream to be
Stream describes a task to be submitted to an executor which will then poll until finished, possibly from different threads, but never concurrently. In contrast, the
Body for an HTTP response in hyper needs to be
Sync due to https://github.com/rust-lang/rust/issues/57017 and related behavior.
To make the long story short: handling
&Body in an async fn requires them to be
Send if held over an
.await, which requires
Body to be
Sync, which in turn requires the wrapped
Stream to be
From the documentation:
Sendtrait indicates that a value of this type is safe to send from one thread to another.
Synctrait indicates that a value of this type is safe to share between multiple threads.
These make perfect sense and enable Rust’s fearless concurrency, a very important and beneficial feature as I see it (as you may know I’m one of the main authors of Akka and I wish the JVM or the languages atop offered affine types!).
With the introduction of async/await, some usage patterns have manifested that warrant a discussion on the exact meaning of the above traits — please let me know if this discussion has already happened elsewhere and I’ll read up on it.
Sync means unrestricted sharing of a value between threads, the “sharing” over the lifetime of an async fn body is of a very particular nature: there will be no concurrent access to the value (barring bugs in the underlying async runtime implementation).
In practice I see two ways for users to deal with this right now:
- make the referenced object
Syncby changing its internal structure, e.g. wrapping values in mutexes
unsafe impl Syncon the grounds that in the case above
&mut selfin its
poll_nextfunction, thereby requiring the caller to guarantee mutual exclusion
Neither of them feels right: the mutex is not actually needed and has a performance overhead (mostly by being an optimization barrier to both the compiler and the CPU), and
unsafe should not be required by any end-user code, it should be purely opt-in if you want to optimize something.
So, is there something else that we can do?
&mut is named after “mutation”, but its type-system function is to guarantee exclusivity. Exclusivity is also required for non-mutating access to a shared data structure in order to guarantee consistency, which is the reason for having to take the mutex for just reading from the structure — calling it
&mut in the syntax is a bit more confusing than calling it
&excl but I think that ship has sailed very long ago
Why I’m bringing it up is that the
&mut fixes the
Sync issue for
Stream, at least to some degree, but not all users will understand this point without help.