use std::{fs, path::Path};
const CACHE_ATTRIBUTION_FILE: &str = ".omena-cache-owner.json";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PersistentCacheLimitsV0 {
pub(crate) max_shards: usize,
pub(crate) max_total_bytes: u64,
pub(crate) max_shard_bytes: u64,
}
pub(crate) const DEFAULT_PERSISTENT_CACHE_LIMITS: PersistentCacheLimitsV0 =
PersistentCacheLimitsV0 {
max_shards: 4096,
max_total_bytes: 256 * 1024 * 1024,
max_shard_bytes: 8 * 1024 * 1024,
};
pub(crate) fn read_cache_shard_with_limits(
path: &Path,
limits: &PersistentCacheLimitsV0,
) -> Option<Vec<u8>> {
let metadata = fs::metadata(path).ok()?;
if !metadata.is_file() || metadata.len() > limits.max_shard_bytes {
let _ = fs::remove_file(path);
return None;
}
fs::read(path).ok()
}
pub(crate) fn write_cache_shard_atomically_with_limits(
path: &Path,
bytes: &[u8],
limits: &PersistentCacheLimitsV0,
) -> bool {
if bytes.len() as u64 > limits.max_shard_bytes {
return false;
}
let Some(dir) = path.parent() else {
return false;
};
if fs::create_dir_all(dir).is_err() {
return false;
}
let temporary_path = path.with_extension(format!("tmp-{}", std::process::id()));
if fs::write(temporary_path.as_path(), bytes).is_err() {
return false;
}
let renamed = fs::rename(temporary_path.as_path(), path).is_ok();
if !renamed {
let _ = fs::remove_file(temporary_path);
}
if renamed {
enforce_cache_limits(dir, limits);
}
renamed
}
fn enforce_cache_limits(dir: &Path, limits: &PersistentCacheLimitsV0) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
let mut shards = entries
.flatten()
.filter_map(|entry| {
let path = entry.path();
if path.extension().and_then(|extension| extension.to_str()) != Some("json") {
return None;
}
let metadata = entry.metadata().ok()?;
if !metadata.is_file() {
return None;
}
let modified = metadata.modified().ok()?;
Some((modified, metadata.len(), path))
})
.collect::<Vec<_>>();
shards.sort_by(|left, right| left.0.cmp(&right.0).then_with(|| left.2.cmp(&right.2)));
let mut shard_count = shards.len();
let mut total_bytes = shards.iter().map(|(_, bytes, _)| *bytes).sum::<u64>();
for (_, bytes, path) in shards {
if shard_count <= limits.max_shards && total_bytes <= limits.max_total_bytes {
break;
}
if fs::remove_file(path).is_ok() {
shard_count = shard_count.saturating_sub(1);
total_bytes = total_bytes.saturating_sub(bytes);
}
}
}
pub(crate) fn ensure_cache_root_attribution(cache_subdir: &Path, workspace_identity: &str) {
let Some(cache_root) = cache_subdir.parent() else {
return;
};
let attribution_path = cache_root.join(CACHE_ATTRIBUTION_FILE);
let value = serde_json::json!({
"schemaVersion": "0",
"product": "omena.cache-root-attribution",
"workspaceIdentity": workspace_identity,
});
let Ok(bytes) = serde_json::to_vec(&value) else {
return;
};
let _ = fs::write(attribution_path, bytes);
}
#[cfg(test)]
pub(crate) fn assert_real_cache_store_enforces_reachable_limits(
cache_name: &str,
write: impl Fn(&Path, &[u8], &PersistentCacheLimitsV0) -> bool,
read: impl Fn(&Path, &PersistentCacheLimitsV0) -> Option<Vec<u8>>,
) {
use std::time::{SystemTime, UNIX_EPOCH};
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let editor_storage_root = std::env::temp_dir().join(format!(
"omena-cache-limits-{}-{}-{nonce}",
cache_name.replace('/', "-"),
std::process::id()
));
let cache_storage = crate::cache_root::LspCacheStorageConfigV0 {
initialization_global_storage: Some(editor_storage_root.join("global")),
initialization_workspace_storage: Some(editor_storage_root.join("workspace")),
log_path: Some(editor_storage_root.join("logs")),
command_cache_dir: None,
location: crate::cache_root::CacheLocationV0::Editor,
};
let resolved_root = crate::cache_root::resolved_workspace_cache_dir(
&cache_storage,
"file:///workspace",
Path::new("/workspace"),
cache_name,
);
assert!(resolved_root.is_some(), "editor cache root must resolve");
let Some(root) = resolved_root else {
return;
};
assert!(
root.starts_with(editor_storage_root.join("workspace")),
"{cache_name}: cap exercise must run under the resolved editor root: {root:?}"
);
let count_dir = root.join("count");
let count_limits = PersistentCacheLimitsV0 {
max_shards: 2,
max_total_bytes: 1024,
max_shard_bytes: 128,
};
let count_victim = count_dir.join("00-victim.json");
let count_survivor = count_dir.join("10-survivor.json");
let count_newest = count_dir.join("20-newest.json");
assert!(write(&count_victim, &[b'v'; 8], &count_limits));
assert!(write(&count_survivor, &[b's'; 8], &count_limits));
assert!(write(&count_newest, &[b'n'; 8], &count_limits));
assert!(
!count_victim.exists(),
"{cache_name}: count victim must be evicted"
);
assert!(count_survivor.exists(), "{cache_name}: survivor missing");
assert!(count_newest.exists(), "{cache_name}: newest missing");
let bytes_dir = root.join("bytes");
let byte_limits = PersistentCacheLimitsV0 {
max_shards: 10,
max_total_bytes: 16,
max_shard_bytes: 128,
};
let byte_victim = bytes_dir.join("00-victim.json");
let byte_survivor = bytes_dir.join("10-survivor.json");
let byte_newest = bytes_dir.join("20-newest.json");
assert!(write(&byte_victim, &[b'v'; 8], &byte_limits));
assert!(write(&byte_survivor, &[b's'; 8], &byte_limits));
assert!(write(&byte_newest, &[b'n'; 8], &byte_limits));
assert!(
!byte_victim.exists(),
"{cache_name}: byte victim must be evicted"
);
assert!(
byte_survivor.exists(),
"{cache_name}: byte survivor missing"
);
assert!(byte_newest.exists(), "{cache_name}: byte newest missing");
let oversize_limits = PersistentCacheLimitsV0 {
max_shards: 10,
max_total_bytes: 1024,
max_shard_bytes: 48,
};
let oversize_dir = root.join("oversize");
let oversize = oversize_dir.join("oversize.json");
assert!(
!write(&oversize, &[b'x'; 49], &oversize_limits),
"{cache_name}: oversize write must be refused"
);
assert!(
!oversize.exists(),
"{cache_name}: oversize shard was written"
);
assert!(fs::create_dir_all(oversize_dir).is_ok(), "oversize dir");
assert!(
fs::write(oversize.as_path(), [b'x'; 49]).is_ok(),
"oversize fixture"
);
assert!(
read(oversize.as_path(), &oversize_limits).is_none(),
"{cache_name}: oversize shard must not be served"
);
assert!(
!oversize.exists(),
"{cache_name}: refused oversize shard must be removed"
);
eprintln!(
"cacheLimit cache={cache_name} root={} countVictimExists={} countSurvivors=2 byteVictimExists={} byteSurvivors=2 oversizeExists={}",
root.display(),
count_victim.exists(),
byte_victim.exists(),
oversize.exists(),
);
let _ = fs::remove_dir_all(editor_storage_root);
}
#[cfg(test)]
pub(crate) fn assert_production_store_enforces_default_count_and_total(
cache_name: &str,
cache_dir: &Path,
new_shard_path: &Path,
mut store: impl FnMut(),
) {
assert_eq!(new_shard_path.parent(), Some(cache_dir));
let _ = fs::remove_dir_all(cache_dir);
assert!(fs::create_dir_all(cache_dir).is_ok());
for ordinal in 0..DEFAULT_PERSISTENT_CACHE_LIMITS.max_shards {
assert!(
fs::write(cache_dir.join(format!("fixture-{ordinal:04}.json")), b"{}").is_ok(),
"{cache_name}: default count fixture {ordinal}"
);
}
store();
assert!(
new_shard_path.is_file(),
"{cache_name}: production store must write under the default count cap"
);
let count_after_store = fs::read_dir(cache_dir)
.into_iter()
.flatten()
.flatten()
.filter(|entry| entry.path().extension().and_then(|value| value.to_str()) == Some("json"))
.count();
assert_eq!(
count_after_store, DEFAULT_PERSISTENT_CACHE_LIMITS.max_shards,
"{cache_name}: production store must enforce the default shard-count cap"
);
let _ = fs::remove_dir_all(cache_dir);
assert!(fs::create_dir_all(cache_dir).is_ok());
let total_victim = cache_dir.join("00-total-victim.json");
let total_fixture = fs::File::create(total_victim.as_path());
assert!(total_fixture.is_ok(), "{cache_name}: total-byte fixture");
if let Ok(total_fixture) = total_fixture {
assert!(
total_fixture
.set_len(DEFAULT_PERSISTENT_CACHE_LIMITS.max_total_bytes)
.is_ok(),
"{cache_name}: sparse total-byte fixture"
);
}
store();
assert!(
new_shard_path.is_file(),
"{cache_name}: production store must retain the newest shard under total-byte pressure"
);
let total_bytes_after_store = fs::read_dir(cache_dir)
.into_iter()
.flatten()
.flatten()
.filter_map(|entry| entry.metadata().ok().map(|metadata| metadata.len()))
.sum::<u64>();
assert!(
total_bytes_after_store <= DEFAULT_PERSISTENT_CACHE_LIMITS.max_total_bytes,
"{cache_name}: production store must enforce the default total-byte cap"
);
assert!(
!total_victim.exists(),
"{cache_name}: the named sparse total-byte victim must be evicted"
);
eprintln!(
"defaultStoreCaps cache={cache_name} countAfterStore={count_after_store} maxShards={} totalBytesAfterStore={total_bytes_after_store} maxTotalBytes={} totalVictimExists={}",
DEFAULT_PERSISTENT_CACHE_LIMITS.max_shards,
DEFAULT_PERSISTENT_CACHE_LIMITS.max_total_bytes,
total_victim.exists(),
);
let _ = fs::remove_dir_all(cache_dir);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_root_attribution_stamp_names_the_workspace_owner()
-> Result<(), Box<dyn std::error::Error>> {
let root =
std::env::temp_dir().join(format!("omena-cache-attribution-{}", std::process::id()));
let cache_dir = root.join("source-document-index-v1");
assert!(fs::create_dir_all(cache_dir.as_path()).is_ok());
ensure_cache_root_attribution(cache_dir.as_path(), "file:///workspace");
let bytes = fs::read(root.join(CACHE_ATTRIBUTION_FILE))?;
let value = serde_json::from_slice::<serde_json::Value>(bytes.as_slice())?;
assert_eq!(
value
.pointer("/workspaceIdentity")
.and_then(serde_json::Value::as_str),
Some("file:///workspace")
);
let _ = fs::remove_dir_all(root);
Ok(())
}
#[test]
fn synthetic_document_corpus_justifies_persistent_cache_limits()
-> Result<(), Box<dyn std::error::Error>> {
const DOCUMENT_COUNT: usize = 8;
let products = [
"source-document-index",
"workspace-occurrence-shard",
"source-type-fact-sidecar",
];
for product in products {
let mut sizes = Vec::new();
for document_ordinal in 1..=DOCUMENT_COUNT {
let repeated_entries = (0..document_ordinal)
.map(|entry_ordinal| {
serde_json::json!({
"documentUri": format!("file:///workspace/src/{document_ordinal}.tsx"),
"entryOrdinal": entry_ordinal,
"selector": format!("component-{document_ordinal}-{entry_ordinal}"),
"range": {"start": entry_ordinal * 8, "end": entry_ordinal * 8 + 7},
})
})
.collect::<Vec<_>>();
let shard = serde_json::json!({
"schemaVersion": "1",
"product": product,
"workspaceFolderUri": "file:///workspace",
"documentOrdinal": document_ordinal,
"payload": {"entries": repeated_entries},
});
let bytes = serde_json::to_vec(&shard)?;
sizes.push(bytes.len() as u64);
}
let min = sizes.iter().copied().min().unwrap_or(0);
let max = sizes.iter().copied().max().unwrap_or(0);
let total = sizes.iter().sum::<u64>();
println!(
"syntheticCorpus product={product} documents={DOCUMENT_COUNT} minBytes={min} maxBytes={max} totalBytes={total}"
);
assert!(max <= DEFAULT_PERSISTENT_CACHE_LIMITS.max_shard_bytes);
assert!(
max <= DEFAULT_PERSISTENT_CACHE_LIMITS.max_total_bytes
/ DEFAULT_PERSISTENT_CACHE_LIMITS.max_shards as u64,
"{product}: the synthetic maximum must fit the per-entry average budget at the count cap"
);
}
Ok(())
}
}