-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into aduffy/rent-at
- Loading branch information
Showing
2 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<u8> = 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<u8> = 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` 转换能力,欢迎汇报相关问题; |