xref: /wasmtime-44.0.1/crates/cache/src/worker.rs (revision 61a371ac)
108f9eb17SAlex Crichton //! Background worker that watches over the cache.
208f9eb17SAlex Crichton //!
308f9eb17SAlex Crichton //! It cleans up old cache, updates statistics and optimizes the cache.
408f9eb17SAlex Crichton //! We allow losing some messages (it doesn't hurt) and some races,
508f9eb17SAlex Crichton //! but we guarantee eventual consistency and fault tolerancy.
608f9eb17SAlex Crichton //! Background tasks can be CPU intensive, but the worker thread has low priority.
708f9eb17SAlex Crichton 
8703871a2SAlex Crichton #![cfg_attr(
9703871a2SAlex Crichton     not(test),
10703871a2SAlex Crichton     expect(
11703871a2SAlex Crichton         clippy::useless_conversion,
12703871a2SAlex Crichton         reason = "cfg(test) and cfg(not(test)) have a different definition \
13703871a2SAlex Crichton                   of `SystemTime`, so conversions below are needed in \
14703871a2SAlex Crichton                   one mode but not the other, just ignore the lint in this \
15703871a2SAlex Crichton                   module in not(test) mode where the conversion isn't required",
16703871a2SAlex Crichton     )
17703871a2SAlex Crichton )]
18703871a2SAlex Crichton 
1990ac295eSAlex Crichton use super::{CacheConfig, fs_write_atomic};
2008f9eb17SAlex Crichton use log::{debug, info, trace, warn};
219ec02f9dSChristopher Serr use serde_derive::{Deserialize, Serialize};
2208f9eb17SAlex Crichton use std::cmp;
2308f9eb17SAlex Crichton use std::collections::HashMap;
2408f9eb17SAlex Crichton use std::ffi::OsStr;
2508f9eb17SAlex Crichton use std::fmt;
2608f9eb17SAlex Crichton use std::fs;
2708f9eb17SAlex Crichton use std::path::{Path, PathBuf};
2890ac295eSAlex Crichton use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
2908f9eb17SAlex Crichton #[cfg(test)]
3008f9eb17SAlex Crichton use std::sync::{Arc, Condvar, Mutex};
3108f9eb17SAlex Crichton use std::thread;
3208f9eb17SAlex Crichton use std::time::Duration;
3308f9eb17SAlex Crichton #[cfg(not(test))]
3408f9eb17SAlex Crichton use std::time::SystemTime;
3508f9eb17SAlex Crichton #[cfg(test)]
3608f9eb17SAlex Crichton use tests::system_time_stub::SystemTimeStub as SystemTime;
3708f9eb17SAlex Crichton 
3808f9eb17SAlex Crichton #[derive(Clone)]
3908f9eb17SAlex Crichton pub(super) struct Worker {
4008f9eb17SAlex Crichton     sender: SyncSender<CacheEvent>,
4108f9eb17SAlex Crichton     #[cfg(test)]
4208f9eb17SAlex Crichton     stats: Arc<(Mutex<WorkerStats>, Condvar)>,
4308f9eb17SAlex Crichton }
4408f9eb17SAlex Crichton 
4508f9eb17SAlex Crichton struct WorkerThread {
4608f9eb17SAlex Crichton     receiver: Receiver<CacheEvent>,
4708f9eb17SAlex Crichton     cache_config: CacheConfig,
4808f9eb17SAlex Crichton     #[cfg(test)]
4908f9eb17SAlex Crichton     stats: Arc<(Mutex<WorkerStats>, Condvar)>,
5008f9eb17SAlex Crichton }
5108f9eb17SAlex Crichton 
5208f9eb17SAlex Crichton #[cfg(test)]
5308f9eb17SAlex Crichton #[derive(Default)]
5408f9eb17SAlex Crichton struct WorkerStats {
5508f9eb17SAlex Crichton     dropped: u32,
5608f9eb17SAlex Crichton     sent: u32,
5708f9eb17SAlex Crichton     handled: u32,
5808f9eb17SAlex Crichton }
5908f9eb17SAlex Crichton 
6008f9eb17SAlex Crichton #[derive(Debug, Clone)]
6108f9eb17SAlex Crichton enum CacheEvent {
6208f9eb17SAlex Crichton     OnCacheGet(PathBuf),
6308f9eb17SAlex Crichton     OnCacheUpdate(PathBuf),
6408f9eb17SAlex Crichton }
6508f9eb17SAlex Crichton 
6608f9eb17SAlex Crichton impl Worker {
start_new(cache_config: &CacheConfig) -> Self671d4766f3SQuentin Gliech     pub(super) fn start_new(cache_config: &CacheConfig) -> Self {
6808f9eb17SAlex Crichton         let queue_size = match cache_config.worker_event_queue_size() {
6908f9eb17SAlex Crichton             num if num <= usize::max_value() as u64 => num as usize,
7008f9eb17SAlex Crichton             _ => usize::max_value(),
7108f9eb17SAlex Crichton         };
7208f9eb17SAlex Crichton         let (tx, rx) = sync_channel(queue_size);
7308f9eb17SAlex Crichton 
7408f9eb17SAlex Crichton         #[cfg(test)]
7508f9eb17SAlex Crichton         let stats = Arc::new((Mutex::new(WorkerStats::default()), Condvar::new()));
7608f9eb17SAlex Crichton 
7708f9eb17SAlex Crichton         let worker_thread = WorkerThread {
7808f9eb17SAlex Crichton             receiver: rx,
7908f9eb17SAlex Crichton             cache_config: cache_config.clone(),
8008f9eb17SAlex Crichton             #[cfg(test)]
8108f9eb17SAlex Crichton             stats: stats.clone(),
8208f9eb17SAlex Crichton         };
8308f9eb17SAlex Crichton 
8408f9eb17SAlex Crichton         // when self is dropped, sender will be dropped, what will cause the channel
8508f9eb17SAlex Crichton         // to hang, and the worker thread to exit -- it happens in the tests
8608f9eb17SAlex Crichton         // non-tests binary has only a static worker, so Rust doesn't drop it
871d4766f3SQuentin Gliech         thread::spawn(move || worker_thread.run());
8808f9eb17SAlex Crichton 
8908f9eb17SAlex Crichton         Self {
9008f9eb17SAlex Crichton             sender: tx,
9108f9eb17SAlex Crichton             #[cfg(test)]
9208f9eb17SAlex Crichton             stats,
9308f9eb17SAlex Crichton         }
9408f9eb17SAlex Crichton     }
9508f9eb17SAlex Crichton 
on_cache_get_async(&self, path: impl AsRef<Path>)9608f9eb17SAlex Crichton     pub(super) fn on_cache_get_async(&self, path: impl AsRef<Path>) {
9708f9eb17SAlex Crichton         let event = CacheEvent::OnCacheGet(path.as_ref().to_path_buf());
9808f9eb17SAlex Crichton         self.send_cache_event(event);
9908f9eb17SAlex Crichton     }
10008f9eb17SAlex Crichton 
on_cache_update_async(&self, path: impl AsRef<Path>)10108f9eb17SAlex Crichton     pub(super) fn on_cache_update_async(&self, path: impl AsRef<Path>) {
10208f9eb17SAlex Crichton         let event = CacheEvent::OnCacheUpdate(path.as_ref().to_path_buf());
10308f9eb17SAlex Crichton         self.send_cache_event(event);
10408f9eb17SAlex Crichton     }
10508f9eb17SAlex Crichton 
10608f9eb17SAlex Crichton     #[inline]
send_cache_event(&self, event: CacheEvent)10708f9eb17SAlex Crichton     fn send_cache_event(&self, event: CacheEvent) {
10808f9eb17SAlex Crichton         let sent_event = self.sender.try_send(event.clone());
10908f9eb17SAlex Crichton 
11008f9eb17SAlex Crichton         if let Err(ref err) = sent_event {
11108f9eb17SAlex Crichton             info!(
11208f9eb17SAlex Crichton                 "Failed to send asynchronously message to worker thread, \
1132bac6574SAlex Crichton                  event: {event:?}, error: {err}"
11408f9eb17SAlex Crichton             );
11508f9eb17SAlex Crichton         }
11608f9eb17SAlex Crichton 
11708f9eb17SAlex Crichton         #[cfg(test)]
11808f9eb17SAlex Crichton         {
11908f9eb17SAlex Crichton             let mut stats = self
12008f9eb17SAlex Crichton                 .stats
12108f9eb17SAlex Crichton                 .0
12208f9eb17SAlex Crichton                 .lock()
12308f9eb17SAlex Crichton                 .expect("Failed to acquire worker stats lock");
12408f9eb17SAlex Crichton 
12508f9eb17SAlex Crichton             if sent_event.is_ok() {
12608f9eb17SAlex Crichton                 stats.sent += 1;
12708f9eb17SAlex Crichton             } else {
12808f9eb17SAlex Crichton                 stats.dropped += 1;
12908f9eb17SAlex Crichton             }
13008f9eb17SAlex Crichton         }
13108f9eb17SAlex Crichton     }
13208f9eb17SAlex Crichton 
13308f9eb17SAlex Crichton     #[cfg(test)]
events_dropped(&self) -> u3213408f9eb17SAlex Crichton     pub(super) fn events_dropped(&self) -> u32 {
13508f9eb17SAlex Crichton         let stats = self
13608f9eb17SAlex Crichton             .stats
13708f9eb17SAlex Crichton             .0
13808f9eb17SAlex Crichton             .lock()
13908f9eb17SAlex Crichton             .expect("Failed to acquire worker stats lock");
14008f9eb17SAlex Crichton         stats.dropped
14108f9eb17SAlex Crichton     }
14208f9eb17SAlex Crichton 
14308f9eb17SAlex Crichton     #[cfg(test)]
wait_for_all_events_handled(&self)14408f9eb17SAlex Crichton     pub(super) fn wait_for_all_events_handled(&self) {
14508f9eb17SAlex Crichton         let (stats, condvar) = &*self.stats;
14608f9eb17SAlex Crichton         let mut stats = stats.lock().expect("Failed to acquire worker stats lock");
14708f9eb17SAlex Crichton         while stats.handled != stats.sent {
14808f9eb17SAlex Crichton             stats = condvar
14908f9eb17SAlex Crichton                 .wait(stats)
15008f9eb17SAlex Crichton                 .expect("Failed to reacquire worker stats lock");
15108f9eb17SAlex Crichton         }
15208f9eb17SAlex Crichton     }
15308f9eb17SAlex Crichton }
15408f9eb17SAlex Crichton 
15508f9eb17SAlex Crichton impl fmt::Debug for Worker {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result15608f9eb17SAlex Crichton     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15708f9eb17SAlex Crichton         f.debug_struct("Worker").finish()
15808f9eb17SAlex Crichton     }
15908f9eb17SAlex Crichton }
16008f9eb17SAlex Crichton 
16108f9eb17SAlex Crichton #[derive(Serialize, Deserialize)]
16208f9eb17SAlex Crichton struct ModuleCacheStatistics {
16308f9eb17SAlex Crichton     pub usages: u64,
16408f9eb17SAlex Crichton     #[serde(rename = "optimized-compression")]
16508f9eb17SAlex Crichton     pub compression_level: i32,
16608f9eb17SAlex Crichton }
16708f9eb17SAlex Crichton 
16808f9eb17SAlex Crichton impl ModuleCacheStatistics {
default(cache_config: &CacheConfig) -> Self16908f9eb17SAlex Crichton     fn default(cache_config: &CacheConfig) -> Self {
17008f9eb17SAlex Crichton         Self {
17108f9eb17SAlex Crichton             usages: 0,
17208f9eb17SAlex Crichton             compression_level: cache_config.baseline_compression_level(),
17308f9eb17SAlex Crichton         }
17408f9eb17SAlex Crichton     }
17508f9eb17SAlex Crichton }
17608f9eb17SAlex Crichton 
17708f9eb17SAlex Crichton enum CacheEntry {
17808f9eb17SAlex Crichton     Recognized {
17908f9eb17SAlex Crichton         path: PathBuf,
18008f9eb17SAlex Crichton         mtime: SystemTime,
18108f9eb17SAlex Crichton         size: u64,
18208f9eb17SAlex Crichton     },
18308f9eb17SAlex Crichton     Unrecognized {
18408f9eb17SAlex Crichton         path: PathBuf,
18508f9eb17SAlex Crichton         is_dir: bool,
18608f9eb17SAlex Crichton     },
18708f9eb17SAlex Crichton }
18808f9eb17SAlex Crichton 
18908f9eb17SAlex Crichton macro_rules! unwrap_or_warn {
19008f9eb17SAlex Crichton     ($result:expr, $cont:stmt, $err_msg:expr, $path:expr) => {
19108f9eb17SAlex Crichton         match $result {
19208f9eb17SAlex Crichton             Ok(val) => val,
19308f9eb17SAlex Crichton             Err(err) => {
19408f9eb17SAlex Crichton                 warn!("{}, path: {}, msg: {}", $err_msg, $path.display(), err);
19508f9eb17SAlex Crichton                 $cont
19608f9eb17SAlex Crichton             }
19708f9eb17SAlex Crichton         }
19808f9eb17SAlex Crichton     };
19908f9eb17SAlex Crichton }
20008f9eb17SAlex Crichton 
20108f9eb17SAlex Crichton impl WorkerThread {
run(self)2021d4766f3SQuentin Gliech     fn run(self) {
20308f9eb17SAlex Crichton         debug!("Cache worker thread started.");
20408f9eb17SAlex Crichton 
20508f9eb17SAlex Crichton         Self::lower_thread_priority();
20608f9eb17SAlex Crichton 
20708f9eb17SAlex Crichton         #[cfg(test)]
20808f9eb17SAlex Crichton         let (stats, condvar) = &*self.stats;
20908f9eb17SAlex Crichton 
21008f9eb17SAlex Crichton         for event in self.receiver.iter() {
21108f9eb17SAlex Crichton             match event {
21208f9eb17SAlex Crichton                 CacheEvent::OnCacheGet(path) => self.handle_on_cache_get(path),
21308f9eb17SAlex Crichton                 CacheEvent::OnCacheUpdate(path) => self.handle_on_cache_update(path),
21408f9eb17SAlex Crichton             }
21508f9eb17SAlex Crichton 
21608f9eb17SAlex Crichton             #[cfg(test)]
21708f9eb17SAlex Crichton             {
21808f9eb17SAlex Crichton                 let mut stats = stats.lock().expect("Failed to acquire worker stats lock");
21908f9eb17SAlex Crichton                 stats.handled += 1;
22008f9eb17SAlex Crichton                 condvar.notify_all();
22108f9eb17SAlex Crichton             }
22208f9eb17SAlex Crichton         }
22308f9eb17SAlex Crichton     }
22408f9eb17SAlex Crichton 
22508f9eb17SAlex Crichton     #[cfg(target_os = "fuchsia")]
lower_thread_priority()22608f9eb17SAlex Crichton     fn lower_thread_priority() {
22708f9eb17SAlex Crichton         // TODO This needs to use Fuchsia thread profiles
22808f9eb17SAlex Crichton         // https://fuchsia.dev/fuchsia-src/reference/kernel_objects/profile
22908f9eb17SAlex Crichton         warn!(
23008f9eb17SAlex Crichton             "Lowering thread priority on Fuchsia is currently a noop. It might affect application performance."
23108f9eb17SAlex Crichton         );
23208f9eb17SAlex Crichton     }
23308f9eb17SAlex Crichton 
23408f9eb17SAlex Crichton     #[cfg(target_os = "windows")]
lower_thread_priority()23508f9eb17SAlex Crichton     fn lower_thread_priority() {
236df150253SAlex Crichton         use windows_sys::Win32::System::Threading::*;
23708f9eb17SAlex Crichton 
23808f9eb17SAlex Crichton         // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority
23908f9eb17SAlex Crichton         // https://docs.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities
24008f9eb17SAlex Crichton 
2412d95076dSAlex Crichton         if unsafe { SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN) } == 0 {
24208f9eb17SAlex Crichton             warn!(
24308f9eb17SAlex Crichton                 "Failed to lower worker thread priority. It might affect application performance."
24408f9eb17SAlex Crichton             );
24508f9eb17SAlex Crichton         }
24608f9eb17SAlex Crichton     }
24708f9eb17SAlex Crichton 
24808f9eb17SAlex Crichton     #[cfg(not(any(target_os = "windows", target_os = "fuchsia")))]
lower_thread_priority()24908f9eb17SAlex Crichton     fn lower_thread_priority() {
25008f9eb17SAlex Crichton         // http://man7.org/linux/man-pages/man7/sched.7.html
25108f9eb17SAlex Crichton 
25208f9eb17SAlex Crichton         const NICE_DELTA_FOR_BACKGROUND_TASKS: i32 = 3;
25308f9eb17SAlex Crichton 
254ea0cb971SDan Gohman         match rustix::process::nice(NICE_DELTA_FOR_BACKGROUND_TASKS) {
25547490b43SDan Gohman             Ok(current_nice) => {
2562bac6574SAlex Crichton                 debug!("New nice value of worker thread: {current_nice}");
25708f9eb17SAlex Crichton             }
25847490b43SDan Gohman             Err(err) => {
25947490b43SDan Gohman                 warn!(
2602bac6574SAlex Crichton                     "Failed to lower worker thread priority ({err:?}). It might affect application performance."
26190ac295eSAlex Crichton                 );
26247490b43SDan Gohman             }
26347490b43SDan Gohman         };
26408f9eb17SAlex Crichton     }
26508f9eb17SAlex Crichton 
26608f9eb17SAlex Crichton     /// Increases the usage counter and recompresses the file
2670e9121daSFrankReh     /// if the usage counter reached configurable threshold.
handle_on_cache_get(&self, path: PathBuf)26808f9eb17SAlex Crichton     fn handle_on_cache_get(&self, path: PathBuf) {
26908f9eb17SAlex Crichton         trace!("handle_on_cache_get() for path: {}", path.display());
27008f9eb17SAlex Crichton 
27108f9eb17SAlex Crichton         // construct .stats file path
27208f9eb17SAlex Crichton         let filename = path.file_name().unwrap().to_str().unwrap();
273a0442ea0SHamir Mahal         let stats_path = path.with_file_name(format!("{filename}.stats"));
27408f9eb17SAlex Crichton 
27508f9eb17SAlex Crichton         // load .stats file (default if none or error)
27608f9eb17SAlex Crichton         let mut stats = read_stats_file(stats_path.as_ref())
27708f9eb17SAlex Crichton             .unwrap_or_else(|| ModuleCacheStatistics::default(&self.cache_config));
27808f9eb17SAlex Crichton 
27908f9eb17SAlex Crichton         // step 1: update the usage counter & write to the disk
28008f9eb17SAlex Crichton         //         it's racy, but it's fine (the counter will be just smaller,
28108f9eb17SAlex Crichton         //         sometimes will retrigger recompression)
28208f9eb17SAlex Crichton         stats.usages += 1;
28308f9eb17SAlex Crichton         if !write_stats_file(stats_path.as_ref(), &stats) {
28408f9eb17SAlex Crichton             return;
28508f9eb17SAlex Crichton         }
28608f9eb17SAlex Crichton 
28708f9eb17SAlex Crichton         // step 2: recompress if there's a need
28808f9eb17SAlex Crichton         let opt_compr_lvl = self.cache_config.optimized_compression_level();
28908f9eb17SAlex Crichton         if stats.compression_level >= opt_compr_lvl
29008f9eb17SAlex Crichton             || stats.usages
29108f9eb17SAlex Crichton                 < self
29208f9eb17SAlex Crichton                     .cache_config
29308f9eb17SAlex Crichton                     .optimized_compression_usage_counter_threshold()
29408f9eb17SAlex Crichton         {
29508f9eb17SAlex Crichton             return;
29608f9eb17SAlex Crichton         }
29708f9eb17SAlex Crichton 
29808f9eb17SAlex Crichton         let lock_path = if let Some(p) = acquire_task_fs_lock(
29908f9eb17SAlex Crichton             path.as_ref(),
30008f9eb17SAlex Crichton             self.cache_config.optimizing_compression_task_timeout(),
30108f9eb17SAlex Crichton             self.cache_config
30208f9eb17SAlex Crichton                 .allowed_clock_drift_for_files_from_future(),
30308f9eb17SAlex Crichton         ) {
30408f9eb17SAlex Crichton             p
30508f9eb17SAlex Crichton         } else {
30608f9eb17SAlex Crichton             return;
30708f9eb17SAlex Crichton         };
30808f9eb17SAlex Crichton 
30908f9eb17SAlex Crichton         trace!("Trying to recompress file: {}", path.display());
31008f9eb17SAlex Crichton 
31108f9eb17SAlex Crichton         // recompress, write to other file, rename (it's atomic file content exchange)
31208f9eb17SAlex Crichton         // and update the stats file
31308f9eb17SAlex Crichton         let compressed_cache_bytes = unwrap_or_warn!(
31408f9eb17SAlex Crichton             fs::read(&path),
31508f9eb17SAlex Crichton             return,
31608f9eb17SAlex Crichton             "Failed to read old cache file",
31708f9eb17SAlex Crichton             path
31808f9eb17SAlex Crichton         );
31908f9eb17SAlex Crichton 
32008f9eb17SAlex Crichton         let cache_bytes = unwrap_or_warn!(
32108f9eb17SAlex Crichton             zstd::decode_all(&compressed_cache_bytes[..]),
32208f9eb17SAlex Crichton             return,
32308f9eb17SAlex Crichton             "Failed to decompress cached code",
32408f9eb17SAlex Crichton             path
32508f9eb17SAlex Crichton         );
32608f9eb17SAlex Crichton 
32708f9eb17SAlex Crichton         let recompressed_cache_bytes = unwrap_or_warn!(
32808f9eb17SAlex Crichton             zstd::encode_all(&cache_bytes[..], opt_compr_lvl),
32908f9eb17SAlex Crichton             return,
33008f9eb17SAlex Crichton             "Failed to compress cached code",
33108f9eb17SAlex Crichton             path
33208f9eb17SAlex Crichton         );
33308f9eb17SAlex Crichton 
33408f9eb17SAlex Crichton         unwrap_or_warn!(
33508f9eb17SAlex Crichton             fs::write(&lock_path, &recompressed_cache_bytes),
33608f9eb17SAlex Crichton             return,
33708f9eb17SAlex Crichton             "Failed to write recompressed cache",
33808f9eb17SAlex Crichton             lock_path
33908f9eb17SAlex Crichton         );
34008f9eb17SAlex Crichton 
34108f9eb17SAlex Crichton         unwrap_or_warn!(
34208f9eb17SAlex Crichton             fs::rename(&lock_path, &path),
34308f9eb17SAlex Crichton             {
34408f9eb17SAlex Crichton                 if let Err(error) = fs::remove_file(&lock_path) {
34508f9eb17SAlex Crichton                     warn!(
34608f9eb17SAlex Crichton                         "Failed to clean up (remove) recompressed cache, path {}, err: {}",
34708f9eb17SAlex Crichton                         lock_path.display(),
34808f9eb17SAlex Crichton                         error
34908f9eb17SAlex Crichton                     );
35008f9eb17SAlex Crichton                 }
35108f9eb17SAlex Crichton 
35208f9eb17SAlex Crichton                 return;
35308f9eb17SAlex Crichton             },
35408f9eb17SAlex Crichton             "Failed to rename recompressed cache",
35508f9eb17SAlex Crichton             lock_path
35608f9eb17SAlex Crichton         );
35708f9eb17SAlex Crichton 
35808f9eb17SAlex Crichton         // update stats file (reload it! recompression can take some time)
35908f9eb17SAlex Crichton         if let Some(mut new_stats) = read_stats_file(stats_path.as_ref()) {
36008f9eb17SAlex Crichton             if new_stats.compression_level >= opt_compr_lvl {
36108f9eb17SAlex Crichton                 // Rare race:
36208f9eb17SAlex Crichton                 //    two instances with different opt_compr_lvl: we don't know in which order they updated
36308f9eb17SAlex Crichton                 //    the cache file and the stats file (they are not updated together atomically)
36408f9eb17SAlex Crichton                 // Possible solution is to use directories per cache entry, but it complicates the system
36508f9eb17SAlex Crichton                 // and is not worth it.
36608f9eb17SAlex Crichton                 debug!(
36708f9eb17SAlex Crichton                     "DETECTED task did more than once (or race with new file): \
36808f9eb17SAlex Crichton                      recompression of {}. Note: if optimized compression level setting \
36908f9eb17SAlex Crichton                      has changed in the meantine, the stats file might contain \
37008f9eb17SAlex Crichton                      inconsistent compression level due to race.",
37108f9eb17SAlex Crichton                     path.display()
37208f9eb17SAlex Crichton                 );
37308f9eb17SAlex Crichton             } else {
37408f9eb17SAlex Crichton                 new_stats.compression_level = opt_compr_lvl;
37508f9eb17SAlex Crichton                 let _ = write_stats_file(stats_path.as_ref(), &new_stats);
37608f9eb17SAlex Crichton             }
37708f9eb17SAlex Crichton 
37808f9eb17SAlex Crichton             if new_stats.usages < stats.usages {
37908f9eb17SAlex Crichton                 debug!(
38008f9eb17SAlex Crichton                     "DETECTED lower usage count (new file or race with counter \
38108f9eb17SAlex Crichton                      increasing): file {}",
38208f9eb17SAlex Crichton                     path.display()
38308f9eb17SAlex Crichton                 );
38408f9eb17SAlex Crichton             }
38508f9eb17SAlex Crichton         } else {
38608f9eb17SAlex Crichton             debug!(
38708f9eb17SAlex Crichton                 "Can't read stats file again to update compression level (it might got \
38808f9eb17SAlex Crichton                  cleaned up): file {}",
38908f9eb17SAlex Crichton                 stats_path.display()
39008f9eb17SAlex Crichton             );
39108f9eb17SAlex Crichton         }
39208f9eb17SAlex Crichton 
39308f9eb17SAlex Crichton         trace!("Task finished: recompress file: {}", path.display());
39408f9eb17SAlex Crichton     }
39508f9eb17SAlex Crichton 
directory(&self) -> &PathBuf396*61a371acSJesse Rusak     fn directory(&self) -> &PathBuf {
397*61a371acSJesse Rusak         self.cache_config
398*61a371acSJesse Rusak             .directory()
399*61a371acSJesse Rusak             .expect("CacheConfig should be validated before being passed to a WorkerThread")
400*61a371acSJesse Rusak     }
401*61a371acSJesse Rusak 
handle_on_cache_update(&self, path: PathBuf)40208f9eb17SAlex Crichton     fn handle_on_cache_update(&self, path: PathBuf) {
40308f9eb17SAlex Crichton         trace!("handle_on_cache_update() for path: {}", path.display());
40408f9eb17SAlex Crichton 
40508f9eb17SAlex Crichton         // ---------------------- step 1: create .stats file
40608f9eb17SAlex Crichton 
40708f9eb17SAlex Crichton         // construct .stats file path
40808f9eb17SAlex Crichton         let filename = path
40908f9eb17SAlex Crichton             .file_name()
41008f9eb17SAlex Crichton             .expect("Expected valid cache file name")
41108f9eb17SAlex Crichton             .to_str()
41208f9eb17SAlex Crichton             .expect("Expected valid cache file name");
413a0442ea0SHamir Mahal         let stats_path = path.with_file_name(format!("{filename}.stats"));
41408f9eb17SAlex Crichton 
41508f9eb17SAlex Crichton         // create and write stats file
41608f9eb17SAlex Crichton         let mut stats = ModuleCacheStatistics::default(&self.cache_config);
41708f9eb17SAlex Crichton         stats.usages += 1;
41808f9eb17SAlex Crichton         write_stats_file(&stats_path, &stats);
41908f9eb17SAlex Crichton 
42008f9eb17SAlex Crichton         // ---------------------- step 2: perform cleanup task if needed
42108f9eb17SAlex Crichton 
42208f9eb17SAlex Crichton         // acquire lock for cleanup task
42308f9eb17SAlex Crichton         // Lock is a proof of recent cleanup task, so we don't want to delete them.
42408f9eb17SAlex Crichton         // Expired locks will be deleted by the cleanup task.
425*61a371acSJesse Rusak         let cleanup_file = self.directory().join(".cleanup"); // some non existing marker file
42608f9eb17SAlex Crichton         if acquire_task_fs_lock(
42708f9eb17SAlex Crichton             &cleanup_file,
42808f9eb17SAlex Crichton             self.cache_config.cleanup_interval(),
42908f9eb17SAlex Crichton             self.cache_config
43008f9eb17SAlex Crichton                 .allowed_clock_drift_for_files_from_future(),
43108f9eb17SAlex Crichton         )
43208f9eb17SAlex Crichton         .is_none()
43308f9eb17SAlex Crichton         {
43408f9eb17SAlex Crichton             return;
43508f9eb17SAlex Crichton         }
43608f9eb17SAlex Crichton 
43708f9eb17SAlex Crichton         trace!("Trying to clean up cache");
43808f9eb17SAlex Crichton 
43908f9eb17SAlex Crichton         let mut cache_index = self.list_cache_contents();
44008f9eb17SAlex Crichton         let future_tolerance = SystemTime::now()
44108f9eb17SAlex Crichton             .checked_add(
44208f9eb17SAlex Crichton                 self.cache_config
44308f9eb17SAlex Crichton                     .allowed_clock_drift_for_files_from_future(),
44408f9eb17SAlex Crichton             )
44508f9eb17SAlex Crichton             .expect("Brace your cache, the next Big Bang is coming (time overflow)");
44608f9eb17SAlex Crichton         cache_index.sort_unstable_by(|lhs, rhs| {
44708f9eb17SAlex Crichton             // sort by age
44808f9eb17SAlex Crichton             use CacheEntry::*;
44908f9eb17SAlex Crichton             match (lhs, rhs) {
45008f9eb17SAlex Crichton                 (Recognized { mtime: lhs_mt, .. }, Recognized { mtime: rhs_mt, .. }) => {
45108f9eb17SAlex Crichton                     match (*lhs_mt > future_tolerance, *rhs_mt > future_tolerance) {
45208f9eb17SAlex Crichton                         // later == younger
45308f9eb17SAlex Crichton                         (false, false) => rhs_mt.cmp(lhs_mt),
45408f9eb17SAlex Crichton                         // files from far future are treated as oldest recognized files
45508f9eb17SAlex Crichton                         // we want to delete them, so the cache keeps track of recent files
45608f9eb17SAlex Crichton                         // however, we don't delete them uncodintionally,
45708f9eb17SAlex Crichton                         // because .stats file can be overwritten with a meaningful mtime
45808f9eb17SAlex Crichton                         (true, false) => cmp::Ordering::Greater,
45908f9eb17SAlex Crichton                         (false, true) => cmp::Ordering::Less,
46008f9eb17SAlex Crichton                         (true, true) => cmp::Ordering::Equal,
46108f9eb17SAlex Crichton                     }
46208f9eb17SAlex Crichton                 }
46308f9eb17SAlex Crichton                 // unrecognized is kind of infinity
46408f9eb17SAlex Crichton                 (Recognized { .. }, Unrecognized { .. }) => cmp::Ordering::Less,
46508f9eb17SAlex Crichton                 (Unrecognized { .. }, Recognized { .. }) => cmp::Ordering::Greater,
46608f9eb17SAlex Crichton                 (Unrecognized { .. }, Unrecognized { .. }) => cmp::Ordering::Equal,
46708f9eb17SAlex Crichton             }
46808f9eb17SAlex Crichton         });
46908f9eb17SAlex Crichton 
47008f9eb17SAlex Crichton         // find "cut" boundary:
47108f9eb17SAlex Crichton         // - remove unrecognized files anyway,
47208f9eb17SAlex Crichton         // - remove some cache files if some quota has been exceeded
47308f9eb17SAlex Crichton         let mut total_size = 0u64;
47408f9eb17SAlex Crichton         let mut start_delete_idx = None;
47508f9eb17SAlex Crichton         let mut start_delete_idx_if_deleting_recognized_items: Option<usize> = None;
47608f9eb17SAlex Crichton 
47708f9eb17SAlex Crichton         let total_size_limit = self.cache_config.files_total_size_soft_limit();
47808f9eb17SAlex Crichton         let file_count_limit = self.cache_config.file_count_soft_limit();
47908f9eb17SAlex Crichton         let tsl_if_deleting = total_size_limit
48008f9eb17SAlex Crichton             .checked_mul(
48108f9eb17SAlex Crichton                 self.cache_config
48208f9eb17SAlex Crichton                     .files_total_size_limit_percent_if_deleting() as u64,
48308f9eb17SAlex Crichton             )
48408f9eb17SAlex Crichton             .unwrap()
48508f9eb17SAlex Crichton             / 100;
48608f9eb17SAlex Crichton         let fcl_if_deleting = file_count_limit
48708f9eb17SAlex Crichton             .checked_mul(self.cache_config.file_count_limit_percent_if_deleting() as u64)
48808f9eb17SAlex Crichton             .unwrap()
48908f9eb17SAlex Crichton             / 100;
49008f9eb17SAlex Crichton 
49108f9eb17SAlex Crichton         for (idx, item) in cache_index.iter().enumerate() {
49208f9eb17SAlex Crichton             let size = if let CacheEntry::Recognized { size, .. } = item {
49308f9eb17SAlex Crichton                 size
49408f9eb17SAlex Crichton             } else {
49508f9eb17SAlex Crichton                 start_delete_idx = Some(idx);
49608f9eb17SAlex Crichton                 break;
49708f9eb17SAlex Crichton             };
49808f9eb17SAlex Crichton 
49908f9eb17SAlex Crichton             total_size += size;
50008f9eb17SAlex Crichton             if start_delete_idx_if_deleting_recognized_items.is_none()
50108f9eb17SAlex Crichton                 && (total_size > tsl_if_deleting || (idx + 1) as u64 > fcl_if_deleting)
50208f9eb17SAlex Crichton             {
50308f9eb17SAlex Crichton                 start_delete_idx_if_deleting_recognized_items = Some(idx);
50408f9eb17SAlex Crichton             }
50508f9eb17SAlex Crichton 
50608f9eb17SAlex Crichton             if total_size > total_size_limit || (idx + 1) as u64 > file_count_limit {
50708f9eb17SAlex Crichton                 start_delete_idx = start_delete_idx_if_deleting_recognized_items;
50808f9eb17SAlex Crichton                 break;
50908f9eb17SAlex Crichton             }
51008f9eb17SAlex Crichton         }
51108f9eb17SAlex Crichton 
51208f9eb17SAlex Crichton         if let Some(idx) = start_delete_idx {
51308f9eb17SAlex Crichton             for item in &cache_index[idx..] {
51408f9eb17SAlex Crichton                 let (result, path, entity) = match item {
51508f9eb17SAlex Crichton                     CacheEntry::Recognized { path, .. }
51608f9eb17SAlex Crichton                     | CacheEntry::Unrecognized {
51708f9eb17SAlex Crichton                         path,
51808f9eb17SAlex Crichton                         is_dir: false,
51908f9eb17SAlex Crichton                     } => (fs::remove_file(path), path, "file"),
52008f9eb17SAlex Crichton                     CacheEntry::Unrecognized { path, is_dir: true } => {
52108f9eb17SAlex Crichton                         (fs::remove_dir_all(path), path, "directory")
52208f9eb17SAlex Crichton                     }
52308f9eb17SAlex Crichton                 };
52408f9eb17SAlex Crichton                 if let Err(err) = result {
52508f9eb17SAlex Crichton                     warn!(
52608f9eb17SAlex Crichton                         "Failed to remove {} during cleanup, path: {}, err: {}",
52708f9eb17SAlex Crichton                         entity,
52808f9eb17SAlex Crichton                         path.display(),
52908f9eb17SAlex Crichton                         err
53008f9eb17SAlex Crichton                     );
53108f9eb17SAlex Crichton                 }
53208f9eb17SAlex Crichton             }
53308f9eb17SAlex Crichton         }
53408f9eb17SAlex Crichton 
53508f9eb17SAlex Crichton         trace!("Task finished: clean up cache");
53608f9eb17SAlex Crichton     }
53708f9eb17SAlex Crichton 
53808f9eb17SAlex Crichton     // Be fault tolerant: list as much as you can, and ignore the rest
list_cache_contents(&self) -> Vec<CacheEntry>53908f9eb17SAlex Crichton     fn list_cache_contents(&self) -> Vec<CacheEntry> {
54008f9eb17SAlex Crichton         fn enter_dir(
54108f9eb17SAlex Crichton             vec: &mut Vec<CacheEntry>,
54208f9eb17SAlex Crichton             dir_path: &Path,
54308f9eb17SAlex Crichton             level: u8,
54408f9eb17SAlex Crichton             cache_config: &CacheConfig,
54508f9eb17SAlex Crichton         ) {
54608f9eb17SAlex Crichton             macro_rules! add_unrecognized {
54708f9eb17SAlex Crichton                 (file: $path:expr) => {
54808f9eb17SAlex Crichton                     add_unrecognized!(false, $path)
54908f9eb17SAlex Crichton                 };
55008f9eb17SAlex Crichton                 (dir: $path:expr) => {
55108f9eb17SAlex Crichton                     add_unrecognized!(true, $path)
55208f9eb17SAlex Crichton                 };
55308f9eb17SAlex Crichton                 ($is_dir:expr, $path:expr) => {
55408f9eb17SAlex Crichton                     vec.push(CacheEntry::Unrecognized {
55508f9eb17SAlex Crichton                         path: $path.to_path_buf(),
55608f9eb17SAlex Crichton                         is_dir: $is_dir,
5579e142f87SAlex Crichton                     })
55808f9eb17SAlex Crichton                 };
55908f9eb17SAlex Crichton             }
56008f9eb17SAlex Crichton             macro_rules! add_unrecognized_and {
56108f9eb17SAlex Crichton                 ([ $( $ty:ident: $path:expr ),* ], $cont:stmt) => {{
56208f9eb17SAlex Crichton                     $( add_unrecognized!($ty: $path); )*
56308f9eb17SAlex Crichton                         $cont
56408f9eb17SAlex Crichton                 }};
56508f9eb17SAlex Crichton             }
56608f9eb17SAlex Crichton 
56708f9eb17SAlex Crichton             macro_rules! unwrap_or {
56808f9eb17SAlex Crichton                 ($result:expr, $cont:stmt, $err_msg:expr) => {
56908f9eb17SAlex Crichton                     unwrap_or!($result, $cont, $err_msg, dir_path)
57008f9eb17SAlex Crichton                 };
57108f9eb17SAlex Crichton                 ($result:expr, $cont:stmt, $err_msg:expr, $path:expr) => {
57208f9eb17SAlex Crichton                     unwrap_or_warn!(
57308f9eb17SAlex Crichton                         $result,
57408f9eb17SAlex Crichton                         $cont,
57508f9eb17SAlex Crichton                         format!("{}, level: {}", $err_msg, level),
57608f9eb17SAlex Crichton                         $path
57708f9eb17SAlex Crichton                     )
57808f9eb17SAlex Crichton                 };
57908f9eb17SAlex Crichton             }
58008f9eb17SAlex Crichton 
58108f9eb17SAlex Crichton             // If we fail to list a directory, something bad is happening anyway
58208f9eb17SAlex Crichton             // (something touches our cache or we have disk failure)
58308f9eb17SAlex Crichton             // Try to delete it, so we can stay within soft limits of the cache size.
58408f9eb17SAlex Crichton             // This comment applies later in this function, too.
58508f9eb17SAlex Crichton             let it = unwrap_or!(
58608f9eb17SAlex Crichton                 fs::read_dir(dir_path),
58708f9eb17SAlex Crichton                 add_unrecognized_and!([dir: dir_path], return),
58808f9eb17SAlex Crichton                 "Failed to list cache directory, deleting it"
58908f9eb17SAlex Crichton             );
59008f9eb17SAlex Crichton 
59108f9eb17SAlex Crichton             let mut cache_files = HashMap::new();
59208f9eb17SAlex Crichton             for entry in it {
59308f9eb17SAlex Crichton                 // read_dir() returns an iterator over results - in case some of them are errors
59408f9eb17SAlex Crichton                 // we don't know their names, so we can't delete them. We don't want to delete
59508f9eb17SAlex Crichton                 // the whole directory with good entries too, so we just ignore the erroneous entries.
59608f9eb17SAlex Crichton                 let entry = unwrap_or!(
59708f9eb17SAlex Crichton                     entry,
59808f9eb17SAlex Crichton                     continue,
59908f9eb17SAlex Crichton                     "Failed to read a cache dir entry (NOT deleting it, it still occupies space)"
60008f9eb17SAlex Crichton                 );
60108f9eb17SAlex Crichton                 let path = entry.path();
60208f9eb17SAlex Crichton                 match (level, path.is_dir()) {
60308f9eb17SAlex Crichton                     (0..=1, true) => enter_dir(vec, &path, level + 1, cache_config),
60408f9eb17SAlex Crichton                     (0..=1, false) => {
60508f9eb17SAlex Crichton                         if level == 0
60608f9eb17SAlex Crichton                             && path.file_stem() == Some(OsStr::new(".cleanup"))
60708f9eb17SAlex Crichton                                 && path.extension().is_some()
60808f9eb17SAlex Crichton                                 // assume it's cleanup lock
60908f9eb17SAlex Crichton                                 && !is_fs_lock_expired(
61008f9eb17SAlex Crichton                                     Some(&entry),
61108f9eb17SAlex Crichton                                     &path,
61208f9eb17SAlex Crichton                                     cache_config.cleanup_interval(),
61308f9eb17SAlex Crichton                                     cache_config.allowed_clock_drift_for_files_from_future(),
61408f9eb17SAlex Crichton                                 )
61508f9eb17SAlex Crichton                         {
61608f9eb17SAlex Crichton                             continue; // skip active lock
61708f9eb17SAlex Crichton                         }
61808f9eb17SAlex Crichton                         add_unrecognized!(file: path);
61908f9eb17SAlex Crichton                     }
62008f9eb17SAlex Crichton                     (2, false) => {
62108f9eb17SAlex Crichton                         match path.extension().and_then(OsStr::to_str) {
62208f9eb17SAlex Crichton                             // mod or stats file
62308f9eb17SAlex Crichton                             None | Some("stats") => {
62408f9eb17SAlex Crichton                                 cache_files.insert(path, entry);
62508f9eb17SAlex Crichton                             }
62608f9eb17SAlex Crichton 
62708f9eb17SAlex Crichton                             Some(ext) => {
62808f9eb17SAlex Crichton                                 // check if valid lock
62908f9eb17SAlex Crichton                                 let recognized = ext.starts_with("wip-")
63008f9eb17SAlex Crichton                                     && !is_fs_lock_expired(
63108f9eb17SAlex Crichton                                         Some(&entry),
63208f9eb17SAlex Crichton                                         &path,
63308f9eb17SAlex Crichton                                         cache_config.optimizing_compression_task_timeout(),
63408f9eb17SAlex Crichton                                         cache_config.allowed_clock_drift_for_files_from_future(),
63508f9eb17SAlex Crichton                                     );
63608f9eb17SAlex Crichton 
63708f9eb17SAlex Crichton                                 if !recognized {
63808f9eb17SAlex Crichton                                     add_unrecognized!(file: path);
63908f9eb17SAlex Crichton                                 }
64008f9eb17SAlex Crichton                             }
64108f9eb17SAlex Crichton                         }
64208f9eb17SAlex Crichton                     }
64308f9eb17SAlex Crichton                     (_, is_dir) => add_unrecognized!(is_dir, path),
64408f9eb17SAlex Crichton                 }
64508f9eb17SAlex Crichton             }
64608f9eb17SAlex Crichton 
64708f9eb17SAlex Crichton             // associate module with its stats & handle them
64808f9eb17SAlex Crichton             // assumption: just mods and stats
64908f9eb17SAlex Crichton             for (path, entry) in cache_files.iter() {
65008f9eb17SAlex Crichton                 let path_buf: PathBuf;
65108f9eb17SAlex Crichton                 let (mod_, stats_, is_mod) = match path.extension() {
65208f9eb17SAlex Crichton                     Some(_) => {
65308f9eb17SAlex Crichton                         path_buf = path.with_extension("");
65408f9eb17SAlex Crichton                         (
65508f9eb17SAlex Crichton                             cache_files.get(&path_buf).map(|v| (&path_buf, v)),
65608f9eb17SAlex Crichton                             Some((path, entry)),
65708f9eb17SAlex Crichton                             false,
65808f9eb17SAlex Crichton                         )
65908f9eb17SAlex Crichton                     }
66008f9eb17SAlex Crichton                     None => {
66108f9eb17SAlex Crichton                         path_buf = path.with_extension("stats");
66208f9eb17SAlex Crichton                         (
66308f9eb17SAlex Crichton                             Some((path, entry)),
66408f9eb17SAlex Crichton                             cache_files.get(&path_buf).map(|v| (&path_buf, v)),
66508f9eb17SAlex Crichton                             true,
66608f9eb17SAlex Crichton                         )
66708f9eb17SAlex Crichton                     }
66808f9eb17SAlex Crichton                 };
66908f9eb17SAlex Crichton 
67008f9eb17SAlex Crichton                 // construct a cache entry
67108f9eb17SAlex Crichton                 match (mod_, stats_, is_mod) {
67208f9eb17SAlex Crichton                     (Some((mod_path, mod_entry)), Some((stats_path, stats_entry)), true) => {
67308f9eb17SAlex Crichton                         let mod_metadata = unwrap_or!(
67408f9eb17SAlex Crichton                             mod_entry.metadata(),
67508f9eb17SAlex Crichton                             add_unrecognized_and!([file: stats_path, file: mod_path], continue),
67608f9eb17SAlex Crichton                             "Failed to get metadata, deleting BOTH module cache and stats files",
67708f9eb17SAlex Crichton                             mod_path
67808f9eb17SAlex Crichton                         );
67908f9eb17SAlex Crichton                         let stats_mtime = unwrap_or!(
68008f9eb17SAlex Crichton                             stats_entry.metadata().and_then(|m| m.modified()),
68108f9eb17SAlex Crichton                             add_unrecognized_and!(
68208f9eb17SAlex Crichton                                 [file: stats_path],
68308f9eb17SAlex Crichton                                 unwrap_or!(
68408f9eb17SAlex Crichton                                     mod_metadata.modified(),
68508f9eb17SAlex Crichton                                     add_unrecognized_and!(
68608f9eb17SAlex Crichton                                         [file: stats_path, file: mod_path],
68708f9eb17SAlex Crichton                                         continue
68808f9eb17SAlex Crichton                                     ),
68908f9eb17SAlex Crichton                                     "Failed to get mtime, deleting BOTH module cache and stats \
69008f9eb17SAlex Crichton                                      files",
69108f9eb17SAlex Crichton                                     mod_path
69208f9eb17SAlex Crichton                                 )
69308f9eb17SAlex Crichton                             ),
69408f9eb17SAlex Crichton                             "Failed to get metadata/mtime, deleting the file",
69508f9eb17SAlex Crichton                             stats_path
69608f9eb17SAlex Crichton                         );
69708f9eb17SAlex Crichton                         // .into() called for the SystemTimeStub if cfg(test)
69808f9eb17SAlex Crichton                         vec.push(CacheEntry::Recognized {
69908f9eb17SAlex Crichton                             path: mod_path.to_path_buf(),
70008f9eb17SAlex Crichton                             mtime: stats_mtime.into(),
70108f9eb17SAlex Crichton                             size: mod_metadata.len(),
70208f9eb17SAlex Crichton                         })
70308f9eb17SAlex Crichton                     }
70408f9eb17SAlex Crichton                     (Some(_), Some(_), false) => (), // was or will be handled by previous branch
70508f9eb17SAlex Crichton                     (Some((mod_path, mod_entry)), None, _) => {
70608f9eb17SAlex Crichton                         let (mod_metadata, mod_mtime) = unwrap_or!(
70708f9eb17SAlex Crichton                             mod_entry
70808f9eb17SAlex Crichton                                 .metadata()
70908f9eb17SAlex Crichton                                 .and_then(|md| md.modified().map(|mt| (md, mt))),
71008f9eb17SAlex Crichton                             add_unrecognized_and!([file: mod_path], continue),
71108f9eb17SAlex Crichton                             "Failed to get metadata/mtime, deleting the file",
71208f9eb17SAlex Crichton                             mod_path
71308f9eb17SAlex Crichton                         );
71408f9eb17SAlex Crichton                         // .into() called for the SystemTimeStub if cfg(test)
71508f9eb17SAlex Crichton                         vec.push(CacheEntry::Recognized {
71608f9eb17SAlex Crichton                             path: mod_path.to_path_buf(),
71708f9eb17SAlex Crichton                             mtime: mod_mtime.into(),
71808f9eb17SAlex Crichton                             size: mod_metadata.len(),
71908f9eb17SAlex Crichton                         })
72008f9eb17SAlex Crichton                     }
72108f9eb17SAlex Crichton                     (None, Some((stats_path, _stats_entry)), _) => {
72208f9eb17SAlex Crichton                         debug!("Found orphaned stats file: {}", stats_path.display());
72308f9eb17SAlex Crichton                         add_unrecognized!(file: stats_path);
72408f9eb17SAlex Crichton                     }
72508f9eb17SAlex Crichton                     _ => unreachable!(),
72608f9eb17SAlex Crichton                 }
72708f9eb17SAlex Crichton             }
72808f9eb17SAlex Crichton         }
72908f9eb17SAlex Crichton 
73008f9eb17SAlex Crichton         let mut vec = Vec::new();
731*61a371acSJesse Rusak         enter_dir(&mut vec, self.directory(), 0, &self.cache_config);
73208f9eb17SAlex Crichton         vec
73308f9eb17SAlex Crichton     }
73408f9eb17SAlex Crichton }
73508f9eb17SAlex Crichton 
read_stats_file(path: &Path) -> Option<ModuleCacheStatistics>73608f9eb17SAlex Crichton fn read_stats_file(path: &Path) -> Option<ModuleCacheStatistics> {
73708c7359fSAlex Crichton     fs::read_to_string(path)
73808f9eb17SAlex Crichton         .map_err(|err| {
73908f9eb17SAlex Crichton             trace!(
74008f9eb17SAlex Crichton                 "Failed to read stats file, path: {}, err: {}",
74108f9eb17SAlex Crichton                 path.display(),
74208f9eb17SAlex Crichton                 err
74308f9eb17SAlex Crichton             )
74408f9eb17SAlex Crichton         })
74508c7359fSAlex Crichton         .and_then(|contents| {
74608c7359fSAlex Crichton             toml::from_str::<ModuleCacheStatistics>(&contents).map_err(|err| {
74708f9eb17SAlex Crichton                 trace!(
74808f9eb17SAlex Crichton                     "Failed to parse stats file, path: {}, err: {}",
74908f9eb17SAlex Crichton                     path.display(),
75008f9eb17SAlex Crichton                     err,
75108f9eb17SAlex Crichton                 )
75208f9eb17SAlex Crichton             })
75308f9eb17SAlex Crichton         })
75408f9eb17SAlex Crichton         .ok()
75508f9eb17SAlex Crichton }
75608f9eb17SAlex Crichton 
write_stats_file(path: &Path, stats: &ModuleCacheStatistics) -> bool75708f9eb17SAlex Crichton fn write_stats_file(path: &Path, stats: &ModuleCacheStatistics) -> bool {
75808f9eb17SAlex Crichton     toml::to_string_pretty(&stats)
75908f9eb17SAlex Crichton         .map_err(|err| {
76008f9eb17SAlex Crichton             warn!(
76108f9eb17SAlex Crichton                 "Failed to serialize stats file, path: {}, err: {}",
76208f9eb17SAlex Crichton                 path.display(),
76308f9eb17SAlex Crichton                 err
76408f9eb17SAlex Crichton             )
76508f9eb17SAlex Crichton         })
76608f9eb17SAlex Crichton         .and_then(|serialized| {
7671ced2ef4SXinzhao Xu             fs_write_atomic(path, "stats", serialized.as_bytes()).map_err(|_| ())
76808f9eb17SAlex Crichton         })
76908f9eb17SAlex Crichton         .is_ok()
77008f9eb17SAlex Crichton }
77108f9eb17SAlex Crichton 
77208f9eb17SAlex Crichton /// Tries to acquire a lock for specific task.
77308f9eb17SAlex Crichton ///
77408f9eb17SAlex Crichton /// Returns Some(path) to the lock if succeeds. The task path must not
77508f9eb17SAlex Crichton /// contain any extension and have file stem.
77608f9eb17SAlex Crichton ///
77708f9eb17SAlex Crichton /// To release a lock you need either manually rename or remove it,
77808f9eb17SAlex Crichton /// or wait until it expires and cleanup task removes it.
77908f9eb17SAlex Crichton ///
78008f9eb17SAlex Crichton /// Note: this function is racy. Main idea is: be fault tolerant and
78108f9eb17SAlex Crichton ///       never block some task. The price is that we rarely do some task
78208f9eb17SAlex Crichton ///       more than once.
acquire_task_fs_lock( task_path: &Path, timeout: Duration, allowed_future_drift: Duration, ) -> Option<PathBuf>78308f9eb17SAlex Crichton fn acquire_task_fs_lock(
78408f9eb17SAlex Crichton     task_path: &Path,
78508f9eb17SAlex Crichton     timeout: Duration,
78608f9eb17SAlex Crichton     allowed_future_drift: Duration,
78708f9eb17SAlex Crichton ) -> Option<PathBuf> {
78808f9eb17SAlex Crichton     assert!(task_path.extension().is_none());
78908f9eb17SAlex Crichton     assert!(task_path.file_stem().is_some());
79008f9eb17SAlex Crichton 
79108f9eb17SAlex Crichton     // list directory
79208f9eb17SAlex Crichton     let dir_path = task_path.parent()?;
79308f9eb17SAlex Crichton     let it = fs::read_dir(dir_path)
79408f9eb17SAlex Crichton         .map_err(|err| {
79508f9eb17SAlex Crichton             warn!(
79608f9eb17SAlex Crichton                 "Failed to list cache directory, path: {}, err: {}",
79708f9eb17SAlex Crichton                 dir_path.display(),
79808f9eb17SAlex Crichton                 err
79908f9eb17SAlex Crichton             )
80008f9eb17SAlex Crichton         })
80108f9eb17SAlex Crichton         .ok()?;
80208f9eb17SAlex Crichton 
80308f9eb17SAlex Crichton     // look for existing locks
80408f9eb17SAlex Crichton     for entry in it {
80508f9eb17SAlex Crichton         let entry = entry
80608f9eb17SAlex Crichton             .map_err(|err| {
80708f9eb17SAlex Crichton                 warn!(
80808f9eb17SAlex Crichton                     "Failed to list cache directory, path: {}, err: {}",
80908f9eb17SAlex Crichton                     dir_path.display(),
81008f9eb17SAlex Crichton                     err
81108f9eb17SAlex Crichton                 )
81208f9eb17SAlex Crichton             })
81308f9eb17SAlex Crichton             .ok()?;
81408f9eb17SAlex Crichton 
81508f9eb17SAlex Crichton         let path = entry.path();
81608f9eb17SAlex Crichton         if path.is_dir() || path.file_stem() != task_path.file_stem() {
81708f9eb17SAlex Crichton             continue;
81808f9eb17SAlex Crichton         }
81908f9eb17SAlex Crichton 
82008f9eb17SAlex Crichton         // check extension and mtime
82108f9eb17SAlex Crichton         match path.extension() {
82208f9eb17SAlex Crichton             None => continue,
82308f9eb17SAlex Crichton             Some(ext) => {
82408f9eb17SAlex Crichton                 if let Some(ext_str) = ext.to_str() {
82508f9eb17SAlex Crichton                     // if it's None, i.e. not valid UTF-8 string, then that's not our lock for sure
82608f9eb17SAlex Crichton                     if ext_str.starts_with("wip-")
82708f9eb17SAlex Crichton                         && !is_fs_lock_expired(Some(&entry), &path, timeout, allowed_future_drift)
82808f9eb17SAlex Crichton                     {
82908f9eb17SAlex Crichton                         return None;
83008f9eb17SAlex Crichton                     }
83108f9eb17SAlex Crichton                 }
83208f9eb17SAlex Crichton             }
83308f9eb17SAlex Crichton         }
83408f9eb17SAlex Crichton     }
83508f9eb17SAlex Crichton 
83608f9eb17SAlex Crichton     // create the lock
83708f9eb17SAlex Crichton     let lock_path = task_path.with_extension(format!("wip-{}", std::process::id()));
83808f9eb17SAlex Crichton     let _file = fs::OpenOptions::new()
83908f9eb17SAlex Crichton         .create_new(true)
84008f9eb17SAlex Crichton         .write(true)
84108f9eb17SAlex Crichton         .open(&lock_path)
84208f9eb17SAlex Crichton         .map_err(|err| {
84308f9eb17SAlex Crichton             warn!(
84408f9eb17SAlex Crichton                 "Failed to create lock file (note: it shouldn't exists): path: {}, err: {}",
84508f9eb17SAlex Crichton                 lock_path.display(),
84608f9eb17SAlex Crichton                 err
84708f9eb17SAlex Crichton             )
84808f9eb17SAlex Crichton         })
84908f9eb17SAlex Crichton         .ok()?;
85008f9eb17SAlex Crichton 
85108f9eb17SAlex Crichton     Some(lock_path)
85208f9eb17SAlex Crichton }
85308f9eb17SAlex Crichton 
85408f9eb17SAlex Crichton // we have either both, or just path; dir entry is desirable since on some platforms we can get
85508f9eb17SAlex Crichton // metadata without extra syscalls
8560e9121daSFrankReh // furthermore: it's better to get a path if we have it instead of allocating a new one from the dir entry
is_fs_lock_expired( entry: Option<&fs::DirEntry>, path: &PathBuf, threshold: Duration, allowed_future_drift: Duration, ) -> bool85708f9eb17SAlex Crichton fn is_fs_lock_expired(
85808f9eb17SAlex Crichton     entry: Option<&fs::DirEntry>,
85908f9eb17SAlex Crichton     path: &PathBuf,
86008f9eb17SAlex Crichton     threshold: Duration,
86108f9eb17SAlex Crichton     allowed_future_drift: Duration,
86208f9eb17SAlex Crichton ) -> bool {
86308f9eb17SAlex Crichton     let mtime = match entry
86408f9eb17SAlex Crichton         .map_or_else(|| path.metadata(), |e| e.metadata())
86508f9eb17SAlex Crichton         .and_then(|metadata| metadata.modified())
86608f9eb17SAlex Crichton     {
86708f9eb17SAlex Crichton         Ok(mt) => mt,
86808f9eb17SAlex Crichton         Err(err) => {
86908f9eb17SAlex Crichton             warn!(
87008f9eb17SAlex Crichton                 "Failed to get metadata/mtime, treating as an expired lock, path: {}, err: {}",
87108f9eb17SAlex Crichton                 path.display(),
87208f9eb17SAlex Crichton                 err
87308f9eb17SAlex Crichton             );
87408f9eb17SAlex Crichton             return true; // can't read mtime, treat as expired, so this task will not be starved
87508f9eb17SAlex Crichton         }
87608f9eb17SAlex Crichton     };
87708f9eb17SAlex Crichton 
87808f9eb17SAlex Crichton     // DON'T use: mtime.elapsed() -- we must call SystemTime directly for the tests to be deterministic
87908f9eb17SAlex Crichton     match SystemTime::now().duration_since(mtime) {
88008f9eb17SAlex Crichton         Ok(elapsed) => elapsed >= threshold,
88108f9eb17SAlex Crichton         Err(err) => {
88208f9eb17SAlex Crichton             trace!(
88308f9eb17SAlex Crichton                 "Found mtime in the future, treating as a not expired lock, path: {}, err: {}",
88408f9eb17SAlex Crichton                 path.display(),
88508f9eb17SAlex Crichton                 err
88608f9eb17SAlex Crichton             );
88708f9eb17SAlex Crichton             // the lock is expired if the time is too far in the future
88808f9eb17SAlex Crichton             // it is fine to have network share and not synchronized clocks,
88908f9eb17SAlex Crichton             // but it's not good when user changes time in their system clock
89008f9eb17SAlex Crichton             err.duration() > allowed_future_drift
89108f9eb17SAlex Crichton         }
89208f9eb17SAlex Crichton     }
89308f9eb17SAlex Crichton }
89408f9eb17SAlex Crichton 
89508f9eb17SAlex Crichton #[cfg(test)]
89608f9eb17SAlex Crichton mod tests;
897