diff --git a/crates/engine/src/settings/session_settings.rs b/crates/engine/src/settings/session_settings.rs index a4e800da6..609ad5987 100644 --- a/crates/engine/src/settings/session_settings.rs +++ b/crates/engine/src/settings/session_settings.rs @@ -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 diff --git a/crates/engine/src/system/sysinfo.rs b/crates/engine/src/system/sysinfo.rs index c02e68773..b0bf16231 100644 --- a/crates/engine/src/system/sysinfo.rs +++ b/crates/engine/src/system/sysinfo.rs @@ -44,8 +44,9 @@ impl Default for Watch { /// The system's memory ([`memory`]). pub type MemoryWatch = Watch; -/// 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 Watch { /// Start a new reading in the background on a thread called `name` (no threads on the web: diff --git a/crates/render/src/disk_cache.rs b/crates/render/src/disk_cache.rs index 44a53f8d9..ca5660a67 100644 --- a/crates/render/src/disk_cache.rs +++ b/crates/render/src/disk_cache.rs @@ -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, q: Arc<(Mutex, 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 };