Skip to main content

a3s_box_core/traits/
cache.rs

1//! Cache backend abstraction.
2//!
3//! Decouples rootfs and layer caching from the filesystem-based LRU
4//! implementation in `a3s-box-runtime`. Implementations can use any
5//! storage backend: local filesystem, shared NFS, Redis, content-addressable
6//! store, etc.
7//!
8//! The cache operates on opaque string keys and directory-shaped values
9//! (a `PathBuf` pointing to a directory tree). The backend is responsible
10//! for storage, retrieval, and eviction.
11
12use std::path::{Path, PathBuf};
13
14use serde::{Deserialize, Serialize};
15
16use crate::error::Result;
17
18/// Metadata about a single cache entry.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct CacheEntry {
21    /// Cache key.
22    pub key: String,
23
24    /// Human-readable description of the cached content.
25    pub description: String,
26
27    /// Size of the cached content in bytes.
28    pub size_bytes: u64,
29
30    /// When this entry was first cached (Unix timestamp).
31    pub cached_at: i64,
32
33    /// When this entry was last accessed (Unix timestamp).
34    pub last_accessed: i64,
35}
36
37/// Statistics about the cache as a whole.
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39pub struct CacheStats {
40    /// Number of entries in the cache.
41    pub entry_count: usize,
42
43    /// Total size of all cached content in bytes.
44    pub total_bytes: u64,
45}
46
47/// Abstraction over directory-based caching.
48///
49/// Used for both OCI layer caching and fully-built rootfs caching.
50/// The key is an opaque string (typically a content hash), and the
51/// value is a directory tree on disk.
52///
53/// # Lifecycle
54///
55/// 1. `get(key)` — check if a cached directory exists for this key
56/// 2. On miss: build the content, then `put(key, source_dir, desc)`
57/// 3. `prune(max_entries, max_bytes)` — evict old entries to stay within limits
58///
59/// # Thread Safety
60///
61/// Implementations must be `Send + Sync`. Concurrent `get`/`put` calls
62/// with different keys must be safe. Concurrent calls with the same key
63/// have implementation-defined behavior (last writer wins is acceptable).
64pub trait CacheBackend: Send + Sync {
65    /// Retrieve a cached directory by key.
66    ///
67    /// Returns `Some(path)` if the key exists and the cached content is valid.
68    /// The returned path points to a directory that the caller can read from.
69    /// Returns `None` on cache miss.
70    ///
71    /// Implementations should update the last-accessed timestamp on hit.
72    fn get(&self, key: &str) -> Result<Option<PathBuf>>;
73
74    /// Store a directory tree in the cache under the given key.
75    ///
76    /// Copies (or moves) the contents of `source_dir` into the cache.
77    /// If an entry with this key already exists, it is replaced.
78    ///
79    /// Returns the path to the cached directory (which may differ from `source_dir`).
80    fn put(&self, key: &str, source_dir: &Path, description: &str) -> Result<PathBuf>;
81
82    /// Remove a cached entry by key.
83    ///
84    /// No-op if the key does not exist.
85    fn invalidate(&self, key: &str) -> Result<()>;
86
87    /// Evict entries to satisfy the given constraints.
88    ///
89    /// Implementations should evict least-recently-accessed entries first.
90    /// Returns the number of entries evicted.
91    fn prune(&self, max_entries: usize, max_bytes: u64) -> Result<usize>;
92
93    /// List all cache entries with their metadata.
94    fn list(&self) -> Result<Vec<CacheEntry>>;
95
96    /// Get aggregate cache statistics.
97    fn stats(&self) -> Result<CacheStats> {
98        let entries = self.list()?;
99        Ok(CacheStats {
100            entry_count: entries.len(),
101            total_bytes: entries.iter().map(|e| e.size_bytes).sum(),
102        })
103    }
104}