-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
io: propose new AsyncRead / AsyncWrite traits #2716
Comments
I've heard at least a few people mention that they would prefer to use Tokio's Have we considered factoring out just the IO traits (and perhaps the This would hopefully result in more widespread ecosystem use of Tokio's IO traits, which could be useful for collecting design feedback, and good precedent if we do want to eventually propose them for inclusion in Just a thought. |
I'm one of the people that have mentioned concerns that feature flags on a |
For me, the main reason to keep |
@Nemo157 We also could possibly do the reverse. Provide a |
I noticed a difference in return type of
|
Ah. I would go w/ the std RFC. There is no point in returning the same data twice. That would lead to errors. I can update the original issue. |
That doesn't help identifying whether more feature flags have been set by a dependency. I guess the real solution would be to enhance |
I'm in a similar predicament: We want to use the traits, but we can't use the tokio runtime on fuchsia. So (since we use cargo vendor) there's a big blob of mostly-unused code in our tree, with a big diff of unsafe code every time we try to update. We'd get compiler errors if the runtime were accidentally enabled on fuchsia, but it could happen silently on host. It's really not a good situation when all we need is a couple traits. |
Works towards #2716. Changes the argument to `AsyncRead::poll_read` to take a `ReadBuf` struct that safely manages writes to uninitialized memory.
An initial attempt at implementing One would need to do: pin!(let stream_ref = my_stream.by_ref());
stream_ref.read(...). This is a bit awkward. A possible solution would be to have |
Enough has been done for 0.3. The remaining questions can be addressed before 1.0. |
The RFC link is dead. https://github.com/sfackler/rfcs/blob/read-buf/text/0000-read-buf.md . Could you please find a good link for it, and update the issue description? |
The |
Nothing left requires breaking changes. Removing the 1.0 tag. |
The OP was planning to remove vectored fns from
Therefore, with 1.0 there's no way to do vectored reads, only writes. What are the options now? Adding |
Vectored read methods can still be added to |
Vectored reads are also useful for dealing with input data that is splitted into several buffers, which is the case when dealing with fixed-size header + variable size payload protocols, to minimize the number of syscalls. Currently (as of Tokio 1.9.0) it is already possible to do vectored reads for |
There are no news, but I see no reason against adding them. |
Is this implemented as of 1.22.0? https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html |
Vectored reads? No. |
Hi, I'd like to ask current status for async trait from io-uring user perspective. Is there any ongoing discussion or plan to express completion API with async read trait variants? For example, ringbhan suggested withoutboats mentioned the trait in his blog as well (https://without.boats/blog/io-uring/), and I think it makes much sense. We've been hard user for tokio-uring, and current dedicated API requires owning buffer is one of the most painful design that user accepts and I want to fix this situation. Any ideas would be appreicated. |
I think the existing Io-uring works best with uring managed buffer, so tokio could maintain that io-uring buffer and then copy data out of it once the read is done, and put that buffer back for reuse. Same for For proxy which just forward data, some special copy methods could be provided using io-uring sendfile/splice/copy_file_range, or For I/O that needs to further process the data (i.e. decryption/decompression), then yeah, I think it needs to expose the internal buffer somehow, so that it could do zero-copy decryption/decompression. |
I think the solution that looks best right now is this:
|
That's reasonable decision, since having an implicit copy changes behavior of existing program, and io-uring might not provide enough speedup to justify the additional memory usage/copy. |
Ah, I did not aware that |
You can read about the status quo in the docs for |
Thx. This also helps me to understand current status. |
I'm not really a fan of implicit buffering. I think any new file IO we do with io_uring should probably have new traits which do not rely on buffering under the hood. |
Previous attempt: #1744.
Summary
The proposal aims to improve the I/O traits in a few ways:
unsafe
except in the leaf implementations (TcpStream
,File
, ...).poll_read_buf
andpoll_write_buf
functions from the traitsImproving the API to better support reads into uninitialized memory will be done by matching this RFC.
Removing
poll_read_buf
andpoll_write_buf
from the traits implies that vectored operations will no longer be covered by theAsyncRead
/AsyncWrite
traits. Instead, vectored operations will be provided as methods on structs that support them (TcpStream
, ...).Motivation
See the std RFC for the motivation behind better supporting reads into uninitialized memory.
As for
poll_read_buf
andpoll_write_buf
, these functions require function level generics. Including them on the trait causes problems when using trait objects. These functions were originally added toAsyncRead
andAsyncWrite
to provide vectored operation support at the trait level. However, vectored operations are not proving to be very useful on the traits.First, very few implementations of
AsyncRead
/AsyncWrite
actually implement these functions. Second, the default implementation of these functions is worse than just callingpoll_read
andpoll_write
. The default implementations just forward the first buffer topoll_read
andpoll_write
. However, when vectored operations are not available, the best strategy is to create an intermediate buffer instead of many small buffers. The cost of copies is lower than the cost of many syscalls.Including vectored operation functions on the trait makes it difficult for the caller to decide how to interact with the byte stream. The fact that callers need to behave differently with the byte stream when the byte stream supports vectored operation vs. when it doesn't implies that vectored vs. non-vectored operations are separate concerns.
In the future, we could provide vectored variants of
AsyncRead
/AsyncWrite
(maybeAsyncVectoredRead
andAsyncVectoredWrite
). However, as adding this would be forwards compatible, this decision can be punted.Proposal
The proposed traits are:
See the std rfc for the full
ReadBuf
type and examples of how to use it.Changes for the end-user of Tokio
This change will not materially change how the end-user of Tokio interacts with read/write types. Most users go via the
AsyncReadExt
andAsyncWriteExt
helper traits. The functions on these traits will stay the same for the most part. For example:Here the read fn remains the same. The implementation of the
read
fn wraps the supplied buffer, which is fully initialized in aReadBuf
.To support reads into uninitialized memory, a new helper function is added to
AsyncReadExt
:This function uses the
BufMut
trait provided by thebytes
crate to handle reading into uninitialized memory. The function bridges the trait with theReadBuf
struct.Where should the traits be defined?
A common question is "where should the traits be defined?". Should they be in Tokio, the
futures
crate, orstd
?Tokio aims to release version 1.0 by the end of the year. The 1.0 release will come with stability guarantees as well as a clear support timeline. To do this, all public types must provide at least as strong of a guarantee as Tokio. All types in
std
provide such a guarantee. As of now, thefutures
crate does not include stability guarantees.If the linked RFC is accepted, the
ReadBuf
type will be included bystd
at some point. We could then evaluate if using theReadBuf
instd
is possible.We could also propose equivalent
AsyncRead
/AsyncWrite
traits forstd
. The path would be to first land this proposal in Tokio 0.3 to gain experience and propose the traits as an RFC after that.That said, there may be an argument to maintain the
ReadBuf
and traits in Tokio itself for future-proofing Tokio for possibleio_uring
integration.Potential
io_uring
future-proofingio_uring
is a submission-based async I/O API in modern Linux kernels. It is still under active development but is already showing impressive results. Being a submission-based API makes it challenging to integrate w/ Tokio's current I/O model. Also, because it is still under active development and not available on most of today's Linux boxes, supportingio_uring
is not a priority for 1.0. However, it would be nice to be able to support it at some level before Tokio 2.0.At a high level,
io_uring
works by passing ownership of buffers between the process and the kernel. Submitting a read involves passing ownership of a buffer to the kernel to be filled with data. Submitting a write involves passing ownership of a buffer containing the data to write to the kernel. In both cases, when the kernel completes the operation, ownership of the buffer is passed back to the process.If Tokio owns the
ReadBuf
type, it could provide a specialized API forio_uring
resources to "take ownership" of the buffer:A
TcpStream
implementation backed byio_uring
would be able to try to take ownership of the buffer from theReadBuf
. If successful, the buffer could then be passed directly to the kernel without any additional copying. The caller would then callpoll_read
again once the read has completed. At that point,TcpStream
would attempt to "re-attach" the buffer. This, again, would be a no-copy operation.This works because
BytesMut
is a ref-counted structure. Re-attaching is done by ensuring that twoBytesMut
instances originally came from the same allocation. If re-attaching fails due to the caller passing a different buffer, then the read falls back to copying the data into the given read buffer.The same could be done with
AsyncWrite
:Risks
It is still much too early to know for sure if the future-proofed API will work well in supporting
io_uring
. However, the risk of future-proofingAsyncWrite
is minimal. TheWriteBuf
wrapper around a&[u8]
does not add overhead besides friction when using the traits. Most users would not interact directly withAsyncWrite
and, instead, use the helper functions provided byAsyncWriteExt
. As withAsyncReadExt
, these functions would not expose theWriteBuf
struct. Instead, they would bridge the input buffer with theWriteBuf
struct.Open questions
Should we attempt to future-proof
AsyncRead
/AsyncWrite
traits forio_uring
?The text was updated successfully, but these errors were encountered: