use anyhow::{Context, Result};
use dashmap::DashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use wasmtime::component::Component;
use wasmtime::{Config, Engine};
pub struct WasmCache {
engine: Engine,
components: DashMap<PathBuf, Arc<Component>>,
}
impl WasmCache {
pub fn new() -> Result<Self> {
let mut config = Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config)?;
Ok(Self {
engine,
components: DashMap::new(),
})
}
pub fn get_or_load(&self, path: &Path) -> Result<Arc<Component>> {
if let Some(component) = self.components.get(path) {
return Ok::<Arc<Component>, anyhow::Error>(component.clone());
}
let component = Component::from_file(&self.engine, path)
.with_context(|| format!("failed to load wasm decree: {}", path.display()))?;
let component = Arc::new(component);
self.components
.insert(path.to_path_buf(), component.clone());
tracing::info!("Cached WASM component: {}", path.display());
Ok(component)
}
pub fn clear(&self) {
self.components.clear();
tracing::info!("Cleared WASM component cache");
}
#[must_use]
pub fn stats(&self) -> WasmCacheStats {
WasmCacheStats {
entries: self.components.len(),
}
}
}
impl Default for WasmCache {
fn default() -> Self {
Self::new().expect("Failed to create WASM cache")
}
}
#[derive(Debug, Clone)]
pub struct WasmCacheStats {
pub entries: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_cache_miss_then_hit() {
let cache = WasmCache::new().unwrap();
let mut temp_file = NamedTempFile::new().unwrap();
writeln!(temp_file, "dummy wasm content").unwrap();
let path = temp_file.path();
let result = cache.get_or_load(path);
assert!(result.is_err());
let stats = cache.stats();
assert_eq!(stats.entries, 0); }
#[test]
fn test_cache_clear() {
let cache = WasmCache::new().unwrap();
cache.clear();
let stats = cache.stats();
assert_eq!(stats.entries, 0);
}
}