-
-
Notifications
You must be signed in to change notification settings - Fork 1.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
hyper should use its own IO traits #3110
Comments
I am a fan of this if a proper trait could be found and the tokio experience has short-cuts such that the difference in experience with this change is minimal. But since that will go in util I don't see why hyper core it self shouldn't be based on its own io types. |
I think you want to go 1.0 with Hyper long before we add such traits to std, but I think it would be great if using the std ones was in the longer-term plan. From my PoV, I'd love to know if there is anything in the current design proposal which wouldn't work for Hyper in the long run. Text: https://github.com/nrc/portable-interoperable/tree/master/io-traits#readme |
also really like this idea, at least until the traits are available in std, and the possibility to take them in directions that make sense for hyper while not necessarily desirable for tokio and to experiment with completion io. it also feels more in line with the design of 1.0 (from an onlooker perspective) when the main reason to keep using the tokio traits is easier integration given the other integration helpers have all (?) been moved to util |
The main thing I'm personally interested in here would be a mechanism to allow zero copy IO for APIs which require ownership of buffers. For IO uring, ideally implementations of the IO traits would be able to provide their own buffer types, which is essential for things like provided buffers (a facility for kernel-managed buffer pools). |
If this was going to be implemented how would it work in relation to the crates Hyper depends on? For example, h2 also uses Given these facts, I assume it would be necessary for a I came across this problem while trying to implement this PR, I would be curious about the planned solution and if so I might be able to continue my implementation. |
Oh, that's an interesting catch! I suppose it's a possibility we could define another crate, though I'm not so sure it's worth the hassle, yet. Another option is that I think that we perhaps don't have to decide that just yet, and could use an internal adapter anyways inside hyper, wrapping the IO type inside something implements Tokio's traits when we pass it to |
I'm taking this one now. One not-too-important detail I wondered at immediately, figured I'd ask for other opinions: should there be a separate |
|
There are two considerations I have here to add. The first one is that it's useful if hyper can avoid having to allocate memory for each in-progress TCP read that occurs. In io_uring (which we will get to in a minute) this is achieved via provided buffers. In epoll, this is achieved via buffer pools, where you wait for readiness, grab a buffer, attempt a read, and put it back if the read fails due to lack of readiness, and then you put the buffer back when you are done with it. The second is that I'd like an io_uring compatible hyper at some point. I think both are solvable if we get a bit more high-level and think about what hyper is trying to do. Do we need something that looks like tokio's IO traits, or can we make do with something more specialized to what we need? What I was thinking about was something resembling the below code for reads: pub trait HyperAsyncRead {
type Buffer: AsMut<[u8]>;
fn poll_recv(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<Self::Buffer>>;
}
pub trait HyperAsyncReadExt {
fn read(&mut self) -> Read<Self> {
Read {
io: self,
_pin: PhantomPinned::default(),
}
}
}
#[pin_project]
pub struct Read<'a, I: ?Sized> {
io: &'a mut I,
#[pin]
_pin: PhantomPinned,
}
impl<'a, I> Future for Read<'a, I>
where
I: HyperAsyncRead + Unpin + ?Sized,
{
type Output = io::Result<I::Buffer>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let projected = self.project();
Pin::new(&mut **projected.io).poll_recv(cx)
}
}
impl<I: HyperAsyncRead> HyperAsyncReadExt for I {} I'm still trying to think about writes. |
I have far less experience with low-level concepts like these than some folks here but I'd like to point out some implications of the above In order to use your own buffer pool, you need to implement an IO type yourself (this is probably good) struct MyOwnBufferPoolTcpStream {
inner: tokio::net::TcpStream
}
impl HyperAsyncRead for MyOwnbufferPoolTcpStream {
type Buffer = MyOwnBufferType;
...
} As far as alternate suggestions, I don't have any. I thought briefly about a All this said - owned buffer writes are more complicated. Hyper will likely need to acquire buffers, write into them, and then pass them to the Here's a brief sketch of an idea.. pub trait HyperBuffer: Sized {
fn poll_acquire(cx: &mut Context<'_>) -> Poll<io::Result<Self>>;
fn put_bytes(&mut self, bytes: &[u8]) -> std::io::Result<()>;
}
pub trait HyperAsyncWriter {
type Buffer;
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), (io::Error, Option<Self::Buffer>)>>;
}
pub trait HyperAsyncWrite {
type Buffer: HyperBuffer;
type Writer<'a>: HyperAsyncWriter
where
Self: 'a;
fn start_write<'a>(self: Pin<&'a mut Self>, buffer: Self::Buffer) -> Self::Writer<'a>;
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>>;
} It's important that the This API forces the struct VecBuffer {
vec: Vec<u8>,
}
struct TcpStream {
io: tokio::net::TcpStream,
}
struct TcpStreamVecWriter<'a> {
written: usize,
buffer: Option<Vec<u8>>,
io: Pin<&'a mut TcpStream>,
}
impl HyperBuffer for VecBuffer {
fn poll_acquire(_: &mut Context<'_>) -> Poll<io::Result<Self>> {
Poll::Ready(Ok(VecBuffer {
vec: Vec::with_capacity(4096),
}))
}
fn put_bytes(&mut self, bytes: &[u8]) -> std::io::Result<()> {
self.vec.extend_from_slice(bytes);
Ok(())
}
}
impl<'a> HyperAsyncWriter for TcpStreamVecWriter<'a> {
type Buffer = VecBuffer;
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), (io::Error, Option<Self::Buffer>)>> {
let this = self.get_mut();
let to_write = this.buffer.as_ref().expect("Polled after completion");
match ready!(Pin::new(&mut this.io.io).poll_write(cx, &to_write[this.written..])) {
Ok(count) => {
this.written += count;
if this.written == to_write.len() {
this.buffer.take();
Poll::Ready(Ok(()))
} else {
Pin::new(this).poll_write(cx)
}
}
Err(e) => Poll::Ready(Err((e, this.buffer.take().map(|vec| VecBuffer { vec })))),
}
}
}
impl HyperAsyncWrite for TcpStream {
type Buffer = VecBuffer;
type Writer<'a> = TcpStreamVecWriter<'a>;
fn start_write<'a>(self: Pin<&'a mut Self>, buffer: Self::Buffer) -> Self::Writer<'a> {
TcpStreamVecWriter {
written: 0,
buffer: Some(buffer.vec),
io: self,
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.get_mut().io).poll_flush(cx)
}
} While I think this "would work". There's a few things I'm not happy with or unsure about.
Unsure about:
Perhaps an alternative to Thanks for considering my rambling Edit: Here's an alternative write trait that does away with the Writer trait: pub trait HyperAsyncWrite {
type Buffer: HyperBuffer;
type Writable;
fn start_write(self: Pin<&mut Self>, buffer: Self::Buffer) -> Self::Writable;
fn poll_write(
self: Pin<&mut Self>,
writable: Pin<&mut Self::Writable>,
cx: &mut Context<'_>,
) -> Poll<Result<(), (std::io::Error, Option<Self::Buffer>)>>;
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>>;
} This enables concurrent reads and writes, since each call to |
I've opened #3230 which implements this. There are some remaining decisions to be made as part of the implementation, listed in the PR description. |
Given that provided buffers where mentioned so much, I think it is relevant to say that it has been superseded by ring mapped buffers in 5.19. There is little to no documentation about it (this commit and this file are the only ones as far as I know). Now, the naming seems to be a little ambiguous, it seems that provided buffers is sometimes used to refer to both ring mapped buffers and the legacy provided buffers, but I thought it was relevant. |
My apologies, I used the term provided buffers to refer to both collectively when I spoke. |
This replaces the usage of `tokio::io::{AsyncRead, AsyncWrite}` in hyper's public API with new traits in the `hyper::rt` module. Closes #3110 BREAKING CHANGE: Any IO transport type provided must not implement `hyper::rt::{Read, Write}` instead of `tokio::io` traits. You can grab a helper type from `hyper-util` to wrap Tokio types, or implement the traits yourself, if it's a custom type.
Was “Prove that it is forwards-compatible with completion-based IO” resolved? |
How is that forward compatible? Do you have any examples? |
Just want to call out here that this was merged but none of the examples on https://hyper.rs were updated, leading to the following error with the examples:
|
Includes a lightly-modified version of hyper-util's `TokioIo` utility. Hyper changes: v1.0.0-rc.4 (2023-07-10) Bug Fixes http1: http1 server graceful shutdown fix (#3261) ([f4b51300](hyperium/hyper@f4b5130)) send error on Incoming body when connection errors (#3256) ([52f19259](hyperium/hyper@52f1925), closes hyperium/hyper#3253) properly end chunked bodies when it was known to be empty (#3254) ([fec64cf0](hyperium/hyper@fec64cf), closes hyperium/hyper#3252) Features client: Make clients able to use non-Send executor (#3184) ([d977f209](hyperium/hyper@d977f20), closes hyperium/hyper#3017) rt: replace IO traits with hyper::rt ones (#3230) ([f9f65b7a](hyperium/hyper@f9f65b7), closes hyperium/hyper#3110) add downcast on Sleep trait (#3125) ([d92d3917](hyperium/hyper@d92d391), closes hyperium/hyper#3027) service: change Service::call to take &self (#3223) ([d894439e](hyperium/hyper@d894439), closes hyperium/hyper#3040) Breaking Changes Any IO transport type provided must not implement hyper::rt::{Read, Write} instead of tokio::io traits. You can grab a helper type from hyper-util to wrap Tokio types, or implement the traits yourself, if it's a custom type. ([f9f65b7a](hyperium/hyper@f9f65b7)) client::conn::http2 types now use another generic for an Executor. Code that names Connection needs to include the additional generic parameter. ([d977f209](hyperium/hyper@d977f20)) The Service::call function no longer takes a mutable reference to self. The FnMut trait bound on the service::util::service_fn function and the trait bound on the impl for the ServiceFn struct were changed from FnMut to Fn.
Hey, congrats on 1.0! Sorry if I should open a new issue instead of posting here, but I was wondering if there was a reason |
@coolreader18 let's make it a separate issue. but in short, we could provide it, and if done quickly, I think it's a fair non-breaking-breaking-change. Like, people probably didn't get that far, and little fixes like that right after launch happen. |
This replaces the usage of `tokio::io::{AsyncRead, AsyncWrite}` in hyper's public API with new traits in the `hyper::rt` module. Closes hyperium#3110 BREAKING CHANGE: Any IO transport type provided must not implement `hyper::rt::{Read, Write}` instead of `tokio::io` traits. You can grab a helper type from `hyper-util` to wrap Tokio types, or implement the traits yourself, if it's a custom type.
This replaces the usage of `tokio::io::{AsyncRead, AsyncWrite}` in hyper's public API with new traits in the `hyper::rt` module. Closes hyperium#3110 BREAKING CHANGE: Any IO transport type provided must not implement `hyper::rt::{Read, Write}` instead of `tokio::io` traits. You can grab a helper type from `hyper-util` to wrap Tokio types, or implement the traits yourself, if it's a custom type. Signed-off-by: Sven Pfennig <[email protected]>
… `tower` versions. Added `hyper-utils` (hyperium/hyper#3110)
Upgrade `tonic` to version 0.12, as a prerequisite to later on upgrading `hyper`. As of version 1, `hyper` no longer uses tokios `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrade `tonic` to version 0.12, as a prerequisite to later on upgrading `hyper`. As of version 1, `hyper` no longer uses tokios `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrade `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
Upgrading `tonic` is a prerequisite to later on upgrading `hyper` to version 1.0. As of version 1.0, `hyper` no longer uses `tokio`s `AsyncWriter` and `AsyncReader` traits, instead defining its own versions, see <hyperium/hyper#3110>. As tonic `0.12` is updated to use the `hyper 1.0` ecosystem, it changed some of its trait-bounds to the new `hyper` traits. The `hyper-utils` crate provides the wrapper `TokioIo`, which converts between the two. `prost` had to be upgraded as well, for compatibility.
This was actually brought up as an unresolved question in the ROADMAP. I think I mentally dismissed it at some point as just being annoying, but in the past week as I've thought about how we could make use of io-uring in hyper, I've been thinking about this again. If nothing else, this should be a public record of the decision, whether for or against. This will help others, including Future Us.
All other integration with a runtime (Tokio) has been removed, and helpers exist in
hyper-util
. hyper 1.0 (as of of rc.2) still depends ontokio
with all features turned off, to ask that IO transports implementtokio::io::{AsyncRead, AsyncWrite}
. By doing so, it makes it easier for users to simply supply atokio::net::TcpStream
. But, let me at least bring up some downsides.Reasons for
hyper::io::{AsyncRead, AsyncWrite}
tokio
just to implement the IO traits hyper wants. People have expressed concern that they don't want to compile multiple runtimes. While we can explain it's just the traits (when the features are turned off), owning the traits could make people less confused.We can provide
hyper_util::io::Tokio(T)
that implements the traits for Tokio types, to reduce friction.The text was updated successfully, but these errors were encountered: