use std::collections::HashMap;
use std::fs::File;
use std::io::Write as IoWrite;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::time::{SystemTime, UNIX_EPOCH};
use memmap2::Mmap;
use parking_lot::Mutex;
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
use std::os::unix::fs::MetadataExt;
pub const SHARD_MAGIC: u32 = 0x5A525343;
pub const SHARD_FORMAT_VERSION: u32 = 5;
#[derive(Archive, RkyvDeserialize, RkyvSerialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct ShardHeader {
pub magic: u32,
pub format_version: u32,
pub zshrs_version: String,
pub pointer_width: u32,
pub built_at_secs: u64,
}
#[derive(Archive, RkyvDeserialize, RkyvSerialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct ScriptEntry {
pub mtime_secs: i64,
pub mtime_nsecs: i64,
pub binary_mtime_at_cache: i64,
pub binary_len_at_cache: u64,
pub cached_at_secs: i64,
pub env_fingerprint: u64,
pub chunk_blob: Vec<u8>,
}
#[derive(Archive, RkyvDeserialize, RkyvSerialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct ScriptShard {
pub header: ShardHeader,
pub entries: HashMap<String, ScriptEntry>,
}
pub struct MmappedShard {
_mmap: Mmap,
archived: *const ArchivedScriptShard,
identity: (u64, u64, i64),
}
unsafe impl Send for MmappedShard {}
unsafe impl Sync for MmappedShard {}
impl MmappedShard {
pub fn open(path: &Path) -> Option<Self> {
let file = File::open(path).ok()?;
let identity = file_identity(&file.metadata().ok()?);
let mmap = unsafe { Mmap::map(&file).ok()? };
let archived = rkyv::check_archived_root::<ScriptShard>(&mmap[..]).ok()?;
let archived_ptr = archived as *const ArchivedScriptShard;
Some(Self {
_mmap: mmap,
archived: archived_ptr,
identity,
})
}
fn shard(&self) -> &ArchivedScriptShard {
unsafe { &*self.archived }
}
fn header_ok(&self) -> bool {
let h = &self.shard().header;
let magic: u32 = h.magic.into();
let fv: u32 = h.format_version.into();
let pw: u32 = h.pointer_width.into();
magic == SHARD_MAGIC
&& fv == SHARD_FORMAT_VERSION
&& pw as usize == std::mem::size_of::<usize>()
&& h.zshrs_version.as_str() == env!("CARGO_PKG_VERSION")
}
fn lookup(&self, path: &str) -> Option<&ArchivedScriptEntry> {
self.shard().entries.get(path)
}
fn entry_count(&self) -> usize {
self.shard().entries.len()
}
}
#[derive(Clone)]
pub struct PendingPut {
path: String,
mtime_secs: i64,
mtime_nsecs: i64,
env_fingerprint: u64,
blob: Vec<u8>,
}
static PENDING: Mutex<Vec<PendingPut>> = Mutex::new(Vec::new());
static PENDING_PID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
static RAN: Mutex<Vec<(String, i64)>> = Mutex::new(Vec::new());
static RAN_PID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
const RUNS_COMPACT_BYTES: u64 = 64 * 1024;
const PENDING_FLUSH_MAX: usize = 128;
pub struct ScriptCache {
path: PathBuf,
lock_path: PathBuf,
mmap: Mutex<Option<MmappedShard>>,
}
impl ScriptCache {
pub fn open(path: &Path) -> std::io::Result<Self> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let parent = path.parent().unwrap_or_else(|| Path::new("/tmp"));
let lock_path = parent.join(format!(
"{}.lock",
path.file_name()
.and_then(|s| s.to_str())
.unwrap_or("scripts.rkyv")
));
Ok(Self {
path: path.to_path_buf(),
lock_path,
mmap: Mutex::new(None),
})
}
fn ensure_mmap(&self) {
let mut guard = self.mmap.lock();
let on_disk = std::fs::metadata(&self.path).ok().map(|m| file_identity(&m));
let stale = match (guard.as_ref(), on_disk) {
(Some(m), Some(id)) => m.identity != id,
(Some(_), None) => true,
(None, _) => false,
};
if stale {
*guard = None;
}
if guard.is_none() {
*guard = MmappedShard::open(&self.path);
}
}
fn invalidate_mmap(&self) {
let mut guard = self.mmap.lock();
*guard = None;
}
pub fn get(
&self,
path: &str,
mtime_secs: i64,
mtime_nsecs: i64,
env_fingerprint: u64,
) -> Option<Vec<u8>> {
self.ensure_mmap();
let guard = self.mmap.lock();
let shard = guard.as_ref()?;
if !shard.header_ok() {
return None;
}
let entry = shard.lookup(path)?;
let entry_mtime_s: i64 = entry.mtime_secs.into();
let entry_mtime_ns: i64 = entry.mtime_nsecs.into();
if entry_mtime_s != mtime_secs || entry_mtime_ns != mtime_nsecs {
return None;
}
let entry_fp: u64 = entry.env_fingerprint.into();
if entry_fp != env_fingerprint {
return None;
}
match current_binary_identity() {
Some((bin_mtime, bin_len)) => {
let cached_bin_mtime: i64 = entry.binary_mtime_at_cache.into();
let cached_bin_len: u64 = entry.binary_len_at_cache.into();
if cached_bin_mtime != bin_mtime || cached_bin_len != bin_len {
return None;
}
}
None => return None,
}
Some(entry.chunk_blob.as_slice().to_vec())
}
pub fn put(
&self,
path: &str,
mtime_secs: i64,
mtime_nsecs: i64,
env_fingerprint: u64,
chunk_blob: Vec<u8>,
) -> Result<(), String> {
let _lock = match acquire_lock(&self.lock_path) {
Some(l) => l,
None => return Ok(()),
};
let mut shard = match read_owned_shard(&self.path) {
Some(s)
if s.header.zshrs_version == env!("CARGO_PKG_VERSION")
&& s.header.pointer_width as usize == std::mem::size_of::<usize>()
&& s.header.format_version == SHARD_FORMAT_VERSION =>
{
s
}
_ => fresh_shard(),
};
self.fold_runs(&mut shard);
let (bin_mtime, bin_len) = current_binary_identity().unwrap_or((0, 0));
let entry = ScriptEntry {
mtime_secs,
mtime_nsecs,
binary_mtime_at_cache: bin_mtime,
binary_len_at_cache: bin_len,
cached_at_secs: now_secs(),
env_fingerprint,
chunk_blob,
};
shard.entries.insert(path.to_string(), entry);
shard.header.built_at_secs = now_secs() as u64;
write_shard_atomic(&self.path, &shard)?;
let _ = std::fs::remove_file(self.runs_path());
self.invalidate_mmap();
Ok(())
}
pub fn put_many(&self, batch: Vec<PendingPut>) -> Result<(), String> {
if batch.is_empty() {
return Ok(());
}
let _lock = match acquire_lock(&self.lock_path) {
Some(l) => l,
None => return Ok(()),
};
let mut shard = match read_owned_shard(&self.path) {
Some(s)
if s.header.zshrs_version == env!("CARGO_PKG_VERSION")
&& s.header.pointer_width as usize == std::mem::size_of::<usize>()
&& s.header.format_version == SHARD_FORMAT_VERSION =>
{
s
}
_ => fresh_shard(),
};
self.fold_runs(&mut shard);
let (bin_mtime, bin_len) = current_binary_identity().unwrap_or((0, 0));
for p in batch {
shard.entries.insert(
p.path,
ScriptEntry {
mtime_secs: p.mtime_secs,
mtime_nsecs: p.mtime_nsecs,
binary_mtime_at_cache: bin_mtime,
binary_len_at_cache: bin_len,
cached_at_secs: now_secs(),
env_fingerprint: p.env_fingerprint,
chunk_blob: p.blob,
},
);
}
shard.header.built_at_secs = now_secs() as u64;
write_shard_atomic(&self.path, &shard)?;
let _ = std::fs::remove_file(self.runs_path());
self.invalidate_mmap();
Ok(())
}
pub fn stats(&self) -> (i64, i64) {
self.ensure_mmap();
let guard = self.mmap.lock();
let (mut count, mut bytes) = guard.as_ref().map_or((0, 0), |shard| {
let bytes: i64 = shard
.shard()
.entries
.values()
.map(|e| e.chunk_blob.len() as i64)
.sum();
(shard.entry_count() as i64, bytes)
});
let mut seen = std::collections::HashSet::new();
for p in PENDING.lock().iter() {
let in_shard = guard
.as_ref()
.is_some_and(|s| s.shard().entries.get(p.path.as_str()).is_some());
if !in_shard && seen.insert(p.path.clone()) {
count += 1;
bytes += p.blob.len() as i64;
}
}
(count, bytes)
}
pub fn list_scripts(&self) -> Vec<(String, f64, String, String)> {
self.ensure_mmap();
let guard = self.mmap.lock();
let v = guard
.as_ref()
.map(|s| s.shard().header.zshrs_version.as_str().to_string())
.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
let mut out: Vec<(String, f64, String, String, i64)> = guard
.as_ref()
.map(|shard| {
shard
.shard()
.entries
.iter()
.map(|(k, e)| {
let chunk_kb = e.chunk_blob.len() as f64 / 1024.0;
let cached_at: i64 = e.cached_at_secs.into();
let ts = format_local_ts(cached_at);
(k.as_str().to_string(), chunk_kb, v.clone(), ts, cached_at)
})
.collect()
})
.unwrap_or_default();
self.overlay_runs(&mut out);
let now = now_secs();
for p in PENDING.lock().iter() {
out.retain(|row| row.0 != p.path);
out.push((
p.path.clone(),
p.blob.len() as f64 / 1024.0,
v.clone(),
format_local_ts(now),
now,
));
}
out.sort_by_key(|x| std::cmp::Reverse(x.4));
out.into_iter()
.map(|(p, ck, ver, ts, _)| (p, ck, ver, ts))
.collect()
}
fn runs_path(&self) -> PathBuf {
self.path.with_file_name("scripts.runs")
}
fn overlay_runs(&self, rows: &mut [(String, f64, String, String, i64)]) {
let mut ran = read_runs(&self.runs_path());
for (path, ts) in RAN.lock().iter() {
let t = ran.entry(path.clone()).or_insert(*ts);
*t = (*t).max(*ts);
}
for row in rows.iter_mut() {
if let Some(&ts) = ran.get(&row.0).filter(|&&ts| ts > row.4) {
row.3 = format_local_ts(ts);
row.4 = ts;
}
}
}
fn fold_runs(&self, shard: &mut ScriptShard) {
for (path, ts) in read_runs(&self.runs_path()) {
if let Some(e) = shard.entries.get_mut(&path) {
e.cached_at_secs = e.cached_at_secs.max(ts);
}
}
}
fn mirror_path(&self) -> PathBuf {
self.path.with_file_name("scripts.db")
}
fn refresh_mirror(&self, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
use rusqlite::OptionalExtension;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS scripts (
path TEXT PRIMARY KEY,
kind TEXT NOT NULL,
events INTEGER NOT NULL,
bytes INTEGER NOT NULL,
version TEXT NOT NULL,
cached_at INTEGER NOT NULL,
source_mtime_secs INTEGER NOT NULL,
source_mtime_nsecs INTEGER NOT NULL,
env_fingerprint TEXT NOT NULL,
binary_len INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS scripts_cached_at ON scripts(cached_at DESC);",
)?;
let on_disk = std::fs::metadata(&self.path).ok().map(|m| {
let (ino, len, mtime_ns) = file_identity(&m);
format!("{ino}:{len}:{mtime_ns}")
});
let recorded: Option<String> = conn
.query_row("SELECT value FROM meta WHERE key = 'shard'", [], |r| r.get(0))
.optional()?;
if recorded == on_disk {
return Ok(());
}
let tx = conn.unchecked_transaction()?;
tx.execute("DELETE FROM scripts", [])?;
if let Some(shard) = read_owned_shard(&self.path) {
let mut insert = tx.prepare(
"INSERT OR REPLACE INTO scripts
(path, kind, events, bytes, version, cached_at,
source_mtime_secs, source_mtime_nsecs, env_fingerprint, binary_len)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
)?;
for (path, e) in &shard.entries {
let (kind, events) = match e.chunk_blob.first() {
Some(1) if e.chunk_blob.len() >= 9 => {
let n = u64::from_le_bytes(e.chunk_blob[1..9].try_into().unwrap());
("events", n as i64)
}
Some(0) => ("script", 1),
_ => ("unknown", 0),
};
insert.execute(rusqlite::params![
path,
kind,
events,
e.chunk_blob.len() as i64,
shard.header.zshrs_version,
e.cached_at_secs,
e.mtime_secs,
e.mtime_nsecs,
format!("{:016x}", e.env_fingerprint),
e.binary_len_at_cache as i64,
])?;
}
}
match on_disk {
Some(id) => tx.execute("INSERT OR REPLACE INTO meta (key, value) VALUES ('shard', ?1)", [id])?,
None => tx.execute("DELETE FROM meta WHERE key = 'shard'", [])?,
};
tx.commit()
}
pub fn mirror_rows(&self, pattern: &str) -> Option<Vec<(String, f64, String, String)>> {
let _lowfd = crate::lowfd::LowFdGuard::new();
let conn = rusqlite::Connection::open(self.mirror_path()).ok()?;
conn.busy_timeout(std::time::Duration::from_secs(5)).ok()?;
let _ = conn.pragma_update(None, "journal_mode", "WAL");
crate::lowfd::register_internal_fds();
self.refresh_mirror(&conn).ok()?;
let mut stmt = conn
.prepare(
"SELECT path, bytes, version, cached_at FROM scripts
WHERE instr(path, ?1) > 0 ORDER BY cached_at DESC, path",
)
.ok()?;
let mut out: Vec<(String, f64, String, String, i64)> = stmt
.query_map([pattern], |r| {
let path: String = r.get(0)?;
let bytes: i64 = r.get(1)?;
let version: String = r.get(2)?;
let cached_at: i64 = r.get(3)?;
Ok((path, bytes as f64 / 1024.0, version, format_local_ts(cached_at), cached_at))
})
.ok()?
.filter_map(Result::ok)
.collect();
self.overlay_runs(&mut out);
let now = now_secs();
for p in PENDING.lock().iter().filter(|p| p.path.contains(pattern)) {
out.retain(|row| row.0 != p.path);
out.push((
p.path.clone(),
p.blob.len() as f64 / 1024.0,
env!("CARGO_PKG_VERSION").to_string(),
format_local_ts(now),
now,
));
}
out.sort_by_key(|x| std::cmp::Reverse(x.4));
Some(out.into_iter().map(|(p, kb, v, ts, _)| (p, kb, v, ts)).collect())
}
pub fn evict_stale(&self) -> usize {
let _lock = match acquire_lock(&self.lock_path) {
Some(l) => l,
None => return 0,
};
let mut shard = match read_owned_shard(&self.path) {
Some(s) => s,
None => return 0,
};
let before = shard.entries.len();
shard.entries.retain(|p, e| match file_mtime(Path::new(p)) {
Some((s, ns)) => s == e.mtime_secs && ns == e.mtime_nsecs,
None => false,
});
let evicted = before - shard.entries.len();
if evicted > 0 {
let _ = write_shard_atomic(&self.path, &shard);
self.invalidate_mmap();
}
evicted
}
pub fn clear(&self) -> std::io::Result<()> {
let _lock = acquire_lock(&self.lock_path);
let res = match std::fs::remove_file(&self.path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
};
let _ = std::fs::remove_file(self.runs_path());
self.invalidate_mmap();
res
}
}
fn file_identity(m: &std::fs::Metadata) -> (u64, u64, i64) {
(m.ino(), m.len(), m.mtime() * 1_000_000_000 + m.mtime_nsec())
}
fn acquire_lock(path: &Path) -> Option<nix::fcntl::Flock<File>> {
let f = File::options()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path)
.ok()?;
nix::fcntl::Flock::lock(f, nix::fcntl::FlockArg::LockExclusive).ok()
}
fn fresh_shard() -> ScriptShard {
ScriptShard {
header: ShardHeader {
magic: SHARD_MAGIC,
format_version: SHARD_FORMAT_VERSION,
zshrs_version: env!("CARGO_PKG_VERSION").to_string(),
pointer_width: std::mem::size_of::<usize>() as u32,
built_at_secs: now_secs() as u64,
},
entries: HashMap::new(),
}
}
fn read_owned_shard(path: &Path) -> Option<ScriptShard> {
let bytes = std::fs::read(path).ok()?;
let archived = rkyv::check_archived_root::<ScriptShard>(&bytes[..]).ok()?;
archived.deserialize(&mut rkyv::Infallible).ok()
}
fn write_shard_atomic(path: &Path, shard: &ScriptShard) -> Result<(), String> {
let bytes = rkyv::to_bytes::<_, 4096>(shard).map_err(|e| format!("rkyv serialize: {}", e))?;
crate::atomic_write::write_bytes_atomic(path, &bytes)
}
fn now_secs() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
fn format_local_ts(secs: i64) -> String {
let dt = chrono::DateTime::<chrono::Local>::from(
UNIX_EPOCH + std::time::Duration::from_secs(secs.max(0) as u64),
);
dt.format("%Y-%m-%d %H:%M:%S").to_string()
}
pub fn file_mtime(path: &Path) -> Option<(i64, i64)> {
let meta = std::fs::metadata(path).ok()?;
Some((meta.mtime(), meta.mtime_nsec()))
}
fn current_binary_identity() -> Option<(i64, u64)> {
static BIN_ID: OnceLock<Option<(i64, u64)>> = OnceLock::new();
*BIN_ID.get_or_init(|| {
let exe = std::env::current_exe().ok()?;
let meta = std::fs::metadata(&exe).ok()?;
Some((meta.mtime(), meta.len()))
})
}
pub fn default_cache_path() -> PathBuf {
let root = if let Some(custom) = std::env::var_os("ZSHRS_HOME") {
PathBuf::from(custom)
} else {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".zshrs")
};
root.join("scripts.rkyv")
}
pub static CACHE_DISABLED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn cache_enabled() -> bool {
if CACHE_DISABLED.load(std::sync::atomic::Ordering::Relaxed) {
return false;
}
!matches!(
std::env::var("ZSHRS_CACHE").as_deref(),
Ok("0") | Ok("false") | Ok("no")
)
}
pub static CACHE: once_cell::sync::Lazy<Option<ScriptCache>> = once_cell::sync::Lazy::new(|| {
if !cache_enabled() {
return None;
}
ScriptCache::open(&default_cache_path()).ok()
});
struct Capture {
path: PathBuf,
env_fp: u64,
alias_base: u64,
toplevel: bool,
pid: u32,
events: Vec<GuardedEvent>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct GuardedEvent {
pub chunk: fusevm::Chunk,
pub alias_delta: u64,
pub start: u64,
}
thread_local! {
static CAPTURES: std::cell::RefCell<Vec<Capture>> = const { std::cell::RefCell::new(Vec::new()) };
}
pub fn capture_begin(path: &Path, env_fp: u64, toplevel: bool) {
let cap = Capture {
path: path.canonicalize().unwrap_or_else(|_| path.to_path_buf()),
env_fp,
alias_base: env_generation().0,
toplevel,
pid: std::process::id(),
events: Vec::new(),
};
CAPTURES.with(|c| c.borrow_mut().push(cap));
}
pub fn capture_push(chunk: &fusevm::Chunk, start: usize) {
let now = env_generation().0;
CAPTURES.with(|c| {
if let Some(top) = c.borrow_mut().last_mut() {
top.events.push(GuardedEvent {
chunk: chunk.clone(),
alias_delta: now.wrapping_sub(top.alias_base),
start: start as u64,
});
}
});
}
pub fn capture_commit(complete: bool) {
let Some(cap) = CAPTURES.with(|c| c.borrow_mut().pop()) else {
return;
};
store_capture(cap, complete);
}
pub fn capture_commit_all_on_exit(errored: bool) {
let caps: Vec<Capture> = CAPTURES.with(|c| std::mem::take(&mut *c.borrow_mut()));
for cap in caps.into_iter().rev() {
store_capture(cap, !errored);
}
}
fn store_capture(cap: Capture, complete: bool) {
if !complete || cap.events.is_empty() || cap.pid != std::process::id() {
return;
}
let _ = cap.toplevel;
if let Ok(blob) = bincode::serialize(&cap.events) {
let _ = try_save_bytes(&cap.path, BlobKind::Guarded, cap.env_fp, &blob);
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BlobKind {
Script = 0,
Events = 1,
Guarded = 2,
}
pub fn env_fingerprint() -> u64 {
use std::sync::atomic::Ordering;
static MEMO: Mutex<Option<(u64, u64, u64, u64)>> = Mutex::new(None);
let alias_gen = crate::ported::hashtable::ALIAS_GEN.load(Ordering::Relaxed);
let opt_gen = crate::opts_cache::generation();
let mut memo = MEMO.lock();
if let Some((ag, og, alias_hash, opt_hash)) = *memo {
if ag == alias_gen && og == opt_gen {
return fnv_mix(alias_hash, opt_hash);
}
}
let alias_hash = hash_alias_tables();
let opt_hash = hash_option_store();
*memo = Some((alias_gen, opt_gen, alias_hash, opt_hash));
fnv_mix(alias_hash, opt_hash)
}
pub fn env_generation() -> (u64, u64) {
(
crate::ported::hashtable::ALIAS_GEN.load(std::sync::atomic::Ordering::Relaxed),
crate::opts_cache::generation(),
)
}
fn fnv_mix(a: u64, b: u64) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for byte in a.to_le_bytes().into_iter().chain(b.to_le_bytes()) {
h ^= u64::from(byte);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn fnv_bytes(mut h: u64, bytes: &[u8]) -> u64 {
for &byte in bytes {
h ^= u64::from(byte);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h ^= 0xff;
h.wrapping_mul(0x0000_0100_0000_01b3)
}
fn hash_alias_tables() -> u64 {
use crate::ported::hashtable::{aliastab_lock, sufaliastab_lock};
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for table in [aliastab_lock(), sufaliastab_lock()] {
let Ok(t) = table.read() else { continue };
let mut rows: Vec<(&String, &crate::ported::zsh_h::alias)> = t.iter().collect();
rows.sort_by(|a, b| a.0.cmp(b.0));
for (name, a) in rows {
h = fnv_bytes(h, name.as_bytes());
h = fnv_bytes(h, a.text.as_bytes());
h = fnv_bytes(h, &a.node.flags.to_le_bytes());
}
h = fnv_bytes(h, b"|");
}
h
}
fn hash_option_store() -> u64 {
let mut rows: Vec<(String, bool)> = crate::ported::options::opt_state_snapshot()
.into_iter()
.collect();
rows.sort();
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for (name, on) in rows {
h = fnv_bytes(h, name.as_bytes());
h = fnv_bytes(h, &[on as u8]);
}
let emul = crate::ported::options::emulation.load(std::sync::atomic::Ordering::Relaxed);
fnv_bytes(h, &emul.to_le_bytes())
}
fn read_runs(path: &Path) -> HashMap<String, i64> {
let mut out = HashMap::new();
let text = std::fs::read(path).map(|b| String::from_utf8_lossy(&b).into_owned()).unwrap_or_default();
for line in text.lines() {
let Some((ts, p)) = line.split_once('\t') else { continue };
let Ok(ts) = ts.parse::<i64>() else { continue };
let t = out.entry(p.to_string()).or_insert(ts);
*t = (*t).max(ts);
}
out
}
fn append_runs(path: &Path, batch: &[(String, i64)]) {
let lines: String = batch.iter().map(|(p, ts)| format!("{ts}\t{p}\n")).collect();
let appended = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.and_then(|mut f| f.write_all(lines.as_bytes()));
if appended.is_err() || std::fs::metadata(path).map_or(0, |m| m.len()) < RUNS_COMPACT_BYTES {
return;
}
let deduped: String = read_runs(path).into_iter().map(|(p, ts)| format!("{ts}\t{p}\n")).collect();
let _ = crate::atomic_write::write_bytes_atomic(path, deduped.as_bytes());
}
pub fn try_load_bytes(path: &Path, kind: BlobKind, env_fp: u64) -> Option<Vec<u8>> {
let cache = CACHE.as_ref()?;
let canonical = path.canonicalize().ok()?;
let path_str = canonical.to_string_lossy();
let (mtime_s, mtime_ns) = file_mtime(&canonical)?;
let buffered = PENDING.lock().iter().rev().find_map(|p| {
(p.path == path_str && p.mtime_secs == mtime_s && p.mtime_nsecs == mtime_ns && p.env_fingerprint == env_fp)
.then(|| p.blob.clone())
});
let blob = match buffered {
Some(b) => b,
None => cache.get(&path_str, mtime_s, mtime_ns, env_fp)?,
};
let (tag, body) = blob.split_first()?;
if *tag != kind as u8 {
return None;
}
RAN_PID.store(std::process::id(), std::sync::atomic::Ordering::Relaxed);
RAN.lock().push((path_str.into_owned(), now_secs()));
Some(body.to_vec())
}
pub fn try_save_bytes(
path: &Path,
kind: BlobKind,
env_fp: u64,
chunk_blob: &[u8],
) -> Result<(), String> {
let Some(cache) = CACHE.as_ref() else {
return Ok(());
};
let canonical = match path.canonicalize() {
Ok(p) => p,
Err(_) => return Ok(()),
};
let path_str = canonical.to_string_lossy();
let (mtime_s, mtime_ns) = match file_mtime(&canonical) {
Some(m) => m,
None => return Ok(()),
};
let mut tagged = Vec::with_capacity(chunk_blob.len() + 1);
tagged.push(kind as u8);
tagged.extend_from_slice(chunk_blob);
PENDING_PID.store(std::process::id(), std::sync::atomic::Ordering::Relaxed);
let over = {
let mut pending = PENDING.lock();
pending.push(PendingPut {
path: path_str.into_owned(),
mtime_secs: mtime_s,
mtime_nsecs: mtime_ns,
env_fingerprint: env_fp,
blob: tagged,
});
pending.len() >= PENDING_FLUSH_MAX
};
if over {
try_flush_pending();
}
Ok(())
}
pub fn try_flush_pending() {
if RAN_PID.load(std::sync::atomic::Ordering::Relaxed) == std::process::id() {
let ran = std::mem::take(&mut *RAN.lock());
if let (false, Some(cache)) = (ran.is_empty(), CACHE.as_ref()) {
append_runs(&cache.runs_path(), &ran);
}
}
if PENDING_PID.load(std::sync::atomic::Ordering::Relaxed) != std::process::id() {
return;
}
let batch = std::mem::take(&mut *PENDING.lock());
if batch.is_empty() {
return;
}
if let Some(cache) = CACHE.as_ref() {
let _ = cache.put_many(batch);
}
}
pub fn stats() -> Option<(i64, i64)> {
CACHE.as_ref().map(|c| c.stats())
}
pub fn evict_stale() -> usize {
CACHE.as_ref().map(|c| c.evict_stale()).unwrap_or(0)
}
pub fn clear() -> bool {
CACHE.as_ref().map(|c| c.clear().is_ok()).unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn round_trip() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
let cache = ScriptCache::open(&cache_path).unwrap();
let script_path = dir.path().join("test.zsh");
std::fs::write(&script_path, "echo hi").unwrap();
let (mtime_s, mtime_ns) = file_mtime(&script_path).unwrap();
let path_str = script_path.to_string_lossy().to_string();
let blob = vec![1u8, 2, 3, 4, 5];
cache
.put(&path_str, mtime_s, mtime_ns, 0, blob.clone())
.unwrap();
let loaded = cache.get(&path_str, mtime_s, mtime_ns, 0).unwrap();
assert_eq!(loaded, blob);
let (count, _bytes) = cache.stats();
assert_eq!(count, 1);
}
#[test]
fn a_write_from_another_process_shows_up_in_a_mapped_reader() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
let reader = ScriptCache::open(&cache_path).unwrap();
let writer = ScriptCache::open(&cache_path).unwrap();
let first = dir.path().join("first.zsh");
let second = dir.path().join("second.zsh");
std::fs::write(&first, "echo 1").unwrap();
std::fs::write(&second, "echo 2").unwrap();
let (s1, n1) = file_mtime(&first).unwrap();
let (s2, n2) = file_mtime(&second).unwrap();
writer.put(&first.to_string_lossy(), s1, n1, 0, vec![1]).unwrap();
assert_eq!(reader.list_scripts().len(), 1, "reader maps the shard");
writer.put(&second.to_string_lossy(), s2, n2, 0, vec![2]).unwrap();
let rows = reader.list_scripts();
assert_eq!(rows.len(), 2, "the replaced shard must be remapped: {rows:?}");
assert_eq!(reader.stats().0, 2);
}
#[test]
fn the_sqlite_mirror_follows_the_shard() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache = ScriptCache::open(&dir.path().join("scripts.rkyv")).unwrap();
let one = dir.path().join("one.zsh");
let two = dir.path().join("two.zsh");
std::fs::write(&one, "echo 1").unwrap();
std::fs::write(&two, "echo 2").unwrap();
let (s1, n1) = file_mtime(&one).unwrap();
let (s2, n2) = file_mtime(&two).unwrap();
assert_eq!(cache.mirror_rows("").unwrap().len(), 0, "empty before any write");
cache.put(&one.to_string_lossy(), s1, n1, 0, vec![1u8]).unwrap();
let rows = cache.mirror_rows("").unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].0.ends_with("one.zsh"));
assert!(dir.path().join("scripts.db").is_file());
cache.put(&two.to_string_lossy(), s2, n2, 0, vec![2u8; 4096]).unwrap();
assert_eq!(cache.mirror_rows("").unwrap().len(), 2);
let only_two = cache.mirror_rows("two.zsh").unwrap();
assert_eq!(only_two.len(), 1);
assert!(only_two[0].1 > 3.9, "kb is the blob size: {only_two:?}");
cache.clear().unwrap();
assert_eq!(cache.mirror_rows("").unwrap().len(), 0, "deleted shard empties the mirror");
}
#[test]
fn a_cache_hit_moves_the_listed_time_and_survives_a_rewrite() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache = ScriptCache::open(&dir.path().join("scripts.rkyv")).unwrap();
let one = dir.path().join("one.zsh");
let two = dir.path().join("two.zsh");
std::fs::write(&one, "echo 1").unwrap();
std::fs::write(&two, "echo 2").unwrap();
let (s1, n1) = file_mtime(&one).unwrap();
let (s2, n2) = file_mtime(&two).unwrap();
let one_key = one.to_string_lossy().to_string();
cache.put(&one_key, s1, n1, 0, vec![1u8]).unwrap();
cache.put(&two.to_string_lossy(), s2, n2, 0, vec![2u8]).unwrap();
let ran_at = now_secs() + 1000;
append_runs(&cache.runs_path(), &[(one_key.clone(), ran_at)]);
let by_path = |rows: Vec<(String, f64, String, String)>| {
rows.into_iter().map(|r| (r.0, r.3)).collect::<Vec<_>>()
};
let listed = by_path(cache.list_scripts());
assert!(listed[0].0.ends_with("one.zsh"), "newest run first: {listed:?}");
assert_eq!(listed[0].1, format_local_ts(ran_at));
let mirrored = by_path(cache.mirror_rows("").unwrap());
assert_eq!(mirrored[0], listed[0]);
let three = dir.path().join("three.zsh");
std::fs::write(&three, "echo 3").unwrap();
let (s3, n3) = file_mtime(&three).unwrap();
cache.put(&three.to_string_lossy(), s3, n3, 0, vec![3u8]).unwrap();
assert!(!cache.runs_path().exists());
let after = by_path(cache.list_scripts());
assert!(after[0].0.ends_with("one.zsh"), "{after:?}");
assert_eq!(after[0].1, format_local_ts(ran_at));
}
#[test]
fn mtime_invalidation() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
let cache = ScriptCache::open(&cache_path).unwrap();
let script_path = dir.path().join("test.zsh");
std::fs::write(&script_path, "echo hi").unwrap();
let (mtime_s, mtime_ns) = file_mtime(&script_path).unwrap();
let path_str = script_path.to_string_lossy().to_string();
cache.put(&path_str, mtime_s, mtime_ns, 0, vec![9u8]).unwrap();
assert!(cache.get(&path_str, mtime_s + 1, mtime_ns, 0).is_none());
}
#[test]
fn an_entry_from_another_binary_is_never_served() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
let cache = ScriptCache::open(&cache_path).unwrap();
let script_path = dir.path().join("test.zsh");
std::fs::write(&script_path, "echo hi").unwrap();
let (mtime_s, mtime_ns) = file_mtime(&script_path).unwrap();
let path_str = script_path.to_string_lossy().to_string();
cache.put(&path_str, mtime_s, mtime_ns, 0, vec![9u8]).unwrap();
assert_eq!(
cache.get(&path_str, mtime_s, mtime_ns, 0),
Some(vec![9u8]),
"the emitting binary must hit its own entry",
);
let mut shard = read_owned_shard(&cache_path).expect("shard readable");
shard
.entries
.get_mut(&path_str)
.expect("entry present")
.binary_mtime_at_cache += 10_000;
write_shard_atomic(&cache_path, &shard).unwrap();
let reopened = ScriptCache::open(&cache_path).unwrap();
assert!(
reopened.get(&path_str, mtime_s, mtime_ns, 0).is_none(),
"a chunk from a newer build was accepted",
);
let mut shard = read_owned_shard(&cache_path).expect("shard readable");
let entry = shard.entries.get_mut(&path_str).expect("entry present");
entry.binary_mtime_at_cache -= 10_000;
entry.binary_len_at_cache += 1;
write_shard_atomic(&cache_path, &shard).unwrap();
let reopened = ScriptCache::open(&cache_path).unwrap();
assert!(
reopened.get(&path_str, mtime_s, mtime_ns, 0).is_none(),
"a chunk from a same-second build of a different size was accepted",
);
}
#[test]
fn second_put_replaces_first() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
let cache = ScriptCache::open(&cache_path).unwrap();
let p1 = dir.path().join("a.zsh");
let p2 = dir.path().join("b.zsh");
std::fs::write(&p1, "1").unwrap();
std::fs::write(&p2, "2").unwrap();
let (m1s, m1n) = file_mtime(&p1).unwrap();
let (m2s, m2n) = file_mtime(&p2).unwrap();
cache
.put(&p1.to_string_lossy(), m1s, m1n, 0, vec![1u8])
.unwrap();
cache
.put(&p2.to_string_lossy(), m2s, m2n, 0, vec![2u8])
.unwrap();
let (count, _) = cache.stats();
assert_eq!(count, 2);
assert!(cache.get(&p1.to_string_lossy(), m1s, m1n, 0).is_some());
assert!(cache.get(&p2.to_string_lossy(), m2s, m2n, 0).is_some());
}
#[test]
fn corrupt_file_returns_no_mmap() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
std::fs::write(&cache_path, b"this is not a valid rkyv archive").unwrap();
let cache = ScriptCache::open(&cache_path).unwrap();
assert!(cache.get("/nope", 0, 0, 0).is_none());
}
#[test]
fn clear_removes_file() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let cache_path = dir.path().join("scripts.rkyv");
let cache = ScriptCache::open(&cache_path).unwrap();
let script_path = dir.path().join("test.zsh");
std::fs::write(&script_path, "echo hi").unwrap();
let (mtime_s, mtime_ns) = file_mtime(&script_path).unwrap();
cache
.put(&script_path.to_string_lossy(), mtime_s, mtime_ns, 0, vec![7u8])
.unwrap();
assert!(cache_path.exists());
cache.clear().unwrap();
assert!(!cache_path.exists());
}
#[test]
fn now_secs_is_positive_and_within_realistic_range() {
let _g = crate::test_util::global_state_lock();
let n = now_secs();
assert!(
(1_577_836_800..4_102_444_800).contains(&n),
"now_secs out of plausible range: {}",
n
);
}
#[test]
fn now_secs_does_not_go_backwards_in_quick_succession() {
let _g = crate::test_util::global_state_lock();
let a = now_secs();
let b = now_secs();
assert!(b >= a, "now_secs went backwards: {} -> {}", a, b);
}
#[test]
fn format_local_ts_includes_year_and_punctuation() {
let _g = crate::test_util::global_state_lock();
let s = format_local_ts(1_704_067_200);
assert!(s.starts_with("202"), "expected 21st century year: {}", s);
assert!(s.contains('-'), "expected dash separator: {}", s);
assert!(s.contains(':'), "expected colon separator: {}", s);
}
#[test]
fn format_local_ts_length_matches_pattern() {
let _g = crate::test_util::global_state_lock();
let s = format_local_ts(1_700_000_000);
assert_eq!(s.len(), 19, "unexpected width: {}", s);
}
#[test]
fn format_local_ts_handles_zero_secs_via_clamp() {
let _g = crate::test_util::global_state_lock();
let s = format_local_ts(0);
assert_eq!(s.len(), 19);
assert!(s.starts_with("19"), "expected 1970-ish year: {}", s);
}
#[test]
fn format_local_ts_negative_clamped_to_zero() {
let _g = crate::test_util::global_state_lock();
let s = format_local_ts(-1_000_000);
assert_eq!(s.len(), 19);
}
#[test]
fn file_mtime_returns_some_for_real_file() {
let _g = crate::test_util::global_state_lock();
let dir = tempdir().unwrap();
let p = dir.path().join("foo.zsh");
std::fs::write(&p, b"x").unwrap();
let (s, _ns) = file_mtime(&p).unwrap();
assert!(s > 0);
}
#[test]
fn file_mtime_returns_none_for_missing_path() {
let _g = crate::test_util::global_state_lock();
assert!(file_mtime(Path::new("/nonexistent/zshrs/script_cache_missing.bin")).is_none());
}
#[test]
fn default_cache_path_ends_in_scripts_rkyv() {
let _g = crate::test_util::global_state_lock();
let p = default_cache_path();
assert_eq!(p.file_name().and_then(|s| s.to_str()), Some("scripts.rkyv"));
}
#[test]
fn cache_enabled_true_when_env_unset() {
let _g = crate::test_util::global_state_lock();
let prev = std::env::var_os("ZSHRS_CACHE");
std::env::remove_var("ZSHRS_CACHE");
let on = cache_enabled();
if let Some(v) = prev {
std::env::set_var("ZSHRS_CACHE", v);
}
assert!(on, "cache should be enabled when ZSHRS_CACHE is unset");
}
#[test]
fn cache_enabled_false_when_env_is_zero_false_or_no() {
let _g = crate::test_util::global_state_lock();
let prev = std::env::var_os("ZSHRS_CACHE");
for v in ["0", "false", "no"] {
std::env::set_var("ZSHRS_CACHE", v);
assert!(!cache_enabled(), "ZSHRS_CACHE={} must disable cache", v);
}
if let Some(v) = prev {
std::env::set_var("ZSHRS_CACHE", v);
} else {
std::env::remove_var("ZSHRS_CACHE");
}
}
#[test]
fn cache_enabled_true_for_other_env_values() {
let _g = crate::test_util::global_state_lock();
let prev = std::env::var_os("ZSHRS_CACHE");
for v in ["1", "true", "yes", "on", ""] {
std::env::set_var("ZSHRS_CACHE", v);
assert!(
cache_enabled(),
"ZSHRS_CACHE={:?} must NOT disable cache",
v
);
}
if let Some(v) = prev {
std::env::set_var("ZSHRS_CACHE", v);
} else {
std::env::remove_var("ZSHRS_CACHE");
}
}
}