Skip to content
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

Use io_uring for io operations #2140

Open
andresmargalef opened this issue Feb 26, 2020 · 21 comments
Open

Use io_uring for io operations #2140

andresmargalef opened this issue Feb 26, 2020 · 21 comments
Labels
B-upstream Blocked: needs a change in a dependency or the compiler. E-hard Effort: hard. Likely requires a deeper understanding of how hyper's internals work.

Comments

@andresmargalef
Copy link

I don't know if this is for hyper, tokio or mio but it is interesting to use the new io stack from linux 5.1 +.
In async java world there is interest to start to support the new feature.
Copy&paste that issue here

...
Since Linux 5.1 there’s a new mechanism for high-performance batched asynchronous IO called io_uring. While it was primarily designed for disk io, it also works for network requests: https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/

Is this something that would make sense for netty?
...
@seanmonstar
Copy link
Member

Yes we've been actively looking into this.

@seanmonstar seanmonstar added B-upstream Blocked: needs a change in a dependency or the compiler. E-hard Effort: hard. Likely requires a deeper understanding of how hyper's internals work. labels May 15, 2020
@quininer
Copy link
Contributor

quininer commented Jun 2, 2020

I have some attempts, and currently ritsu can be used in combination with hyper. :D

https://github.com/quininer/ritsu/blob/master/ritsu-hyper/src/main.rs

@andresmargalef
Copy link
Author

@quininer I will try that code, have you seen some improvement in performance?

@quininer
Copy link
Contributor

quininer commented Jun 2, 2020

@andresmargalef I am currently not focus performance improvement.

@fwsGonzo
Copy link

Have any of you had a look at rio? It seems to be a safe approach to io_uring

@MikailBag
Copy link

rio readme states:

use-after-free bugs are still possible without unsafe when using rio

AFAIK there is no way to build safe&sound API on top of io_uring without redesigning Tokio IO traits.

@malobre
Copy link

malobre commented Jul 20, 2021

@threeseed
Copy link

Also would add you can use Glommio which uses io_uring internally:

https://github.com/DataDog/glommio/blob/master/examples/hyper.rs

@jim-king-2000
Copy link

#577
So, does hyper support io_uring?

@andresmargalef
Copy link
Author

Tokio is working on io-uring here

@jim-king-2000
Copy link

Any web framework which supports io_uring?

@davidpdrsn
Copy link
Member

Any web framework which supports io_uring?

Unlikely given that most frameworks are built on top of hyper. There is this actix/actix-web#2404 but thats specifically for files 🤷

@jim-king-2000
Copy link

Any web framework which supports io_uring?

Unlikely given that most frameworks are built on top of hyper. There is this actix/actix-web#2404 but thats specifically for files 🤷

So, any next-generation web framework which will supports io_uring in the future?

@hiqsociety
Copy link

just support monoio and then we dont have to keep on upgrading for speed. right? the world is complicated enough

@Thomasdezeeuw
Copy link
Contributor

I have a runtime that is based on io_uring: Heph, which is based on A10. I'm trying to port Hyper's client to it, but its I/O traits will not work with io_uring (without introducing an intermediary buffer).

Taking hyper::rt::Read as an example, it's defined as below.

pub trait Read {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: ReadBufCursor<'_>
    ) -> Poll<Result<(), Error>>;
}

The problem is the lifetime in ReadBufCursor. This is fine for readiness based I/O, where you try the operation and if it fails (e.g. would block) you hand back the buffer to the caller and try again later (after get a ready event).

With completion based I/O, such as io_uring, this doesn't work. With completion based I/O you pass a mutable reference to the buffer to the kernel which it keeps around until the read(2) system call is complete. This means that if the read can't be completed within the call to Read::poll_read, which is very likely, we still need to keep the mutable reference to the buffer, because the kernel still has the mutable reference to the buffer. Furthermore if the Future/type owning the buffer is dropped before the read is complete we need to not deallocate the buffer, otherwise the kernel will write into memory we don't own any more.

For A10 the only solution to this problem I could come up with is that the Future that represents the read(2) call must have ownership of the buffer. If the Future is dropped before completion we can defer the deallocation of the buffer (by leaking it or in the case of A10 by deallocating it once the kernel is done with the read(2) call).

I'm not exactly sure how this would change Hyper's I/O traits though. I've been thinking about a an owned version of ReadBuf, which I think would be the easiest solution and would solve the problem described above. However if we want to take it to the next level and use io_uring's buffer pool (IORING_REGISTER_PBUF_RING) an owned version of ReadBuf will not be sufficient either.

Maybe we can use something similar to the hyper::body::Body trait where the I/O implementation can define its own buffer type. Ownership of the buffer will remain with the I/O type until the buffer is filled and Hyper can use it, which would solve the lifetime problem of ReadBufCursor described above. Furthermore the buffer type can be defined by the I/O implementation so io_uring's buffer pool can be used, which would solve the second problem.

Long post, but I hope it highlights some of the major blockers of using Hyper with a completion based I/O implementation.

@darren-fu

This comment has been minimized.

@saolof
Copy link

saolof commented Nov 25, 2024

@Thomasdezeeuw One alternative to the owned buffer could be to replace ReadBufCursor<'_> with some variation of ReadBufCursor<'self> (probably not the actual ReadBufCursor type). Then this ensures that the buffer stays available as long as you can poll the future. I'm not sure if io_uring has a straightforward way to deregister a target buffer when a future is dropped though.

@Thomasdezeeuw
Copy link
Contributor

this ensures that the buffer stays available as long as you can poll the future.

Except that's not long enough. The future can be dropped at any time, however the read operation might still be ongoing, which means the kernel still has mutable access to the buffer even after the future is dropped.

Either you have to do a synchronous cancel in the future's Drop implementation, meaning having a blocking Drop implement, or you need another solution. For A10 I chose to not block in the Drop implementation and instead delay the deallocation of the buffer until after the kernel was finished with it. But to achieve this you need ownership of the buffer.

@benesch
Copy link
Contributor

benesch commented Nov 28, 2024

Except that's not long enough. The future can be dropped at any time, however the read operation might still be ongoing, which means the kernel still has mutable access to the buffer even after the future is dropped.

Either you have to do a synchronous cancel in the future's Drop implementation, meaning having a blocking Drop implement...

And even that's not safe, right? The future might be leaked via mem::forget, and then the Drop implementation will never run.

@Thomasdezeeuw
Copy link
Contributor

And even that's not safe, right? The future might be leaked via mem::forget, and then the Drop implementation will never run.

It's only safe if the buffer is heap (or not stack) allocated. Because then the buffer is leaked and the kernel still has safe mutable access.

@Berrysoft
Copy link

About using io-uring, compio is such an async runtime that could use it in a safe way, and we have already wrap it for hyper in cyper. The traits of hyper is a little painful for us, but finally we find a way to provide such interfaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-upstream Blocked: needs a change in a dependency or the compiler. E-hard Effort: hard. Likely requires a deeper understanding of how hyper's internals work.
Projects
None yet
Development

No branches or pull requests