io2 0.0.2

single threaded async-io library
Documentation
use std::{io, marker::PhantomData, path::Path};

use super::file::{Close, File, Read, SyncAll, Write};

pub struct DioFile {
    file: File,
    dio_offset_align: u64,
    dio_mem_align: usize,
}

impl DioFile {
    pub async fn open(path: &Path, flags: i32, mode: i32) -> io::Result<DioFile> {
        let file = File::open(path, flags | libc::O_DIRECT, mode)?.await?;
        let statx = file.statx().await?;

        let dio_mem_align = usize::try_from(statx.stx_dio_mem_align).unwrap();
        let dio_offset_align = u64::from(statx.stx_dio_offset_align);

        if dio_mem_align == 0 || dio_offset_align == 0 {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "direct_io is not supported on this file, kernel might be old, or the file might be on an unsupported file system",
            ));
        }

        if !dio_mem_align.is_power_of_two() || !dio_offset_align.is_power_of_two() {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "at least one of direct_io alignment requirements returned by statx is not a power of two",
            ));
        }

        Ok(DioFile {
            file,
            dio_mem_align,
            dio_offset_align,
        })
    }

    pub fn dio_mem_align(&self) -> usize {
        self.dio_mem_align
    }

    pub fn dio_offset_align(&self) -> u64 {
        self.dio_offset_align
    }

    pub fn close(self) -> Close {
        self.file.close()
    }

    pub async fn file_size(&self) -> io::Result<u64> {
        self.file.file_size().await
    }

    pub fn sync_all(&self) -> SyncAll {
        self.file.sync_all()
    }

    fn assert_alignment(&self, buf: &[u8], offset: u64) {
        let iov = (offset, buf.len());
        assert_eq!(align_iov(self.dio_offset_align, iov), iov);
    }

    pub fn align_iov(&self, iov: (u64, usize)) -> (u64, usize) {
        align_iov(self.dio_offset_align(), iov)
    }

    pub fn read_aligned<'file, 'buf>(
        &'file self,
        buf: &'buf mut [u8],
        offset: u64,
    ) -> Read<'file, 'buf> {
        self.assert_alignment(buf, offset);

        Read {
            file: &self.file,
            offset,
            buf,
            io_id: None,
            direct_io: true,
            _non_send: PhantomData,
        }
    }

    pub fn write_aligned<'file, 'buf>(
        &'file self,
        buf: &'buf [u8],
        offset: u64,
    ) -> Write<'file, 'buf> {
        self.assert_alignment(buf, offset);

        Write {
            offset,
            buf,
            file: &self.file,
            io_id: None,
            direct_io: true,
            _non_send: PhantomData,
        }
    }
}

pub fn align_iov(dio_offset_align: u64, (offset, size): (u64, usize)) -> (u64, usize) {
    (
        align_down(offset, dio_offset_align),
        usize::try_from(align_up(u64::try_from(size).unwrap(), dio_offset_align)).unwrap(),
    )
}

pub fn align_up(v: u64, align: u64) -> u64 {
    (v + align - 1) & !(align - 1)
}

pub fn align_down(v: u64, align: u64) -> u64 {
    v & !(align - 1)
}

#[cfg(test)]
mod tests {
    use crate::{executor::ExecutorConfig, io_buffer::IoBuffer};
    use std::alloc::Layout;

    use super::*;

    #[test]
    fn smoke_test_dio_file() {
        let x = ExecutorConfig::new()
            .run(Box::pin(async {
                let file = DioFile::open(Path::new("Cargo.toml"), libc::O_RDONLY, 0)
                    .await
                    .unwrap();
                let size = file.file_size().await.unwrap();
                let size = usize::try_from(size).unwrap();
                let iov = align_iov(file.dio_offset_align(), (0, size));
                let mut buf = IoBuffer::<std::alloc::Global>::new(
                    Layout::from_size_align(iov.1, file.dio_mem_align()).unwrap(),
                    std::alloc::Global,
                )
                .unwrap();
                let start = std::time::Instant::now();
                let n_read = file.read_aligned(buf.as_mut_slice(), 0).await.unwrap();
                assert_eq!(n_read, size);
                println!("{}", std::str::from_utf8(buf.as_slice()).unwrap());
                println!("delay {}ns", start.elapsed().as_nanos());
                5
            }))
            .unwrap();

        assert_eq!(x, 5);
        dbg!(x);
    }
}