Disk cache: apply the free-space reading where it's taken, evict off the UI thread at open (#2551)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
arnav-hiwarkar
2026-10-28 22:45:48 +05:30
co-authored by Claude Opus 5.5
parent 74ea0be88f
commit 8ab492d8cd
3 changed files with 31 additions and 8 deletions
+16 -5
View File
@@ -84,11 +84,7 @@ impl Session {
/// re-apply the cache budgets when the reading changes them. Returns the effective budgets.
/// The disk cache's free disk space is read the same way (it keeps a reserve free).
pub fn memory_tick(&mut self) -> crate::prefs::CacheBudgets {
if let Some(dc) = &self.disk_cache {
dc.set_disk_space(self.disk_watch.latest());
let folder = dc.folder().to_path_buf();
self.disk_watch.poll_with("ec-disk-space", move || crate::sysinfo::disk_space(&folder));
}
self.poll_disk_space();
let before = self.prefs.cache_budgets(self.sys_memory);
self.sys_memory = self.memory_watch.latest().or(self.sys_memory);
self.memory_watch.poll();
@@ -104,6 +100,19 @@ impl Session {
after
}
/// Read the disk cache's free disk space in the background and apply it there (the reserve
/// it keeps free): the free space and the cache's size are taken at the same moment, and the
/// reader holds its own cache, so a reading never reaches another folder's.
fn poll_disk_space(&mut self) {
if let Some(dc) = &self.disk_cache {
let dc = dc.clone();
self.disk_watch.poll_with("ec-disk-space", move || {
dc.set_disk_space(crate::sysinfo::disk_space(dc.folder()));
Some(())
});
}
}
/// Apply Settings ▸ Media & Disk Cache ▸ Disk Cache: open (or close) the persistent cache
/// and attach it to the layer cache. Without a folder setting the platform cache folder is
/// used, but only by frontends with a settings store (headless sessions stay off disk).
@@ -150,6 +159,8 @@ impl Session {
},
};
self.disk_cache = Some(dc.clone());
// The reserve applies from the start (a new folder's watcher is idle).
self.poll_disk_space();
let salt = self.refresh_disk_salt(true).unwrap_or_default();
self.layer_cache.set_disk(Some((dc.clone(), salt)));
// Roto Brush / Object Matte segmentations persist there too (held weakly: switching the
+3 -2
View File
@@ -44,8 +44,9 @@ impl<T> Default for Watch<T> {
/// The system's memory ([`memory`]).
pub type MemoryWatch = Watch<SysMemory>;
/// The disk cache's disk: `(total, available)` bytes ([`disk_space`]).
pub type DiskWatch = Watch<(u64, u64)>;
/// The disk cache's free-space reading ([`disk_space`]): the reader applies it to its cache
/// itself, so only the busy guard is used.
pub type DiskWatch = Watch<()>;
impl<T: Copy + Send + 'static> Watch<T> {
/// Start a new reading in the background on a thread called `name` (no threads on the web:
+12 -1
View File
@@ -320,10 +320,12 @@ impl DiskCache {
dropped: AtomicU64::new(0),
failed: AtomicU64::new(0),
tmp_counter: AtomicU64::new(0),
queue: Arc::new((Mutex::new(Queue { jobs: Default::default(), busy: false }), Condvar::new())),
queue: Arc::new((Mutex::new(Queue { jobs: Default::default(), busy: cfg!(not(target_arch = "wasm32")) }), Condvar::new())),
min_layer_ms: AtomicU64::new(30),
});
dc.rescan();
// Shrinking a large cache deletes many files: the writer thread does it.
#[cfg(target_arch = "wasm32")]
dc.evict();
#[cfg(not(target_arch = "wasm32"))]
{
@@ -644,6 +646,15 @@ impl DiskCache {
#[cfg(not(target_arch = "wasm32"))]
fn writer(dc: std::sync::Weak<DiskCache>, q: Arc<(Mutex<Queue>, Condvar)>) {
let (m, cv) = &*q;
// The eviction at open (a lower limit than the folder holds), off the opening thread;
// `busy` starts set, so `flush` waits for it.
if let Some(cache) = dc.upgrade() {
cache.evict();
}
if let Ok(mut g) = m.lock() {
g.busy = false;
}
cv.notify_all();
loop {
let job = {
let Ok(mut g) = m.lock() else { return };