Why is tokio::io::AsyncRead/Write not Send?

Why is AsyncRead in tokio does not require Send trait? Like this:

pub trait AsyncWrite: Send {
...

As far as I understand, Send makes it possible to have one invocation of a function from thread 1, and next invocation of the function from thread 2 (meaning that there are no things like thread local variables, and other tricks like this in the implementation of the function).

But AsyncWrite is not Send. Does it mean that tokio can poll futures only from a single thread (I guess where it was first/initially called)? What if this thread becomes busy?

trait AsyncWrite: Send { means that all implementors of AsyncWrite must also implement Send, whether or not this is needed for the application. trait AsyncWrite { without Send is more flexible, because each author of a T: AsyncWrite bound can decide whether or not to require T: AsyncWrite + Send or not.

This logic is true for most traits; the only ones that will have a Send supertrait (or any other supertrait) are ones for which it does not make sense to not also have the other trait.

4 Likes

Yes, I understand that this adds requirements to ALL implementations of the trait.

But then there is this question: what does tokio runtime do?

  • Option 1: It assumes "worst", which is AsyncWrite is not Send => it has to invoke functions of the trait from one and only one thread (no moves between threads).
    • But what if that threads becomes too busy (while other threads are not doing much)? This is going to be a huge issue, this leads to skew (you have 128 threads, but only 1 of them is working 100%)
  • Option 2: It somehow can infer whether a specific implementation of the trait is Send (in which case it can move it between threads) or not Send (in which case it cannot move object between threads).
    • But how would this code look like? We do not have overloading in Rust to have code like this:

      fn schedule<T: AsyncWrite + Send>(...) {} // can schedule from another thread
      fn schedule<T: AsyncWrite>(...) {} // can schedule from single thread
      

In other words, do we pay the price (which is skew) of AsyncWrite not requiring any implementation to be Send in all cases (even if specific implementation is Send)?

It goes for Option 3. If you use the "current thread" runtime or a LocalSet, then futures that get spawned do not have to be Send, since they cannot be moved between threads. If you use the multithread runtime, then spawned futures have to be Send, since they will be moved between threads; failure to respect this rule is a compile error.

4 Likes

Note also that the runtime does not care about the characteristics of AsyncRead or AsyncWrite as traits. Those traits are called by the application code, usually inside of futures — so whether a given AsyncWrite implementor is Send may affect whether its owning future is Send, and that matters to the runtime, but the runtime only sees another Future.

There are no Box<dyn AsyncWrite>s being used by the runtime — if there were, it would matter very much whether AsyncWrite: Send, but there aren't.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.