7 releases

0.1.5 Aug 11, 2023
0.1.4 Jul 26, 2023
0.0.0 Jul 17, 2021

#270 in Asynchronous

43 downloads per month

MIT license



The A10 io_uring library.

This library is meant as a low-level library safely exposing the io_uring API. For simplicity this only has two main types and a number of helper types:

  • Ring is a wrapper around io_uring used to poll for completion events.
  • AsyncFd is a wrapper around a file descriptor that provides a safe API to schedule operations.

Linux Required

Currently this requires a new Linux kernel version, everything should work on Linux v6.1 and up (currently the latest TLS).


A10 is expected to be integrated into a Future runtime, but it can work as a stand-alone library.

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

use a10::{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 = 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.

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

    // 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`.

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

For more examples see the examples directory.