-
Notifications
You must be signed in to change notification settings - Fork 0
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
AsyncRead and AsyncWrite #5
Comments
Some resources:
|
async fn is better, but it was blocked by async-trait which is unlikely to be stabilized in the short term. 😞 |
I think we have a solution to dyn-safe async traits that doesn't require 'inline' async. |
ReadBuf (RFC 2930) has landed on nightly now (rust-lang/rust#81156) |
Note that |
Another known blocker that I haven't seen mentioned yet: the working group needs to make a decision on the feasibility of async overloading. This will have consequences for the shape and location of the async IO traits. Async overloading is being tracked on the roadmap, but does not yet have an initiative owner. |
From the docs, they appear to have different definitions? The async-std versions have significantly more methods. Not sure if it a re-export of an earlier version of the futures definition or something more complex than that. |
yeah, that touches on another thing we probably shouldn't have done: the methods on The reason why we did this is because we wanted to push the "async-std is an async version of std" idea as far as we could. We wanted to prove out that it is indeed possible to map std's abstractions and usage patterns 1:1 to async Rust. And it worked; we now know that that it indeed can be done - modulo some missing language features like "async closures", "async drop" and "async traits". But perhaps if we had to try this again, we could've just exposed it as |
A proposed design: https://www.ncameron.org/blog/async-read-and-write-traits/ I should flesh out the alternatives with examples and/or why they don't meet the stated goals. |
Just one comment on the proposed design (which looks fine to me although I'm not really the target audience):
|
These are simplified examples, in real life we might use one shared buffer or allocate space on the heap to be used in other functions, etc. |
Shouldn't |
Whoops! They absolutely should. Fixed now, thanks! |
Feedback from Fuchsia:
And wonder if we could add a byte count to Discussion ongoing on Zulip |
Some discussion on a possible alternative where we have a type like smol's |
The |
I've started to write up the designs from the blog posts in https://github.com/nrc/portable-interoperable/tree/master/io-traits |
Filed #7 related to this issue. |
The current version of the traits described in the README include vectored methods. In practice, I have found this to be a mistake because it is not possible to have a good default implementation. The user of Read/Write must have a different implementation depending on whether the I/O handle can support vectored ops. What this means in practice, a library like Hyper that takes a I think, for vectored ops, it should be a separate trait. Converting a |
Do you think the
interface that can be found in existing AsyncWrite/AsyncRead/Write/Read solves the problem? |
Would it work? Possibly. However, once you start adding boolean checks to see if a trait impl supports a feature or not, this seems to strongly suggest there should be two traits. |
That's true. But if it is split into two traits, then what if the user has an IoSlice and doesn't care about the efficiency anyway? |
@Noah-Kennedy I wonder is it possible for Suppose that:
With these two assumptions, can we implement |
Nope, because when dropping a future for an in-flight op, it would be unsound still. |
You can still make this work via IORING_OP_POLL_ADD, which lets you do readiness-based IO via uring. |
Can we add a It will be great if we have async drop though. |
No, because this would block the runtime. |
Thanks, sounds like this can only be fixed with async drop. |
Yup |
Even async Drop does not fix it. See https://ncameron.org/blog/async-io-with-completion-model-io-systems/ (tl;dr: destructors are not guaranteed to run in Rust) |
I didn't have time to read the post, but based on the tl;dir: it's okay if the destructors aren't run. The problem is if they are run while the OS is still using the buffer. Leaking the buffer would be fine, not ideal of course, but at least it won't be unsound. I've been thinking about using a separate "clean up" future for this. It would be implemented as a queue to which items can be send, on receiving an item it would wait for it to complete and call the destructor, essentially spawning a future to the "async drop". For I/O uring this would for example send it a function/future that would cancel the I/O operation and drop/dealloc the buffer. Of course this design doesn't work with |
If the returned future is not |
|
According to
Also from
And this:
Essentially, you can only |
@Thomasdezeeuw been thinking about that as well actually |
@Noah-Kennedy @nrc IMO it might be a good idea to to standardise Then we can add the following interfaces: trait Read {
async fn get_managed_buffers(&mut self, n: NonZeroUsize) -> Result<BytesMut>;
async fn read_owned(&mut self, n: NonZeroUsize, owned_buf: BytesMut) -> Result<BytesMut>;
async fn read_into_provided_buf(&mut self, n: NonZeroUsize) -> Result<BytesMut>;
}
trait Write {
async fn get_managed_buffers(&mut self, n: NonZeroUsize) -> Result<BytesMut>;
async fn write_owned(&mut self, owned_buf: Bytes) -> Result<usize>;
} That will enable efficient use of io-uring since it is zero-copy and we could also support provided buffer easily. Though stablising |
@NobodyXu The trouble with this approach is that it is a long way from the sync versions of Read/Write, and is a pretty un-ergonomic API. There is more on the requirements, etc. here: https://github.com/nrc/portable-interoperable/tree/master/io-traits#requirements |
Yeah, but I don't see any other way to support completion based systems efficiently, since they are most efficient with managed/provided buffer. Passing a reference to a buffer requires copying around to internal buffer and even if we fix that, it still cannot match the performance of provided buffer due to the optimizations that can be applied to provided/managed buffer. Perhaps we can provide separate |
The proposal in the document is to support completion systems via BufRead and possibly a new OwnedRead trait |
Sorry that I missed that part. |
@NobodyXu when you say "provided buffer", are you referring to owned buffers or kernel-managed buffer groups? |
I am referring to kernel-managed buffer groups. |
I think that this is something which, while very, very important, is probably out of scope of current standardization efforts. |
Hmmm yeah, I can see that the existing proposal is already quite complex. |
Any update for this suggested design? I'm looking for async read/write trait for completion APIs and this one is the only thing that I found (and looks reasonable to me), so want to measure feasibility. |
Tracking issue for work on AsyncRead and AsyncWrite. The eventual goal is that we have a single, standardised version of these traits in std which are used by all (or at least most mainstream) async runtimes.
Known technical issues:
poll_read
/poll_write
functions orasync fn read
/async fn write
AsyncRead
Error
trait to libcore, which is work in progressThere are also several closely related traits such as
AsyncSeek
, and in various libraries, extension traits andAsyncBufRead
.Current implementations:
Smol re-exports the futures versions.
The text was updated successfully, but these errors were encountered: