use std::{
fmt::{self, Display},
io::ErrorKind,
mem,
sync::{Mutex, OnceLock},
};
use ruff_db::{
files::{File, system_path_to_file},
system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPathBuf},
};
use crate::db::{MemoryDb, SRC_ROOT};
const MAX_POOLED_DBS: usize = 8;
struct MemoryDbPool {
dbs: Mutex<Vec<MemoryDb>>,
}
impl MemoryDbPool {
fn global() -> &'static Self {
static GLOBAL_POOL: OnceLock<MemoryDbPool> = OnceLock::new();
GLOBAL_POOL.get_or_init(|| Self {
dbs: Mutex::new(Vec::new()),
})
}
fn checkout(&'static self) -> Result<PooledMemoryDb, String> {
let maybe_db = {
let mut dbs = self.dbs.lock().map_err(to_string)?;
dbs.pop()
};
Ok(PooledMemoryDb {
db: Some(maybe_db.unwrap_or_default()),
pool: self,
touched_files: Vec::new(),
})
}
fn release(&self, db: MemoryDb) -> Result<(), String> {
let mut dbs = self.dbs.lock().map_err(to_string)?;
if dbs.len() < MAX_POOLED_DBS {
dbs.push(db);
}
Ok(())
}
}
pub(crate) struct PooledMemoryDb {
db: Option<MemoryDb>,
pool: &'static MemoryDbPool,
touched_files: Vec<TouchedRootFile>,
}
impl fmt::Debug for PooledMemoryDb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PooledMemoryDb")
.field("touched_files", &self.touched_files.len())
.finish_non_exhaustive()
}
}
impl PooledMemoryDb {
pub(crate) fn checkout() -> Result<Self, String> {
MemoryDbPool::global().checkout()
}
pub(crate) fn write_root_file(&mut self, path: &SystemPathBuf, source: &str) -> Result<File, String> {
let db = self.db_mut();
db.write_file(path, source).map_err(to_string)?;
let file = system_path_to_file(db, path)
.unwrap_or_else(|e| panic!("interning a just-written root file must succeed, DB in an unsafe state: {e}"));
self.touched_files.push(TouchedRootFile::new(path.clone(), file));
Ok(file)
}
pub(crate) fn rewrite_root_file(&mut self, path: &SystemPathBuf, source: &str) -> Result<(), String> {
assert!(
self.touched_files.iter().any(|t| &t.path == path),
"rewrite_root_file called for untracked path '{path}' — must call write_root_file first",
);
self.db_mut().write_file(path, source).map_err(to_string)
}
pub(crate) fn db(&self) -> &MemoryDb {
self.db.as_ref().expect("db is only None inside Drop")
}
fn db_mut(&mut self) -> &mut MemoryDb {
self.db.as_mut().expect("db is only None inside Drop")
}
}
impl Drop for PooledMemoryDb {
fn drop(&mut self) {
let Some(mut db) = self.db.take() else { return };
let touched_files = mem::take(&mut self.touched_files);
cleanup_touched_files(&mut db, &touched_files)
.unwrap_or_else(|err| panic!("monty type-check pool: failed to scrub db on drop: {err}"));
self.pool
.release(db)
.unwrap_or_else(|err| panic!("monty type-check pool: failed to release db on drop: {err}"));
}
}
struct TouchedRootFile {
path: SystemPathBuf,
file: File,
}
impl TouchedRootFile {
fn new(path: SystemPathBuf, file: File) -> Self {
Self { path, file }
}
fn cleanup(&self, db: &mut MemoryDb) -> Result<(), String> {
match db.memory_file_system().remove_file(&self.path) {
Ok(()) => {}
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => {
return Err(format!(
"Failed to remove pooled type-check file '{}': {err}",
self.path
));
}
}
self.file.sync(db);
let src_root = SystemPathBuf::from(SRC_ROOT);
let mut ancestor = self.path.parent();
while let Some(dir) = ancestor
&& dir != src_root.as_path()
{
match db.memory_file_system().remove_directory(dir) {
Ok(()) => {}
Err(err) if err.to_string().contains("directory not empty") => break,
Err(err) => {
return Err(format!("Failed to remove pooled type-check directory '{dir}': {err}"));
}
}
File::sync_path(db, dir);
ancestor = dir.parent();
}
Ok(())
}
}
fn cleanup_touched_files(db: &mut MemoryDb, touched_files: &[TouchedRootFile]) -> Result<(), String> {
for touched in touched_files.iter().rev() {
touched.cleanup(db)?;
}
File::sync_path(db, &SystemPathBuf::from(SRC_ROOT));
Ok(())
}
pub(crate) fn to_string(err: impl Display) -> String {
err.to_string()
}
#[cfg(test)]
mod tests {
use std::{ptr, sync::Mutex};
use ruff_db::{files::FileError, system::SystemPath};
use super::*;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn pool_is_global_singleton() {
assert!(
ptr::eq(MemoryDbPool::global(), MemoryDbPool::global()),
"global pool must resolve to the same instance on every call",
);
}
#[test]
fn reused_db_does_not_leak_previous_files() {
let _guard = TEST_LOCK.lock().unwrap();
drain_pool();
let path = SystemPathBuf::from("/pool_test_reuse.py");
let mut pooled = PooledMemoryDb::checkout().expect("initial checkout");
pooled.write_root_file(&path, "x = 1\n").expect("write root file");
assert!(
system_path_to_file(pooled.db(), &path).is_ok(),
"file should be visible within the run that wrote it",
);
drop(pooled);
assert_eq!(pool_len(), 1, "scrubbed db should be released back to the pool");
let pooled = PooledMemoryDb::checkout().expect("re-checkout");
assert_eq!(pool_len(), 0, "pool should be empty after re-checkout");
assert!(
matches!(system_path_to_file(pooled.db(), &path), Err(FileError::NotFound)),
"previous run's file must not be visible in the reused db",
);
drop(pooled);
drain_pool();
}
#[test]
fn nested_file_cleanup_removes_parent_directory() {
let _guard = TEST_LOCK.lock().unwrap();
drain_pool();
let path = SystemPathBuf::from("/sub_dir/nested.py");
let mut pooled = PooledMemoryDb::checkout().expect("checkout");
pooled.write_root_file(&path, "x = 1\n").expect("write nested file");
assert!(pooled.db().memory_file_system().is_directory("/sub_dir"));
assert!(pooled.db().memory_file_system().is_file(&path));
drop(pooled);
let pooled = PooledMemoryDb::checkout().expect("re-checkout");
let fs = pooled.db().memory_file_system();
assert!(!fs.exists(&path), "nested file must be removed after cleanup");
assert!(
!fs.is_directory(SystemPath::new("/sub_dir")),
"empty parent directory must be removed so it cannot leak into the next run"
);
drop(pooled);
drain_pool();
}
#[test]
fn deeply_nested_file_cleans_up_full_chain() {
let _guard = TEST_LOCK.lock().unwrap();
drain_pool();
let path = SystemPathBuf::from("/a/b/c/deep.py");
let mut pooled = PooledMemoryDb::checkout().expect("checkout");
pooled
.write_root_file(&path, "x = 1\n")
.expect("write deeply nested file");
drop(pooled);
let pooled = PooledMemoryDb::checkout().expect("re-checkout");
let fs = pooled.db().memory_file_system();
for dir in ["/a/b/c", "/a/b", "/a"] {
assert!(
!fs.is_directory(SystemPath::new(dir)),
"ancestor directory '{dir}' must be removed after cleanup"
);
}
assert!(fs.is_directory(SystemPath::new("/")), "SRC_ROOT itself must stay");
drop(pooled);
drain_pool();
}
#[test]
fn shared_parent_directory_collapses_when_last_file_removed() {
let _guard = TEST_LOCK.lock().unwrap();
drain_pool();
let a = SystemPathBuf::from("/shared/a.py");
let b = SystemPathBuf::from("/shared/b.py");
let mut pooled = PooledMemoryDb::checkout().expect("checkout");
pooled.write_root_file(&a, "x = 1\n").expect("write a");
pooled.write_root_file(&b, "y = 2\n").expect("write b");
assert!(pooled.db().memory_file_system().is_directory("/shared"));
drop(pooled);
let pooled = PooledMemoryDb::checkout().expect("re-checkout");
let fs = pooled.db().memory_file_system();
assert!(!fs.exists(&a));
assert!(!fs.exists(&b));
assert!(
!fs.is_directory(SystemPath::new("/shared")),
"shared parent must be removed once both files are gone",
);
drop(pooled);
drain_pool();
}
fn pool_len() -> usize {
MemoryDbPool::global().dbs.lock().unwrap().len()
}
fn drain_pool() {
MemoryDbPool::global().dbs.lock().unwrap().clear();
}
}