async-fd-lock 0.2.0

Advisory cross-platform file locks using file descriptors with async support by spawning blocking tasks.
Documentation
use std::io;

use cfg_if::cfg_if;

cfg_if! {
    if #[cfg(unix)] {
        mod unix;

        pub use rustix::fd::AsFd as AsOpenFile;
    } else if #[cfg(windows)] {
        mod windows;

        #[doc(no_inline)]
        pub use std::os::windows::io::AsHandle as AsOpenFile;
    }
}

pub(crate) trait AsOpenFileExt: AsOpenFile {
    type BorrowedOpenFile<'a>: AsOpenFile
    where
        Self: 'a;
    type OwnedOpenFile: AsOpenFile;

    cfg_if! {
        if #[cfg(feature = "async")] {
            fn borrow_open_file(&self) -> Self::BorrowedOpenFile<'_>;
        } else {
            #[allow(unused)]
            fn borrow_open_file(&self) -> Self::BorrowedOpenFile<'_>;
        }
    }

    fn acquire_lock_blocking<const WRITE: bool, const BLOCK: bool>(
        &self,
    ) -> io::Result<RwLockGuard<Self::OwnedOpenFile>>
    where
        Self: Sized;
    fn release_lock_blocking(&self) -> io::Result<()>;
}

#[must_use = "if unused the RwLock will immediately unlock"]
pub struct RwLockGuard<T: AsOpenFile> {
    handle: Option<<T as AsOpenFileExt>::OwnedOpenFile>,
}

impl<T: AsOpenFile> RwLockGuard<T> {
    pub fn new(handle: <T as AsOpenFileExt>::OwnedOpenFile) -> Self {
        Self {
            handle: Some(handle),
        }
    }

    pub fn defuse(mut self) -> <T as AsOpenFileExt>::OwnedOpenFile {
        self.handle.take().expect("handle should always be present")
    }

    pub fn defuse_with<R>(self, map: impl FnOnce(<T as AsOpenFileExt>::OwnedOpenFile) -> R) -> R {
        (map)(self.defuse())
    }
}

impl<T: AsOpenFile> Drop for RwLockGuard<T> {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            let _ = handle.release_lock_blocking();
        }
    }
}