dictator_core/
wasm_cache.rs

1//! WASM module caching for improved performance
2
3use anyhow::{Context, Result};
4use dashmap::DashMap;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7use wasmtime::component::Component;
8use wasmtime::{Config, Engine};
9
10/// Cache for compiled WASM components
11pub struct WasmCache {
12    engine: Engine,
13    components: DashMap<PathBuf, Arc<Component>>,
14}
15
16impl WasmCache {
17    /// Create a new WASM cache with component model enabled
18    pub fn new() -> Result<Self> {
19        let mut config = Config::new();
20        config.wasm_component_model(true);
21        let engine = Engine::new(&config)?;
22
23        Ok(Self {
24            engine,
25            components: DashMap::new(),
26        })
27    }
28
29    /// Get a compiled component from cache or load and compile it
30    pub fn get_or_load(&self, path: &Path) -> Result<Arc<Component>> {
31        // Check if component is already cached
32        if let Some(component) = self.components.get(path) {
33            return Ok::<Arc<Component>, anyhow::Error>(component.clone());
34        }
35
36        // Load and compile the component
37        let component = Component::from_file(&self.engine, path)
38            .with_context(|| format!("failed to load wasm decree: {}", path.display()))?;
39
40        let component = Arc::new(component);
41
42        // Cache the compiled component
43        self.components
44            .insert(path.to_path_buf(), component.clone());
45
46        tracing::info!("Cached WASM component: {}", path.display());
47        Ok(component)
48    }
49
50    /// Clear the cache
51    pub fn clear(&self) {
52        self.components.clear();
53        tracing::info!("Cleared WASM component cache");
54    }
55
56    /// Get cache statistics
57    #[must_use]
58    pub fn stats(&self) -> WasmCacheStats {
59        WasmCacheStats {
60            entries: self.components.len(),
61        }
62    }
63}
64
65impl Default for WasmCache {
66    fn default() -> Self {
67        Self::new().expect("Failed to create WASM cache")
68    }
69}
70
71/// Statistics about the WASM cache
72#[derive(Debug, Clone)]
73pub struct WasmCacheStats {
74    pub entries: usize,
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use std::io::Write;
81    use tempfile::NamedTempFile;
82
83    #[test]
84    fn test_cache_miss_then_hit() {
85        let cache = WasmCache::new().unwrap();
86
87        // Create a dummy WASM file (not valid WASM, but tests the cache logic)
88        let mut temp_file = NamedTempFile::new().unwrap();
89        writeln!(temp_file, "dummy wasm content").unwrap();
90        let path = temp_file.path();
91
92        // First call should miss and try to load (will fail due to invalid WASM)
93        let result = cache.get_or_load(path);
94        assert!(result.is_err());
95
96        // Verify cache stats
97        let stats = cache.stats();
98        assert_eq!(stats.entries, 0); // Should be 0 because loading failed
99    }
100
101    #[test]
102    fn test_cache_clear() {
103        let cache = WasmCache::new().unwrap();
104
105        // Clear empty cache should work
106        cache.clear();
107        let stats = cache.stats();
108        assert_eq!(stats.entries, 0);
109    }
110}