From f6e50e49adf4db30260ef7694968fec6c36d4504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ihc=E7=AB=A5=E9=9E=8B=40=E6=8F=90=E4=B8=8D=E8=B5=B7?= =?UTF-8?q?=E5=8A=B2?= Date: Mon, 21 Oct 2024 17:35:05 +0800 Subject: [PATCH] docs: add docs for poll-io (#311) --- docs/en/poll-io.md | 99 ++++++++++++++++++++++++++++++++++++++++++++++ docs/zh/poll-io.md | 98 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 docs/en/poll-io.md create mode 100644 docs/zh/poll-io.md diff --git a/docs/en/poll-io.md b/docs/en/poll-io.md new file mode 100644 index 00000000..5ade6072 --- /dev/null +++ b/docs/en/poll-io.md @@ -0,0 +1,99 @@ +--- +title: Poll IO Support +date: 2024-10-21 17:00:00 +author: ihciah +--- + +Poll IO is a new IO Driver capability provided in Monoio, which allows for good compatibility with components that use poll-style IO interfaces. + +By default, for example, `TcpStream` will implement the `AsyncReadRent`/`AsyncWriteRent` traits. These traits require ownership transfer during read and write operations to ensure memory safety when using io-uring as the driver. Code written this way can run directly without modification, leveraging Monoio’s internal fallback mechanism, regardless of whether the runtime environment supports io-uring or not. Since this IO trait is completion-based, we refer to IOs implementing this type of trait as `CompIo` in the following text. + +However, many components still use mainstream poll-style IO interfaces, including `tokio::io::AsyncRead`, `async_std::io::Read`, and components like hyper, which abstract Read/Write interfaces to support multiple runtimes. Converting between these different poll-style IO interfaces is relatively easy. Since this IO trait is a synchronous attempt executed when ready, we will refer to it as `PollIo` in the following text. + +To support these components, Monoio provides: + +1. `IntoPollIo`/`IntoCompIo`: for converting between `PollIo` and `CompIo`. +2. Structures like `TcpStreamPoll`, which implement `tokio::io::AsyncRead/AsyncWrite`. + +## Usage Example + +1. First, ensure that your project enables the `poll-io` feature when adding `monoio` as a dependency(Requires version 0.2.2 or higher): + + ```toml + [dependencies] + monoio = { version = "0.2.4", features = ["poll-io"] } + ``` + +2. Import and use `IntoPollIo` to convert `CompIo` to `PollIo`. + + In the example below, we will start a listener, convert accepted connections to `PollIo`, and then use tokio’s `AsyncRead`/`AsyncWrite` traits for reading and writing. + + ```rust + use monoio::{ + io::{ + // Here AsyncReadExt and AsyncWriteExt are re-exported from and equivalent with tokio + poll_io::{AsyncReadExt, AsyncWriteExt}, + IntoPollIo, + }, + net::{TcpListener, TcpStream}, + }; + + #[monoio::main(driver = "fusion")] + async fn main() { + let listener = TcpListener::bind("127.0.0.1:50002").unwrap(); + println!("listening"); + loop { + let incoming = listener.accept().await; + match incoming { + Ok((stream, addr)) => { + println!("accepted a connection from {addr}"); + monoio::spawn(echo(stream)); + } + Err(e) => { + println!("accepted connection failed: {e}"); + return; + } + } + } + } + + async fn echo(stream: TcpStream) -> std::io::Result<()> { + // Convert completion-based io to poll-based io(which impl tokio::io) + let mut stream = stream.into_poll_io()?; + let mut buf: Vec = vec![0; 1024]; + let mut res; + loop { + // read + res = stream.read(&mut buf).await?; + if res == 0 { + return Ok(()); + } + + // write all + stream.write_all(&buf[0..res]).await?; + } + } + ``` + + More examples can be found in [examples/h2_client.rs](../../examples/h2_client.rs) and [examples/h2_server.rs](../../examples/h2_server.rs). + +## Underlying Implementation + +Poll IO capability is implemented based on epoll over io-uring: + +1. If the current driver is a legacy driver (e.g., on macOS or lower version Linux platforms), Poll IO capability can be provided directly; +2. If the current driver is an io-uring driver, enabling this feature will add an epoll fd to io-uring, and the Poll IO capability will be provided via this epoll fd. + +When we convert a `CompIo` to `PollIo`, if running on io-uring, it is necessary to obtain the epoll fd maintained within and register the file descriptor corresponding to the `CompIo` with it. This way, when CompIo becomes ready, the runtime can detect the readiness of the epoll fd via io-uring’s completion notifications and call `epoll_wait(0)` to get all the ready events on it. + +If the current driver is not io-uring, the conversion has almost no cost. + +On Linux platforms, the relationship between IO traits and underlying drivers is as follows: + +1. `CompIo` -> `io-uring` / `epoll` +2. `PollIo` -> `epoll` / `epoll over io-uring` + +## Notes + +1. This feature provides good support for libraries such as hyper and h2. However, to fully utilize io-uring’s capabilities, it is recommended to use CompIo-style IO interfaces (`AsyncReadRent`, `AsyncWriteRent`) in hot paths; +2. This feature has undergone extensive production environment validation, but some components may not yet support the `IntoPollIo`/`IntoCompIo` conversion capabilities. Please feel free to report any related issues. diff --git a/docs/zh/poll-io.md b/docs/zh/poll-io.md new file mode 100644 index 00000000..e155e035 --- /dev/null +++ b/docs/zh/poll-io.md @@ -0,0 +1,98 @@ +--- +title: Poll IO 支持 +date: 2024-10-21 17:00:00 +author: ihciah +--- + +Poll IO 是 Monoio 中提供的一种新的 IO Driver 相关能力,借助这个能力可以良好地兼容使用 poll 风格 IO 接口的组件。 + +默认情况下,例如 `TcpStream` 会实现 `AsyncReadRent`/`AsyncWriteRent` trait。这种 trait 要求读写时转移所有权,因为只有这样才能在使用 io-uring 作为 driver 时保证内存安全。无论运行环境是否支持 io-uring,以该方式编写的代码都可以在不做任何修改的情况下借助 Monoio 内部的 fallback 机制直接运行。由于这种 IO trait 是面向完成通知的,下文将实现该类 trait 的 IO 称之为 `CompIo`。 + +但是,仍有大量的组件使用当前主流的 poll 风格 IO 接口,包括 `tokio::io::AsyncRead`,`async_std::io::Read`,以及 hyper 等组件为了支持多种 runtime 抽象出的 Read/Write 接口。在这些不同的 poll 风格 IO 接口之间转换是非常容易的。由于这种 IO trait 是在就绪是同步尝试执行的,下文中称之为 `PollIo`。 + +为了支持这些组件,Monoio 提供了: + +1. `IntoPollIo`/`IntoCompIo`:在 `PollIo` 和 `CompIo` 之间转换。 +2. `TcpStreamPoll` 等结构,实现 `tokio::io::AsyncRead/AsyncWrite`。 + +## 使用例子 + +1. 首先请确保你的项目在引入 `monoio` 时开启了 `poll-io` feature(需要 0.2.2 及以上版本): + + ```toml + [dependencies] + monoio = { version = "0.2.4", features = ["poll-io"] } + ``` + +2. 引入并使用 `IntoPollIo` 来将 `CompIo` 转换为 `PollIo`。 + 在下面的例子中,我们将启动一个 Listener,并将 accept 到的连接转换为 PollIo,之后使用 tokio 的 `AsyncRead`/`AsyncWrite` trait 进行读写。 + + ```rust + use monoio::{ + io::{ + // Here AsyncReadExt and AsyncWriteExt are re-exported from and equivalent with tokio + poll_io::{AsyncReadExt, AsyncWriteExt}, + IntoPollIo, + }, + net::{TcpListener, TcpStream}, + }; + + #[monoio::main(driver = "fusion")] + async fn main() { + let listener = TcpListener::bind("127.0.0.1:50002").unwrap(); + println!("listening"); + loop { + let incoming = listener.accept().await; + match incoming { + Ok((stream, addr)) => { + println!("accepted a connection from {addr}"); + monoio::spawn(echo(stream)); + } + Err(e) => { + println!("accepted connection failed: {e}"); + return; + } + } + } + } + + async fn echo(stream: TcpStream) -> std::io::Result<()> { + // Convert completion-based io to poll-based io(which impl tokio::io) + let mut stream = stream.into_poll_io()?; + let mut buf: Vec = vec![0; 1024]; + let mut res; + loop { + // read + res = stream.read(&mut buf).await?; + if res == 0 { + return Ok(()); + } + + // write all + stream.write_all(&buf[0..res]).await?; + } + } + ``` + + 更多例子可以参考 [examples/h2_client.rs](../../examples/h2_client.rs) 和 [examples/h2_server.rs](../../examples/h2_server.rs)。 + +## 底层实现 + +Poll IO 能力基于 epoll over io-uring 实现: + +1. 如果当前 driver 为 Legacy Driver(例如,在 macOS,或低版本 Linux 平台上),那么可以直接对外提供 Poll IO 能力; +2. 如果当前 driver 为 io-uring Driver,那么开启本 feature 后,会在 io-uring 上添加一个 epoll fd,并基于这个 epoll fd 对外提供 Poll IO 能力。 + +当我们将一个 `CompIo` 转换为 `PollIo` 时,如果当前运行在 io-uring 上,则需要取得在其中维护的 epoll fd 并向其注册该 `CompIo` 对应的文件描述符。这样,当 `CompIo` 就绪时,Runtime 就可以通过 io-uring 的完成通知感知到 epoll fd 就绪,并执行 `epoll_wait(0)` 以获得所有在其上的就绪事件。 + +如果当前 driver 非 io-uring,则转换操作是几乎没有成本的。 + +在 Linux 平台上,IO trait 与底层 driver 对应关系: + +1. `CompIo` -> `io-uring` / `epoll` +2. `PollIo` -> `epoll` / `epoll over io-uring` + +## 注意事项 + +1. 使用该功能可以良好地支持 hyper、h2 等库;但为了充分利用 io-uring 的能力,建议在 hot path 上使用 `CompIo` 风格的 IO 接口(`AsyncReadRent`、`AsyncWriteRent`); +2. 该功能已经过较大规模的生产环境验证,但当前可能有部分组件没有支持 `IntoPollIo`/`IntoCompIo` 转换能力,欢迎汇报相关问题;