use std::path::PathBuf;
use std::sync::OnceLock;
#[cfg(test)]
use std::sync::{Mutex, MutexGuard};
const CUDA_CACHE_DISABLE: &str = "CUDA_CACHE_DISABLE";
const CUDA_CACHE_PATH: &str = "CUDA_CACHE_PATH";
const CUDA_CACHE_MAXSIZE: &str = "CUDA_CACHE_MAXSIZE";
const DEFAULT_MAX_BYTES: u64 = 1 * 1024 * 1024 * 1024;
static CONFIGURED: OnceLock<Result<(), String>> = OnceLock::new();
#[cfg(test)]
static TEST_ENV_LOCK: Mutex<()> = Mutex::new(());
#[cfg(test)]
fn lock_test_env() -> MutexGuard<'static, ()> {
TEST_ENV_LOCK
.lock()
.expect("Fix: replace expect with fallible API or document caller precondition; panic only on programmer error - CUDA JIT cache env test lock must not be poisoned")
}
pub fn configure_jit_cache_default() -> Result<(), String> {
#[cfg(test)]
let _env_guard = lock_test_env();
CONFIGURED
.get_or_init(|| {
let cache_root = default_cache_root()?;
configure_jit_cache_unlocked(cache_root, DEFAULT_MAX_BYTES)
})
.clone()
}
pub fn configure_jit_cache(cache_dir: PathBuf, max_bytes: u64) -> Result<(), String> {
#[cfg(test)]
let _env_guard = lock_test_env();
configure_jit_cache_unlocked(cache_dir, max_bytes)
}
fn configure_jit_cache_unlocked(cache_dir: PathBuf, max_bytes: u64) -> Result<(), String> {
if max_bytes == 0 {
return Err(
"CUDA JIT cache max size cannot be zero. Fix: configure a positive CUDA_CACHE_MAXSIZE; disabling cache capacity is a production performance regression."
.to_string(),
);
}
unsafe {
std::env::set_var(CUDA_CACHE_DISABLE, "0");
}
let configured_path = std::env::var_os(CUDA_CACHE_PATH).map(PathBuf::from);
let cache_dir = configured_path.unwrap_or(cache_dir);
if cache_dir.as_os_str().is_empty() {
return Err(
"CUDA_CACHE_PATH is empty. Fix: set CUDA_CACHE_PATH to a writable directory or leave it unset so Vyre can choose the XDG cache path."
.to_string(),
);
}
std::fs::create_dir_all(&cache_dir).map_err(|error| {
format!(
"failed to create CUDA JIT cache directory `{}`: {error}. Fix: set CUDA_CACHE_PATH to a writable directory before dispatch.",
cache_dir.display()
)
})?;
if std::env::var_os(CUDA_CACHE_PATH).is_none() {
unsafe {
std::env::set_var(CUDA_CACHE_PATH, &cache_dir);
}
}
match std::env::var_os(CUDA_CACHE_MAXSIZE) {
Some(raw) => {
let raw = raw.to_string_lossy();
let configured = raw.parse::<u64>().map_err(|error| {
format!(
"CUDA_CACHE_MAXSIZE is not a byte count: `{raw}` ({error}). Fix: set CUDA_CACHE_MAXSIZE to at least {max_bytes}."
)
})?;
if configured < max_bytes {
return Err(format!(
"CUDA_CACHE_MAXSIZE={configured} is below Vyre's required floor {max_bytes}. Fix: increase CUDA_CACHE_MAXSIZE; undersized JIT cache causes repeated kernel recompilation."
));
}
}
None => unsafe {
std::env::set_var(CUDA_CACHE_MAXSIZE, max_bytes.to_string());
},
}
Ok(())
}
fn default_cache_root() -> Result<PathBuf, String> {
if let Some(xdg) = std::env::var_os("XDG_CACHE_HOME") {
return Ok(PathBuf::from(xdg).join("vyre").join("cuda-jit"));
}
if let Some(home) = std::env::var_os("HOME") {
return Ok(PathBuf::from(home)
.join(".cache")
.join("vyre")
.join("cuda-jit"));
}
Err(
"CUDA JIT cache has no XDG_CACHE_HOME or HOME. Fix: configure a writable cache root; silent /tmp fallback hides production cache misconfiguration."
.to_string(),
)
}
#[cfg(test)]
mod tests {
use super::*;
struct EnvSnapshot {
disable: Option<std::ffi::OsString>,
path: Option<std::ffi::OsString>,
max_size: Option<std::ffi::OsString>,
xdg: Option<std::ffi::OsString>,
}
impl EnvSnapshot {
fn capture() -> Self {
Self {
disable: std::env::var_os(CUDA_CACHE_DISABLE),
path: std::env::var_os(CUDA_CACHE_PATH),
max_size: std::env::var_os(CUDA_CACHE_MAXSIZE),
xdg: std::env::var_os("XDG_CACHE_HOME"),
}
}
fn restore(self) {
restore_var(CUDA_CACHE_DISABLE, self.disable);
restore_var(CUDA_CACHE_PATH, self.path);
restore_var(CUDA_CACHE_MAXSIZE, self.max_size);
restore_var("XDG_CACHE_HOME", self.xdg);
}
}
fn restore_var(name: &str, value: Option<std::ffi::OsString>) {
unsafe {
match value {
Some(value) => std::env::set_var(name, value),
None => std::env::remove_var(name),
}
}
}
fn reset_env() {
unsafe {
std::env::remove_var(CUDA_CACHE_DISABLE);
std::env::remove_var(CUDA_CACHE_PATH);
std::env::remove_var(CUDA_CACHE_MAXSIZE);
}
}
#[test]
fn jit_cache_env_contract() {
let _env_guard = lock_test_env();
let snapshot = EnvSnapshot::capture();
reset_env();
let dir = std::env::temp_dir().join("vyre-jit-cache-test-1");
match std::fs::remove_dir_all(&dir) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => panic!(
"Fix: failed to remove stale CUDA JIT cache test directory `{}`: {error}",
dir.display()
),
}
configure_jit_cache_unlocked(dir.clone(), 12_345)
.expect("Fix: CUDA JIT cache test path should configure");
assert_eq!(std::env::var(CUDA_CACHE_DISABLE).unwrap(), "0");
assert_eq!(
std::env::var(CUDA_CACHE_PATH).unwrap(),
dir.to_string_lossy()
);
assert_eq!(std::env::var(CUDA_CACHE_MAXSIZE).unwrap(), "12345");
assert!(dir.is_dir(), "cache directory must be created");
reset_env();
unsafe {
std::env::set_var(CUDA_CACHE_DISABLE, "1");
}
let dir2 = std::env::temp_dir().join("vyre-jit-cache-test-2");
configure_jit_cache_unlocked(dir2, 1024)
.expect("Fix: CUDA JIT cache should force-enable driver cache");
assert_eq!(
std::env::var(CUDA_CACHE_DISABLE).unwrap(),
"0",
"Vyre must force-enable the CUDA JIT cache"
);
reset_env();
let custom = PathBuf::from("/tmp/operator-chosen-jit-path");
unsafe {
std::env::set_var(CUDA_CACHE_PATH, &custom);
}
let other = std::env::temp_dir().join("vyre-jit-cache-test-3");
configure_jit_cache_unlocked(other, 1024)
.expect("Fix: CUDA JIT cache should preserve operator path");
assert_eq!(
std::env::var(CUDA_CACHE_PATH).unwrap(),
custom.to_string_lossy()
);
unsafe {
std::env::set_var("XDG_CACHE_HOME", "/tmp/my-xdg-cache");
}
let root = default_cache_root().expect("Fix: XDG cache root should resolve");
assert_eq!(root, PathBuf::from("/tmp/my-xdg-cache/vyre/cuda-jit"));
snapshot.restore();
}
}