Skip to content

Library safely exposing the io_uring API.

License

Notifications You must be signed in to change notification settings

Thomasdezeeuw/a10

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A10

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 fairly new Linux kernel version, everything should work on Linux v6.1 and up.

Examples

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

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),
        |_| {},
        |_| {},
        |_| {},
    );
}

For more examples see the examples directory.