use std::fs;
use std::io::Write;
use std::path::Path;
use concinnity_core::blob::{
CACHE_SEGMENT_VERSION, CacheEntry, CacheEntryKind, CacheMeta, encode_cnb, parse_cnb,
};
struct Item {
kind: CacheEntryKind,
key: String,
bytes: Vec<u8>,
used: bool,
}
pub struct Segment {
items: Vec<Item>,
toolchain: String,
dirty: bool,
}
impl Segment {
pub fn read_from(path: &Path) -> Self {
let Ok(image) = fs::read(path) else {
return Self::empty();
};
let Ok((meta, payload_start)) = parse_cnb::<CacheMeta>(CACHE_SEGMENT_VERSION, &image)
else {
return Self::empty();
};
Self {
items: meta
.entries
.iter()
.filter_map(|entry| {
Some(Item {
kind: entry.kind,
key: entry.key.clone(),
bytes: entry_bytes(&image, payload_start, entry)?.to_vec(),
used: false,
})
})
.collect(),
toolchain: meta.toolchain,
dirty: false,
}
}
fn empty() -> Self {
Self {
items: Vec::new(),
toolchain: String::new(),
dirty: false,
}
}
pub fn get(&mut self, kind: CacheEntryKind, key: &str) -> Option<&[u8]> {
let item = self
.items
.iter_mut()
.find(|i| i.kind == kind && i.key == key)?;
item.used = true;
Some(&item.bytes)
}
pub fn put(&mut self, kind: CacheEntryKind, key: &str, bytes: &[u8]) -> bool {
if bytes.is_empty() {
return false;
}
match self
.items
.iter_mut()
.find(|i| i.kind == kind && i.key == key)
{
Some(item) if bytes.len() <= item.bytes.len() => {
item.used = true;
return false;
}
Some(item) => {
item.bytes.clear();
item.bytes.extend_from_slice(bytes);
item.used = true;
}
None => self.items.push(Item {
kind,
key: key.to_owned(),
bytes: bytes.to_vec(),
used: true,
}),
}
self.dirty = true;
true
}
pub(super) fn remove(&mut self, kind: CacheEntryKind, key: &str) {
let before = self.items.len();
self.items.retain(|i| !(i.kind == kind && i.key == key));
self.dirty |= self.items.len() != before;
}
pub(super) fn adopt_toolchain(&mut self, id: &str) -> bool {
if self.toolchain == id {
return false;
}
let discarded = !self.toolchain.is_empty() && !self.items.is_empty();
if discarded {
self.items.clear();
}
self.toolchain = id.to_owned();
self.dirty = true;
discarded
}
pub fn write_to(&mut self, path: &Path, budget: u64) -> bool {
if !self.dirty {
return false;
}
self.evict_to(budget);
if self.items.is_empty() {
let _ = fs::remove_file(path);
self.dirty = false;
return false;
}
let mut payload = Vec::with_capacity(self.items.iter().map(|i| i.bytes.len()).sum());
let entries = self
.items
.iter()
.map(|item| {
let entry = CacheEntry {
kind: item.kind,
key: item.key.clone(),
offset: payload.len() as u64,
len: item.bytes.len() as u64,
};
payload.extend_from_slice(&item.bytes);
entry
})
.collect();
let meta = CacheMeta {
toolchain: self.toolchain.clone(),
entries,
};
let Ok(image) = encode_cnb(CACHE_SEGMENT_VERSION, &meta, &payload) else {
return false;
};
if !crate::store::atomic::replace(path, |out| out.write_all(&image)) {
return false;
}
self.dirty = false;
true
}
fn evict_to(&mut self, budget: u64) {
let mut total: u64 = self.items.iter().map(|i| i.bytes.len() as u64).sum();
if total <= budget {
return;
}
let before = self.items.len();
self.items.retain(|item| {
if total <= budget || item.used {
return true;
}
total -= item.bytes.len() as u64;
false
});
self.dirty |= self.items.len() != before;
}
}
fn entry_bytes<'a>(image: &'a [u8], payload_start: usize, entry: &CacheEntry) -> Option<&'a [u8]> {
let start = payload_start.checked_add(usize::try_from(entry.offset).ok()?)?;
let end = start.checked_add(usize::try_from(entry.len).ok()?)?;
image.get(start..end)
}
#[cfg(test)]
mod tests {
use super::*;
const PIPELINE: CacheEntryKind = CacheEntryKind::Pipeline;
const SHADER: CacheEntryKind = CacheEntryKind::Shader;
const BUDGET: u64 = 1024;
fn segment_path(dir: &tempfile::TempDir) -> std::path::PathBuf {
dir.path().join("cache").join("0")
}
fn written(path: &Path, entries: &[(CacheEntryKind, &str, &[u8])]) -> Segment {
let mut segment = Segment::read_from(path);
for (kind, key, bytes) in entries {
segment.put(*kind, key, bytes);
}
segment.write_to(path, BUDGET);
segment
}
#[test]
fn an_entry_round_trips_through_a_segment() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
written(&path, &[(PIPELINE, "vk-aa", &[1, 2, 3])]);
let mut reread = Segment::read_from(&path);
assert_eq!(reread.get(PIPELINE, "vk-aa"), Some(&[1, 2, 3][..]));
let leftovers = fs::read_dir(path.parent().unwrap())
.unwrap()
.flatten()
.filter(|e| e.path().extension().is_some_and(|x| x == "tmp"))
.count();
assert_eq!(leftovers, 0, "temp files must not survive a write");
}
#[test]
fn one_entry_does_not_clobber_another() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
written(
&path,
&[
(PIPELINE, "vk-aa", &[1, 2, 3]),
(PIPELINE, "vk-bb", &[9]),
(SHADER, "vk-aa", &[4, 4]),
],
);
let mut reread = Segment::read_from(&path);
assert_eq!(reread.get(PIPELINE, "vk-aa"), Some(&[1, 2, 3][..]));
assert_eq!(reread.get(PIPELINE, "vk-bb"), Some(&[9][..]));
assert_eq!(reread.get(SHADER, "vk-aa"), Some(&[4, 4][..]));
reread.put(PIPELINE, "vk-aa", &[4, 5, 6, 7]);
reread.write_to(&path, BUDGET);
let mut last = Segment::read_from(&path);
assert_eq!(last.get(PIPELINE, "vk-aa"), Some(&[4, 5, 6, 7][..]));
assert_eq!(last.get(PIPELINE, "vk-bb"), Some(&[9][..]));
assert_eq!(last.get(SHADER, "vk-aa"), Some(&[4, 4][..]));
}
#[test]
fn a_segment_nothing_changed_is_not_rewritten() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
written(&path, &[(SHADER, "cafe", &[1, 2, 3])]);
let before = fs::metadata(&path).unwrap().len();
let mut warm = Segment::read_from(&path);
assert_eq!(warm.get(SHADER, "cafe"), Some(&[1, 2, 3][..]));
assert!(
!warm.write_to(&path, BUDGET),
"a read-only run writes nothing"
);
assert_eq!(fs::metadata(&path).unwrap().len(), before);
}
#[test]
fn only_growth_replaces_an_entry() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let mut segment = written(&path, &[(PIPELINE, "vk-aa", &[5, 5])]);
assert!(!segment.put(PIPELINE, "vk-aa", &[5, 5]), "unchanged");
assert!(!segment.put(PIPELINE, "vk-aa", &[6, 5]), "reshuffled");
assert!(!segment.put(PIPELINE, "vk-aa", &[5]), "shrunk");
assert!(!segment.put(PIPELINE, "vk-bb", &[]), "empty");
assert!(!segment.write_to(&path, BUDGET), "none of those is content");
assert!(segment.put(PIPELINE, "vk-aa", &[5, 5, 6]), "grew");
assert!(segment.write_to(&path, BUDGET));
let mut reread = Segment::read_from(&path);
assert_eq!(reread.get(PIPELINE, "vk-aa"), Some(&[5, 5, 6][..]));
}
#[test]
fn an_absent_segment_reads_as_a_miss_and_is_recreated() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
assert!(!path.exists());
assert_eq!(Segment::read_from(&path).get(PIPELINE, "vk-aa"), None);
written(&path, &[(PIPELINE, "vk-aa", &[1])]);
fs::remove_dir_all(path.parent().unwrap()).unwrap();
assert_eq!(Segment::read_from(&path).get(PIPELINE, "vk-aa"), None);
written(&path, &[(PIPELINE, "vk-aa", &[1])]);
assert_eq!(
Segment::read_from(&path).get(PIPELINE, "vk-aa"),
Some(&[1][..])
);
}
#[test]
fn a_missing_entry_reads_as_a_miss() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let mut segment = written(&path, &[(PIPELINE, "vk-aa", &[1])]);
assert_eq!(segment.get(PIPELINE, "vk-bb"), None);
}
#[test]
fn a_removed_entry_is_gone_and_the_last_one_takes_the_file() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let mut segment = written(
&path,
&[(PIPELINE, "vk-aa", &[1]), (PIPELINE, "vk-bb", &[2])],
);
segment.remove(PIPELINE, "vk-aa");
assert!(segment.write_to(&path, BUDGET));
let mut reread = Segment::read_from(&path);
assert_eq!(reread.get(PIPELINE, "vk-aa"), None);
assert_eq!(reread.get(PIPELINE, "vk-bb"), Some(&[2][..]));
reread.remove(PIPELINE, "vk-bb");
assert!(!reread.write_to(&path, BUDGET));
assert!(!path.exists(), "an empty segment leaves no file behind");
reread.remove(PIPELINE, "vk-bb");
assert!(!reread.write_to(&path, BUDGET));
assert!(!path.exists());
}
#[test]
fn a_corrupt_segment_reads_empty_and_is_replaced() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, b"not a segment at all").unwrap();
assert_eq!(Segment::read_from(&path).get(PIPELINE, "vk-aa"), None);
written(&path, &[(PIPELINE, "vk-aa", &[7])]);
assert_eq!(
Segment::read_from(&path).get(PIPELINE, "vk-aa"),
Some(&[7][..])
);
}
#[test]
fn an_entry_pointing_past_the_image_reads_as_a_miss() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
written(&path, &[(PIPELINE, "vk-aa", &[1, 2, 3])]);
let image = fs::read(&path).unwrap();
fs::write(&path, &image[..image.len() - 1]).unwrap();
assert_eq!(Segment::read_from(&path).get(PIPELINE, "vk-aa"), None);
}
#[test]
fn a_sibling_segment_is_left_alone() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let sibling = path.parent().unwrap().join("1");
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&sibling, b"build segment").unwrap();
let mut segment = written(&path, &[(PIPELINE, "vk-aa", &[1])]);
segment.remove(PIPELINE, "vk-aa");
segment.write_to(&path, BUDGET);
assert!(!path.exists());
assert_eq!(fs::read(&sibling).unwrap(), b"build segment");
}
#[test]
fn a_toolchain_change_discards_the_entries() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let mut segment = written(&path, &[(SHADER, "cafe", &[1, 2])]);
assert!(!segment.adopt_toolchain("slang 2026.1"));
assert!(segment.write_to(&path, BUDGET), "the stamp is a change");
let mut warm = Segment::read_from(&path);
assert!(!warm.adopt_toolchain("slang 2026.1"));
assert_eq!(warm.get(SHADER, "cafe"), Some(&[1, 2][..]));
assert!(!warm.write_to(&path, BUDGET));
let mut upgraded = Segment::read_from(&path);
assert!(upgraded.adopt_toolchain("slang 2026.2"), "discarded");
assert_eq!(upgraded.get(SHADER, "cafe"), None);
upgraded.put(SHADER, "f00d", &[3]);
upgraded.write_to(&path, BUDGET);
let mut reread = Segment::read_from(&path);
assert_eq!(reread.get(SHADER, "cafe"), None);
assert_eq!(reread.get(SHADER, "f00d"), Some(&[3][..]));
assert!(!reread.adopt_toolchain("slang 2026.2"), "stamp persisted");
}
#[test]
fn nothing_is_evicted_under_budget() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let mut segment = written(&path, &[(SHADER, "a", &[0; 8]), (SHADER, "b", &[0; 8])]);
segment.evict_to(64);
assert!(segment.get(SHADER, "a").is_some());
assert!(segment.get(SHADER, "b").is_some());
}
#[test]
fn eviction_drops_oldest_first_and_spares_what_this_run_used() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
written(
&path,
&[
(SHADER, "oldest", &[0; 40]),
(SHADER, "middle", &[0; 40]),
(SHADER, "newest", &[0; 40]),
],
);
let mut warm = Segment::read_from(&path);
assert!(warm.get(SHADER, "oldest").is_some(), "this run needs it");
warm.evict_to(80);
assert!(warm.get(SHADER, "oldest").is_some());
assert!(warm.get(SHADER, "middle").is_none());
assert!(warm.get(SHADER, "newest").is_some());
assert!(warm.write_to(&path, 1024));
assert!(Segment::read_from(&path).get(SHADER, "middle").is_none());
}
#[test]
fn eviction_stops_at_the_entries_this_run_used() {
let dir = tempfile::tempdir().unwrap();
let path = segment_path(&dir);
let mut segment = written(&path, &[(SHADER, "a", &[0; 40]), (SHADER, "b", &[0; 40])]);
segment.evict_to(0);
assert!(segment.get(SHADER, "a").is_some());
assert!(segment.get(SHADER, "b").is_some());
}
}