mircuda 0.1.0

Native, explicit-stream Rust gateway to NVIDIA CUDA
use std::{
    fs,
    io::{self, Write},
    path::{Path, PathBuf},
    sync::atomic::{AtomicU64, Ordering},
};

use super::CompileKey;

static TEMPORARY_SEQUENCE: AtomicU64 = AtomicU64::new(0);

#[derive(Debug)]
pub(super) struct ArtifactCache {
    directory: PathBuf,
}

impl ArtifactCache {
    pub(super) const fn new(directory: PathBuf) -> Self {
        Self { directory }
    }

    pub(super) fn read(&self, key: CompileKey) -> io::Result<Option<Vec<u8>>> {
        match fs::read(self.path(key)) {
            Ok(bytes) => Ok(Some(bytes)),
            Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
            Err(error) => Err(error),
        }
    }

    pub(super) fn write(&self, key: CompileKey, bytes: &[u8]) -> io::Result<()> {
        fs::create_dir_all(&self.directory)?;
        let target = self.path(key);
        let temporary = temporary_path(&target);
        let mut file = fs::File::create(&temporary)?;
        file.write_all(bytes)?;
        file.sync_all()?;
        match fs::rename(&temporary, &target) {
            Ok(()) => Ok(()),
            Err(_error) if target.exists() => {
                fs::remove_file(temporary)?;
                Ok(())
            },
            Err(error) => Err(error),
        }
    }

    pub(super) fn remove(&self, key: CompileKey) -> io::Result<()> {
        match fs::remove_file(self.path(key)) {
            Ok(()) => Ok(()),
            Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
            Err(error) => Err(error),
        }
    }

    fn path(&self, key: CompileKey) -> PathBuf {
        self.directory.join(key.file_name())
    }
}

fn temporary_path(target: &Path) -> PathBuf {
    let mut name = target.as_os_str().to_owned();
    let sequence = TEMPORARY_SEQUENCE.fetch_add(1, Ordering::Relaxed);
    name.push(format!(".{}.{sequence}.tmp", std::process::id()));
    PathBuf::from(name)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn absent_artifact_is_a_cache_miss() -> io::Result<()> {
        let directory = std::env::temp_dir().join(format!("mircuda-cache-{}", std::process::id()));
        let cache = ArtifactCache::new(directory.clone());
        let key = CompileKey([7; 32]);
        assert_eq!(cache.read(key)?, None);
        cache.write(key, b"ptx\0")?;
        assert_eq!(cache.read(key)?, Some(b"ptx\0".to_vec()));
        cache.remove(key)?;
        fs::remove_dir_all(directory)?;
        Ok(())
    }
}