14 个版本

新版本 0.2.2 2024 年 8 月 17 日
0.2.0 2024 年 6 月 14 日
0.1.7 2024 年 3 月 30 日
0.1.5 2023 年 8 月 11 日
0.0.0 2021 年 7 月 17 日

#112异步

Download history 2/week @ 2024-04-27 19/week @ 2024-05-18 5/week @ 2024-05-25 4/week @ 2024-06-01 110/week @ 2024-06-08 50/week @ 2024-06-15 4/week @ 2024-06-22 27/week @ 2024-07-27 118/week @ 2024-08-10

145 每月下载量

MIT 许可证

450KB
10K SLoC

A10

A10 是一个针对荷兰 A10 高速公路的 io_uring 库。

这个库旨在作为低级库安全地公开 io_uring API。为了简单起见,它只包含两种主要类型和一些辅助类型

  • Ring 是 io_uring 的包装器,用于轮询完成事件。
  • AsyncFd 是文件描述符的包装器,提供了一个安全的 API 来调度操作。

Linux 要求

目前这需要相当新的 Linux 内核版本,Linux v6.1 及以上版本应该都能正常工作。

示例

A10 预计将被集成到 Future 运行时中,但它也可以作为一个独立的库使用。

use std::future::Future;
use std::io;
use std::path::PathBuf;

use a10::{AsyncFd, Extract, Ring, SubmissionQueue};

fn main() -> io::Result<()> {
    // Create a new I/O uring supporting 8 submission entries.
    let mut ring = Ring::new(8)?;

    // Get access to the submission queue, used to... well queue submissions.
    let sq = ring.submission_queue().clone();
    // A10 makes use of `Future`s to represent the asynchronous nature of
    // io_uring.
    let future = cat(sq, "./src/lib.rs");

    // This `block_on` function would normally be implement by a `Future`
    // runtime, but we show a simple example implementation below.
    block_on(&mut ring, future)
}

/// A "cat" like function, which reads from `filename` and writes it to
/// standard out.
async fn cat(sq: SubmissionQueue, filename: &str) -> io::Result<()> {
    // Because io_uring uses asychronous operation it needs access to the
    // path for the duration the operation is active. To prevent use-after
    // free and similar issues we need ownership of the arguments. In the
    // case of opening a file it means we need ownership of the file name.
    let filename = PathBuf::from(filename);
    // Open a file for reading.
    let file: AsyncFd = a10::fs::OpenOptions::new().open(sq.clone(), filename).await?;

    // Next we'll read from the from the file.
    // Here we need ownership of the buffer, same reason as discussed above.
    let buf = file.read(Vec::with_capacity(32 * 1024)).await?;

    // Let's write what we read from the file to standard out.
    let stdout = a10::io::stdout(sq);
    // For writing we also need ownership of the buffer, so we move the
    // buffer into function call. However by default we won't get it back,
    // to match the API you see in the standard libray.
    // But using buffers just once it a bit wasteful, so we can it back
    // using the `Extract` trait (the call to `extract`). It changes the
    // return values (and `Future` type) to return the buffer and the amount
    // of bytes written.
    let (buf, n) = stdout.write(buf).extract().await?;

    // All done.
    Ok(())
}

/// Block on the `future`, expecting polling `ring` to drive it forward.
fn block_on<Fut, T>(ring: &mut Ring, future: Fut) -> Fut::Output
where
    Fut: Future<Output = io::Result<T>>,
{
    use std::ptr;
    use std::task::{self, Poll, RawWaker, RawWakerVTable};

    // Pin the future to the stack so we don't move it around.
    let mut future = std::pin::pin!(future);

    // Create a task context to poll the future work.
    let waker = unsafe { task::Waker::from_raw(RawWaker::new(ptr::null(), &WAKER_VTABLE)) };
    let mut ctx = task::Context::from_waker(&waker);

    loop {
        match future.as_mut().poll(&mut ctx) {
            Poll::Ready(result) => return result,
            Poll::Pending => {
                // Poll the `Ring` to get an update on the operation(s).
                //
                // In pratice you would first yield to another future, but
                // in this example we don't have one, so we'll always poll
                // the `Ring`.
                ring.poll(None)?;
            }
        }
    }

    // A waker implementation that does nothing.
    static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
        |_| RawWaker::new(ptr::null(), &WAKER_VTABLE),
        |_| {},
        |_| {},
        |_| {},
    );
}

更多示例请参见示例目录

依赖项

~360KB