From 4a7de19c972d4bc30fe413f902a6bb8c077bee2f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 23 Apr 2025 16:46:21 +0200 Subject: [PATCH 01/14] cache: Add builder pattern for CacheConfig --- crates/cache/src/config.rs | 220 ++++++++++++++++++++++++++++--- crates/cache/src/config/tests.rs | 107 +++++++++++++++ crates/cache/src/lib.rs | 2 +- 3 files changed, 312 insertions(+), 17 deletions(-) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index 62fc6e936f8e..da94d0763b03 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -301,6 +301,10 @@ impl CacheConfig { generate_setting_getter!(file_count_limit_percent_if_deleting: u8); generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); + pub fn builder() -> CacheConfigBuilder { + CacheConfigBuilder::default() + } + /// Returns true if and only if the cache is enabled. pub fn enabled(&self) -> bool { self.enabled @@ -345,22 +349,7 @@ impl CacheConfig { /// Parses cache configuration from the file specified pub fn from_file(config_file: Option<&Path>) -> Result { let mut config = Self::load_and_parse_file(config_file)?; - - // validate values and fill in defaults - config.validate_directory_or_default()?; - config.validate_worker_event_queue_size_or_default(); - config.validate_baseline_compression_level_or_default()?; - config.validate_optimized_compression_level_or_default()?; - config.validate_optimized_compression_usage_counter_threshold_or_default(); - config.validate_cleanup_interval_or_default(); - config.validate_optimizing_compression_task_timeout_or_default(); - config.validate_allowed_clock_drift_for_files_from_future_or_default(); - config.validate_file_count_soft_limit_or_default(); - config.validate_files_total_size_soft_limit_or_default(); - config.validate_file_count_limit_percent_if_deleting_or_default()?; - config.validate_files_total_size_limit_percent_if_deleting_or_default()?; - config.spawn_worker(); - + config.validate_or_default()?; Ok(config) } @@ -420,6 +409,24 @@ impl CacheConfig { } } + /// validate values and fill in defaults + fn validate_or_default(&mut self) -> Result<()> { + self.validate_directory_or_default()?; + self.validate_worker_event_queue_size_or_default(); + self.validate_baseline_compression_level_or_default()?; + self.validate_optimized_compression_level_or_default()?; + self.validate_optimized_compression_usage_counter_threshold_or_default(); + self.validate_cleanup_interval_or_default(); + self.validate_optimizing_compression_task_timeout_or_default(); + self.validate_allowed_clock_drift_for_files_from_future_or_default(); + self.validate_file_count_soft_limit_or_default(); + self.validate_files_total_size_soft_limit_or_default(); + self.validate_file_count_limit_percent_if_deleting_or_default()?; + self.validate_files_total_size_limit_percent_if_deleting_or_default()?; + self.spawn_worker(); + Ok(()) + } + fn validate_directory_or_default(&mut self) -> Result<()> { if self.directory.is_none() { match project_dirs() { @@ -579,6 +586,187 @@ impl CacheConfig { } } +/// A builder for `CacheConfig`s. +#[derive(Debug, Clone, Default)] +pub struct CacheConfigBuilder { + enabled: bool, + directory: Option, + worker_event_queue_size: Option, + baseline_compression_level: Option, + optimized_compression_level: Option, + optimized_compression_usage_counter_threshold: Option, + cleanup_interval: Option, + optimizing_compression_task_timeout: Option, + allowed_clock_drift_for_files_from_future: Option, + file_count_soft_limit: Option, + files_total_size_soft_limit: Option, + file_count_limit_percent_if_deleting: Option, + files_total_size_limit_percent_if_deleting: Option, +} + +impl CacheConfigBuilder { + /// Specifies whether the cache system is used or not. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Specifies where the cache directory is. Must be an absolute path. + pub fn directory(mut self, directory: impl Into) -> Self { + self.directory = Some(directory.into()); + self + } + + /// Size of cache worker event queue. If the queue is full, incoming cache usage events will be + /// dropped. + pub fn worker_event_queue_size(mut self, size: u64) -> Self { + self.worker_event_queue_size = Some(size); + self + } + + /// Compression level used when a new cache file is being written by the cache system. Wasmtime + /// uses zstd compression. + pub fn baseline_compression_level(mut self, level: i32) -> Self { + self.baseline_compression_level = Some(level); + self + } + + /// Compression level used when the cache worker decides to recompress a cache file. Wasmtime + /// uses zstd compression. + pub fn optimized_compression_level(mut self, level: i32) -> Self { + self.optimized_compression_level = Some(level); + self + } + + /// One of the conditions for the cache worker to recompress a cache file is to have usage + /// count of the file exceeding this threshold. + pub fn optimized_compression_usage_counter_threshold(mut self, threshold: u64) -> Self { + self.optimized_compression_usage_counter_threshold = Some(threshold); + self + } + + /// When the cache worker is notified about a cache file being updated by the cache system and + /// this interval has already passed since last cleaning up, the worker will attempt a new + /// cleanup. + pub fn cleanup_interval(mut self, interval: Duration) -> Self { + self.cleanup_interval = Some(interval); + self + } + + /// When the cache worker decides to recompress a cache file, it makes sure that no other + /// worker has started the task for this file within the last + /// optimizing-compression-task-timeout interval. If some worker has started working on it, + /// other workers are skipping this task. + pub fn optimizing_compression_task_timeout(mut self, timeout: Duration) -> Self { + self.optimizing_compression_task_timeout = Some(timeout); + self + } + + /// ### Locks + /// + /// When the cache worker attempts acquiring a lock for some task, it checks if some other + /// worker has already acquired such a lock. To be fault tolerant and eventually execute every + /// task, the locks expire after some interval. However, because of clock drifts and different + /// timezones, it would happen that some lock was created in the future. This setting defines a + /// tolerance limit for these locks. If the time has been changed in the system (i.e. two years + /// backwards), the cache system should still work properly. Thus, these locks will be treated + /// as expired (assuming the tolerance is not too big). + /// + /// ### Cache files + /// + /// Similarly to the locks, the cache files or their metadata might have modification time in + /// distant future. The cache system tries to keep these files as long as possible. If the + /// limits are not reached, the cache files will not be deleted. Otherwise, they will be + /// treated as the oldest files, so they might survive. If the user actually uses the cache + /// file, the modification time will be updated. + pub fn allowed_clock_drift_for_files_from_future(mut self, drift: Duration) -> Self { + self.allowed_clock_drift_for_files_from_future = Some(drift); + self + } + + /// Soft limit for the file count in the cache directory. + /// + /// This doesn't include files with metadata. To learn more, please refer to the cache system + /// section. + pub fn file_count_soft_limit(mut self, limit: u64) -> Self { + self.file_count_soft_limit = Some(limit); + self + } + + /// Soft limit for the total size* of files in the cache directory. + /// + /// This doesn't include files with metadata. To learn more, please refer to the cache system + /// section. + /// + /// *this is the file size, not the space physically occupied on the disk. + pub fn files_total_size_soft_limit(mut self, limit: u64) -> Self { + self.files_total_size_soft_limit = Some(limit); + self + } + + /// If file-count-soft-limit is exceeded and the cache worker performs the cleanup task, then + /// the worker will delete some cache files, so after the task, the file count should not + /// exceed file-count-soft-limit * file-count-limit-percent-if-deleting. + /// + /// This doesn't include files with metadata. To learn more, please refer to the cache system + /// section. + pub fn file_count_limit_percent_if_deleting(mut self, percent: u8) -> Self { + self.file_count_limit_percent_if_deleting = Some(percent); + self + } + + /// If files-total-size-soft-limit is exceeded and cache worker performs the cleanup task, then + /// the worker will delete some cache files, so after the task, the files total size should not + /// exceed files-total-size-soft-limit * files-total-size-limit-percent-if-deleting. + /// + /// This doesn't include files with metadata. To learn more, please refer to the cache system + /// section. + pub fn files_total_size_limit_percent_if_deleting(mut self, percent: u8) -> Self { + self.files_total_size_limit_percent_if_deleting = Some(percent); + self + } + + pub fn build(self) -> Result { + let CacheConfigBuilder { + enabled, + directory, + worker_event_queue_size, + baseline_compression_level, + optimized_compression_level, + optimized_compression_usage_counter_threshold, + cleanup_interval, + optimizing_compression_task_timeout, + allowed_clock_drift_for_files_from_future, + file_count_soft_limit, + files_total_size_soft_limit, + file_count_limit_percent_if_deleting, + files_total_size_limit_percent_if_deleting, + } = self; + + let mut config = CacheConfig { + enabled, + directory, + worker_event_queue_size, + baseline_compression_level, + optimized_compression_level, + optimized_compression_usage_counter_threshold, + cleanup_interval, + optimizing_compression_task_timeout, + allowed_clock_drift_for_files_from_future, + file_count_soft_limit, + files_total_size_soft_limit, + file_count_limit_percent_if_deleting, + files_total_size_limit_percent_if_deleting, + worker: None, + state: Arc::new(CacheState::default()), + }; + + config.validate_or_default()?; + + Ok(config) + } +} + #[cfg(test)] #[macro_use] pub mod tests; diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index 9eae5e8657c2..cba9ba075e31 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -517,3 +517,110 @@ fn test_percent_settings() { cd ); } + +/// Default builder produces a disabled cache configuration with the same defaults. +#[test] +fn test_builder_default() { + let dir = tempfile::tempdir().expect("Can't create temporary directory"); + let config_path = dir.path().join("cache-config.toml"); + let config_content = "[cache]\n\ + enabled = false\n"; + fs::write(&config_path, config_content).expect("Failed to write test config file"); + let expected_config = CacheConfig::from_file(Some(&config_path)).unwrap(); + + let config = CacheConfig::builder() + .build() + .expect("Failed to build CacheConfig"); + + assert_eq!(config.enabled, expected_config.enabled); + assert_eq!(config.directory, expected_config.directory); + assert_eq!( + config.worker_event_queue_size, + expected_config.worker_event_queue_size + ); + assert_eq!( + config.baseline_compression_level, + expected_config.baseline_compression_level + ); + assert_eq!( + config.optimized_compression_level, + expected_config.optimized_compression_level + ); + assert_eq!( + config.optimized_compression_usage_counter_threshold, + expected_config.optimized_compression_usage_counter_threshold + ); + assert_eq!(config.cleanup_interval, expected_config.cleanup_interval); + assert_eq!( + config.optimizing_compression_task_timeout, + expected_config.optimizing_compression_task_timeout + ); + assert_eq!( + config.allowed_clock_drift_for_files_from_future, + expected_config.allowed_clock_drift_for_files_from_future + ); + assert_eq!( + config.file_count_soft_limit, + expected_config.file_count_soft_limit + ); + assert_eq!( + config.files_total_size_soft_limit, + expected_config.files_total_size_soft_limit + ); + assert_eq!( + config.file_count_limit_percent_if_deleting, + expected_config.file_count_limit_percent_if_deleting + ); + assert_eq!( + config.files_total_size_limit_percent_if_deleting, + expected_config.files_total_size_limit_percent_if_deleting + ); +} + +#[test] +fn test_builder_all_settings() { + let (_td, cd, _cp) = test_prolog(); + + let conf = CacheConfig::builder() + .enabled(true) + .directory(&cd) + .worker_event_queue_size(0x10) + .baseline_compression_level(3) + .optimized_compression_level(20) + .optimized_compression_usage_counter_threshold(0x100) + .cleanup_interval(Duration::from_secs(60 * 60)) + .optimizing_compression_task_timeout(Duration::from_secs(30 * 60)) + .allowed_clock_drift_for_files_from_future(Duration::from_secs(60 * 60 * 24)) + .file_count_soft_limit(0x10_000) + .files_total_size_soft_limit(512 * (1u64 << 20)) + .file_count_limit_percent_if_deleting(70) + .files_total_size_limit_percent_if_deleting(70) + .build() + .expect("Failed to build config"); + check_conf(&conf, &cd); + + fn check_conf(conf: &CacheConfig, cd: &PathBuf) { + assert!(conf.enabled()); + assert_eq!( + conf.directory(), + &fs::canonicalize(cd).expect("canonicalize failed") + ); + assert_eq!(conf.worker_event_queue_size(), 0x10); + assert_eq!(conf.baseline_compression_level(), 3); + assert_eq!(conf.optimized_compression_level(), 20); + assert_eq!(conf.optimized_compression_usage_counter_threshold(), 0x100); + assert_eq!(conf.cleanup_interval(), Duration::from_secs(60 * 60)); + assert_eq!( + conf.optimizing_compression_task_timeout(), + Duration::from_secs(30 * 60) + ); + assert_eq!( + conf.allowed_clock_drift_for_files_from_future(), + Duration::from_secs(60 * 60 * 24) + ); + assert_eq!(conf.file_count_soft_limit(), 0x10_000); + assert_eq!(conf.files_total_size_soft_limit(), 512 * (1u64 << 20)); + assert_eq!(conf.file_count_limit_percent_if_deleting(), 70); + assert_eq!(conf.files_total_size_limit_percent_if_deleting(), 70); + } +} diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index 88dbcfd953e1..e63955f0bedb 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -12,7 +12,7 @@ use std::{fs, io}; mod config; mod worker; -pub use config::{create_new_config, CacheConfig}; +pub use config::{create_new_config, CacheConfig, CacheConfigBuilder}; use worker::Worker; /// Module level cache entry. From de897281a06e624ed471c0e9e25fe0459f3af28b Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 24 Apr 2025 11:19:01 +0200 Subject: [PATCH 02/14] wasmtime: Add cache_config method to wasmtime::Config --- crates/wasmtime/src/config.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 094d5ee9c102..51421a319a24 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -6,8 +6,6 @@ use core::str::FromStr; #[cfg(any(feature = "cache", feature = "cranelift", feature = "winch"))] use std::path::Path; use wasmparser::WasmFeatures; -#[cfg(feature = "cache")] -use wasmtime_cache::CacheConfig; use wasmtime_environ::{ConfigTunables, TripleExt, Tunables}; #[cfg(feature = "runtime")] @@ -28,6 +26,8 @@ use wasmtime_fiber::RuntimeFiberStackCreator; #[cfg(feature = "runtime")] pub use crate::runtime::code_memory::CustomCodeMemory; +#[cfg(feature = "cache")] +pub use wasmtime_cache::{CacheConfig, CacheConfigBuilder}; #[cfg(all(feature = "incremental-cache", feature = "cranelift"))] pub use wasmtime_environ::CacheStore; @@ -1369,6 +1369,23 @@ impl Config { self } + /// Set a custom cache configuration. + /// + /// If you want to load the cache configuration from a file, use [`Config::cache_config_load`] + /// or [`Config::cache_config_load_default`] for the default enabled configuration. + /// + /// By default cache configuration is not enabled or loaded. + /// + /// This method is only available when the `cache` feature of this crate is + /// enabled. + /// + /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html + #[cfg(feature = "cache")] + pub fn cache_config(&mut self, cache_config: CacheConfig) -> &mut Self { + self.cache_config = cache_config; + self + } + /// Loads cache configuration specified at `path`. /// /// This method will read the file specified by `path` on the filesystem and From 5c6300ba024d1126f992a266e71e95eb5b579368 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 24 Apr 2025 11:29:27 +0200 Subject: [PATCH 03/14] Refactor test_builder_default to use test_prolog helper --- crates/cache/src/config/tests.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index cba9ba075e31..3a5bf031333d 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -521,12 +521,11 @@ fn test_percent_settings() { /// Default builder produces a disabled cache configuration with the same defaults. #[test] fn test_builder_default() { - let dir = tempfile::tempdir().expect("Can't create temporary directory"); - let config_path = dir.path().join("cache-config.toml"); + let (_td, _cd, cp) = test_prolog(); let config_content = "[cache]\n\ enabled = false\n"; - fs::write(&config_path, config_content).expect("Failed to write test config file"); - let expected_config = CacheConfig::from_file(Some(&config_path)).unwrap(); + fs::write(&cp, config_content).expect("Failed to write test config file"); + let expected_config = CacheConfig::from_file(Some(&cp)).unwrap(); let config = CacheConfig::builder() .build() From ffe5ab501c93cadc914c1aa76d18410ca8bd2a84 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 25 Apr 2025 22:53:59 +0200 Subject: [PATCH 04/14] Remove enabled option from CacheConfigBuilder and always set to true --- crates/cache/src/config.rs | 10 +--------- crates/cache/src/config/tests.rs | 3 +-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index da94d0763b03..da24e0d7a40a 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -589,7 +589,6 @@ impl CacheConfig { /// A builder for `CacheConfig`s. #[derive(Debug, Clone, Default)] pub struct CacheConfigBuilder { - enabled: bool, directory: Option, worker_event_queue_size: Option, baseline_compression_level: Option, @@ -605,12 +604,6 @@ pub struct CacheConfigBuilder { } impl CacheConfigBuilder { - /// Specifies whether the cache system is used or not. - pub fn enabled(mut self, enabled: bool) -> Self { - self.enabled = enabled; - self - } - /// Specifies where the cache directory is. Must be an absolute path. pub fn directory(mut self, directory: impl Into) -> Self { self.directory = Some(directory.into()); @@ -728,7 +721,6 @@ impl CacheConfigBuilder { pub fn build(self) -> Result { let CacheConfigBuilder { - enabled, directory, worker_event_queue_size, baseline_compression_level, @@ -744,7 +736,7 @@ impl CacheConfigBuilder { } = self; let mut config = CacheConfig { - enabled, + enabled: true, directory, worker_event_queue_size, baseline_compression_level, diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index 3a5bf031333d..13c71ceec1da 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -523,7 +523,7 @@ fn test_percent_settings() { fn test_builder_default() { let (_td, _cd, cp) = test_prolog(); let config_content = "[cache]\n\ - enabled = false\n"; + enabled = true\n"; fs::write(&cp, config_content).expect("Failed to write test config file"); let expected_config = CacheConfig::from_file(Some(&cp)).unwrap(); @@ -581,7 +581,6 @@ fn test_builder_all_settings() { let (_td, cd, _cp) = test_prolog(); let conf = CacheConfig::builder() - .enabled(true) .directory(&cd) .worker_event_queue_size(0x10) .baseline_compression_level(3) From c7cd6444fc2401eef8838d53aa42cb2d1d048169 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 25 Apr 2025 23:01:47 +0200 Subject: [PATCH 05/14] Change builder methods to take &mut self and return &mut Self --- crates/cache/src/config.rs | 24 ++++++++++++------------ crates/cache/src/config/tests.rs | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index da24e0d7a40a..009dd8c8d075 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -605,35 +605,35 @@ pub struct CacheConfigBuilder { impl CacheConfigBuilder { /// Specifies where the cache directory is. Must be an absolute path. - pub fn directory(mut self, directory: impl Into) -> Self { + pub fn directory(&mut self, directory: impl Into) -> &mut Self { self.directory = Some(directory.into()); self } /// Size of cache worker event queue. If the queue is full, incoming cache usage events will be /// dropped. - pub fn worker_event_queue_size(mut self, size: u64) -> Self { + pub fn worker_event_queue_size(&mut self, size: u64) -> &mut Self { self.worker_event_queue_size = Some(size); self } /// Compression level used when a new cache file is being written by the cache system. Wasmtime /// uses zstd compression. - pub fn baseline_compression_level(mut self, level: i32) -> Self { + pub fn baseline_compression_level(&mut self, level: i32) -> &mut Self { self.baseline_compression_level = Some(level); self } /// Compression level used when the cache worker decides to recompress a cache file. Wasmtime /// uses zstd compression. - pub fn optimized_compression_level(mut self, level: i32) -> Self { + pub fn optimized_compression_level(&mut self, level: i32) -> &mut Self { self.optimized_compression_level = Some(level); self } /// One of the conditions for the cache worker to recompress a cache file is to have usage /// count of the file exceeding this threshold. - pub fn optimized_compression_usage_counter_threshold(mut self, threshold: u64) -> Self { + pub fn optimized_compression_usage_counter_threshold(&mut self, threshold: u64) -> &mut Self { self.optimized_compression_usage_counter_threshold = Some(threshold); self } @@ -641,7 +641,7 @@ impl CacheConfigBuilder { /// When the cache worker is notified about a cache file being updated by the cache system and /// this interval has already passed since last cleaning up, the worker will attempt a new /// cleanup. - pub fn cleanup_interval(mut self, interval: Duration) -> Self { + pub fn cleanup_interval(&mut self, interval: Duration) -> &mut Self { self.cleanup_interval = Some(interval); self } @@ -650,7 +650,7 @@ impl CacheConfigBuilder { /// worker has started the task for this file within the last /// optimizing-compression-task-timeout interval. If some worker has started working on it, /// other workers are skipping this task. - pub fn optimizing_compression_task_timeout(mut self, timeout: Duration) -> Self { + pub fn optimizing_compression_task_timeout(&mut self, timeout: Duration) -> &mut Self { self.optimizing_compression_task_timeout = Some(timeout); self } @@ -672,7 +672,7 @@ impl CacheConfigBuilder { /// limits are not reached, the cache files will not be deleted. Otherwise, they will be /// treated as the oldest files, so they might survive. If the user actually uses the cache /// file, the modification time will be updated. - pub fn allowed_clock_drift_for_files_from_future(mut self, drift: Duration) -> Self { + pub fn allowed_clock_drift_for_files_from_future(&mut self, drift: Duration) -> &mut Self { self.allowed_clock_drift_for_files_from_future = Some(drift); self } @@ -681,7 +681,7 @@ impl CacheConfigBuilder { /// /// This doesn't include files with metadata. To learn more, please refer to the cache system /// section. - pub fn file_count_soft_limit(mut self, limit: u64) -> Self { + pub fn file_count_soft_limit(&mut self, limit: u64) -> &mut Self { self.file_count_soft_limit = Some(limit); self } @@ -692,7 +692,7 @@ impl CacheConfigBuilder { /// section. /// /// *this is the file size, not the space physically occupied on the disk. - pub fn files_total_size_soft_limit(mut self, limit: u64) -> Self { + pub fn files_total_size_soft_limit(&mut self, limit: u64) -> &mut Self { self.files_total_size_soft_limit = Some(limit); self } @@ -703,7 +703,7 @@ impl CacheConfigBuilder { /// /// This doesn't include files with metadata. To learn more, please refer to the cache system /// section. - pub fn file_count_limit_percent_if_deleting(mut self, percent: u8) -> Self { + pub fn file_count_limit_percent_if_deleting(&mut self, percent: u8) -> &mut Self { self.file_count_limit_percent_if_deleting = Some(percent); self } @@ -714,7 +714,7 @@ impl CacheConfigBuilder { /// /// This doesn't include files with metadata. To learn more, please refer to the cache system /// section. - pub fn files_total_size_limit_percent_if_deleting(mut self, percent: u8) -> Self { + pub fn files_total_size_limit_percent_if_deleting(&mut self, percent: u8) -> &mut Self { self.files_total_size_limit_percent_if_deleting = Some(percent); self } diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index 13c71ceec1da..e202e13e71e4 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -580,7 +580,8 @@ fn test_builder_default() { fn test_builder_all_settings() { let (_td, cd, _cp) = test_prolog(); - let conf = CacheConfig::builder() + let mut builder = CacheConfig::builder(); + builder .directory(&cd) .worker_event_queue_size(0x10) .baseline_compression_level(3) @@ -592,9 +593,8 @@ fn test_builder_all_settings() { .file_count_soft_limit(0x10_000) .files_total_size_soft_limit(512 * (1u64 << 20)) .file_count_limit_percent_if_deleting(70) - .files_total_size_limit_percent_if_deleting(70) - .build() - .expect("Failed to build config"); + .files_total_size_limit_percent_if_deleting(70); + let conf = builder.build().expect("Failed to build config"); check_conf(&conf, &cd); fn check_conf(conf: &CacheConfig, cd: &PathBuf) { From 4b357c0e6220b0cc1920e6666d725951d4cf8354 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Sun, 27 Apr 2025 16:39:53 +0200 Subject: [PATCH 06/14] Simplify cache configuration API A new `cache_config(Option)` method replaces multiple methods for controlling module caching. Now `None` disables caching, and users can directly provide a cache config or load one from a file. --- crates/bench-api/src/lib.rs | 2 +- crates/c-api/src/config.rs | 9 ++- crates/cache/src/config.rs | 18 ++++- crates/cli-flags/src/lib.rs | 6 +- crates/wasmtime/src/config.rs | 79 +++------------------ crates/wasmtime/src/engine/serialization.rs | 13 ++-- examples/fast_compilation.rs | 4 +- src/commands/explore.rs | 2 +- 8 files changed, 47 insertions(+), 86 deletions(-) diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index ff6b88be310a..dcabf871f1c1 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -436,7 +436,7 @@ impl BenchState { ) -> Result { let mut config = options.config(None)?; // NB: always disable the compilation cache. - config.disable_cache(); + config.cache_config(None); let engine = Engine::new(&config)?; let mut linker = Linker::::new(&engine); diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs index 53baad88152c..65698b154b48 100644 --- a/crates/c-api/src/config.rs +++ b/crates/c-api/src/config.rs @@ -211,12 +211,17 @@ pub unsafe extern "C" fn wasmtime_config_cache_config_load( c: &mut wasm_config_t, filename: *const c_char, ) -> Option> { + use std::path::Path; + + use wasmtime::CacheConfig; + handle_result( if filename.is_null() { - c.config.cache_config_load_default() + CacheConfig::from_file(None).map(|cfg| c.config.cache_config(Some(cfg))) } else { match CStr::from_ptr(filename).to_str() { - Ok(s) => c.config.cache_config_load(s), + Ok(s) => CacheConfig::from_file(Some(&Path::new(s))) + .map(|cfg| c.config.cache_config(Some(cfg))), Err(e) => Err(e.into()), } }, diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index 009dd8c8d075..3c8bbb7a95d9 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -346,7 +346,23 @@ impl CacheConfig { conf } - /// Parses cache configuration from the file specified + /// Loads cache configuration specified at `path`. + /// + /// This method will read the file specified by `path` on the filesystem and + /// attempt to load cache configuration from it. This method can also fail + /// due to I/O errors, misconfiguration, syntax errors, etc. For expected + /// syntax in the configuration file see the [documentation online][docs]. + /// + /// Passing in `None` loads cache configuration from the system default path. + /// This is located, for example, on Unix at `$HOME/.config/wasmtime/config.toml` + /// and is typically created with the `wasmtime config new` command. + /// + /// # Errors + /// + /// This method can fail due to any error that happens when loading the file + /// pointed to by `path` and attempting to load the cache configuration. + /// + /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html pub fn from_file(config_file: Option<&Path>) -> Result { let mut config = Self::load_and_parse_file(config_file)?; config.validate_or_default()?; diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 6d90e08185f0..beba311db950 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -737,10 +737,12 @@ impl CommonOptions { if self.codegen.cache != Some(false) { match &self.codegen.cache_config { Some(path) => { - config.cache_config_load(path)?; + config.cache_config(Some(wasmtime::CacheConfig::from_file(Some(Path::new( + path, + )))?)); } None => { - config.cache_config_load_default()?; + config.cache_config(Some(wasmtime::CacheConfig::from_file(None)?)); } } } diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 51421a319a24..b5457cbb6306 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1371,88 +1371,25 @@ impl Config { /// Set a custom cache configuration. /// - /// If you want to load the cache configuration from a file, use [`Config::cache_config_load`] - /// or [`Config::cache_config_load_default`] for the default enabled configuration. + /// If you want to load the cache configuration from a file, use [`CacheConfig::from_file`]. + /// You can call [`CacheConfig::from_file(None)`] for the default, enabled configuration. /// - /// By default cache configuration is not enabled or loaded. + /// If you want to disable the cache, you can call this method with `None`. /// - /// This method is only available when the `cache` feature of this crate is - /// enabled. - /// - /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html - #[cfg(feature = "cache")] - pub fn cache_config(&mut self, cache_config: CacheConfig) -> &mut Self { - self.cache_config = cache_config; - self - } - - /// Loads cache configuration specified at `path`. - /// - /// This method will read the file specified by `path` on the filesystem and - /// attempt to load cache configuration from it. This method can also fail - /// due to I/O errors, misconfiguration, syntax errors, etc. For expected - /// syntax in the configuration file see the [documentation online][docs]. - /// - /// By default cache configuration is not enabled or loaded. + /// By default, new configs do not have caching enabled. + /// Every call to [`Module::new(my_wasm)`][crate::Module::new] will recompile `my_wasm`, + /// even when it is unchanged, unless an enabled `CacheConfig` is provided. /// /// This method is only available when the `cache` feature of this crate is /// enabled. /// - /// # Errors - /// - /// This method can fail due to any error that happens when loading the file - /// pointed to by `path` and attempting to load the cache configuration. - /// /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html #[cfg(feature = "cache")] - pub fn cache_config_load(&mut self, path: impl AsRef) -> Result<&mut Self> { - self.cache_config = CacheConfig::from_file(Some(path.as_ref()))?; - Ok(self) - } - - /// Disable caching. - /// - /// Every call to [`Module::new(my_wasm)`][crate::Module::new] will - /// recompile `my_wasm`, even when it is unchanged. - /// - /// By default, new configs do not have caching enabled. This method is only - /// useful for disabling a previous cache configuration. - /// - /// This method is only available when the `cache` feature of this crate is - /// enabled. - #[cfg(feature = "cache")] - pub fn disable_cache(&mut self) -> &mut Self { - self.cache_config = CacheConfig::new_cache_disabled(); + pub fn cache_config(&mut self, cache_config: Option) -> &mut Self { + self.cache_config = cache_config.unwrap_or_else(|| CacheConfig::new_cache_disabled()); self } - /// Loads cache configuration from the system default path. - /// - /// This commit is the same as [`Config::cache_config_load`] except that it - /// does not take a path argument and instead loads the default - /// configuration present on the system. This is located, for example, on - /// Unix at `$HOME/.config/wasmtime/config.toml` and is typically created - /// with the `wasmtime config new` command. - /// - /// By default cache configuration is not enabled or loaded. - /// - /// This method is only available when the `cache` feature of this crate is - /// enabled. - /// - /// # Errors - /// - /// This method can fail due to any error that happens when loading the - /// default system configuration. Note that it is not an error if the - /// default config file does not exist, in which case the default settings - /// for an enabled cache are applied. - /// - /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html - #[cfg(feature = "cache")] - pub fn cache_config_load_default(&mut self) -> Result<&mut Self> { - self.cache_config = CacheConfig::from_file(None)?; - Ok(self) - } - /// Sets a custom memory creator. /// /// Custom memory creators are used when creating host `Memory` objects or when diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 0f4a7b08de9a..5f134ff8db64 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -446,7 +446,7 @@ impl Metadata<'_> { #[cfg(test)] mod test { use super::*; - use crate::{Config, Module, OptLevel}; + use crate::{CacheConfig, Config, Module, OptLevel}; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, @@ -664,7 +664,7 @@ Caused by: )?; let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::None) - .cache_config_load(&config_path)?; + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); @@ -675,7 +675,7 @@ Caused by: let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::Speed) - .cache_config_load(&config_path)?; + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); @@ -686,7 +686,7 @@ Caused by: let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::SpeedAndSize) - .cache_config_load(&config_path)?; + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); @@ -696,7 +696,8 @@ Caused by: assert_eq!(engine.config().cache_config.cache_misses(), 1); let mut cfg = Config::new(); - cfg.debug_info(true).cache_config_load(&config_path)?; + cfg.debug_info(true) + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); @@ -771,7 +772,7 @@ Caused by: ), )?; let mut cfg = Config::new(); - cfg.cache_config_load(&config_path)?; + cfg.cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Component::new(&engine, "(component (core module (func)))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); diff --git a/examples/fast_compilation.rs b/examples/fast_compilation.rs index e6cf4c0e240d..969b6c15b78c 100644 --- a/examples/fast_compilation.rs +++ b/examples/fast_compilation.rs @@ -3,14 +3,14 @@ //! If your application design is compatible with pre-compiling Wasm programs, //! prefer doing that. -use wasmtime::{Config, Engine, Result, Strategy}; +use wasmtime::{CacheConfig, Config, Engine, Result, Strategy}; fn main() -> Result<()> { let mut config = Config::new(); // Enable the compilation cache, using the default cache configuration // settings. - config.cache_config_load_default()?; + config.cache_config(Some(CacheConfig::from_file(None)?)); // Enable Winch, Wasmtime's baseline compiler. config.strategy(Strategy::Winch); diff --git a/src/commands/explore.rs b/src/commands/explore.rs index 9fa9f3bdd321..b91b922beb13 100644 --- a/src/commands/explore.rs +++ b/src/commands/explore.rs @@ -51,7 +51,7 @@ impl ExploreCommand { let clif_dir = if let Some(Strategy::Cranelift) | None = self.common.codegen.compiler { let clif_dir = tempdir()?; config.emit_clif(clif_dir.path()); - config.disable_cache(); // cache does not emit clif + config.cache_config(None); // cache does not emit clif Some(clif_dir) } else { None From 4e35e2a8d33f9454a0396d303f8e1c92005500a0 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 28 Apr 2025 11:48:28 +0200 Subject: [PATCH 07/14] Make cache configuration optional --- crates/cache/src/lib.rs | 11 ++-- crates/wasmtime/src/config.rs | 6 +- crates/wasmtime/src/engine.rs | 4 +- crates/wasmtime/src/engine/serialization.rs | 65 ++++++++++++++------- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index e63955f0bedb..8ddc5e53da91 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -27,14 +27,13 @@ struct Sha256Hasher(Sha256); impl<'config> ModuleCacheEntry<'config> { /// Create the cache entry. - pub fn new(compiler_name: &str, cache_config: &'config CacheConfig) -> Self { - if cache_config.enabled() { - Self(Some(ModuleCacheEntryInner::new( + pub fn new(compiler_name: &str, cache_config: Option<&'config CacheConfig>) -> Self { + match cache_config { + Some(cache_config) if cache_config.enabled() => Self(Some(ModuleCacheEntryInner::new( compiler_name, cache_config, - ))) - } else { - Self(None) + ))), + Some(_) | None => Self(None), } } diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index b5457cbb6306..d9887bb541f1 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -128,7 +128,7 @@ pub struct Config { tunables: ConfigTunables, #[cfg(feature = "cache")] - pub(crate) cache_config: CacheConfig, + pub(crate) cache_config: Option, #[cfg(feature = "runtime")] pub(crate) mem_creator: Option>, #[cfg(feature = "runtime")] @@ -231,7 +231,7 @@ impl Config { #[cfg(feature = "gc")] collector: Collector::default(), #[cfg(feature = "cache")] - cache_config: CacheConfig::new_cache_disabled(), + cache_config: None, profiling_strategy: ProfilingStrategy::None, #[cfg(feature = "runtime")] mem_creator: None, @@ -1386,7 +1386,7 @@ impl Config { /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html #[cfg(feature = "cache")] pub fn cache_config(&mut self, cache_config: Option) -> &mut Self { - self.cache_config = cache_config.unwrap_or_else(|| CacheConfig::new_cache_disabled()); + self.cache_config = cache_config; self } diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index cfc7cb8416c8..4195cd067dd5 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -684,8 +684,8 @@ impl Engine { } #[cfg(all(feature = "cache", any(feature = "cranelift", feature = "winch")))] - pub(crate) fn cache_config(&self) -> &wasmtime_cache::CacheConfig { - &self.config().cache_config + pub(crate) fn cache_config(&self) -> Option<&wasmtime_cache::CacheConfig> { + self.config().cache_config.as_ref() } pub(crate) fn signatures(&self) -> &TypeRegistry { diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 5f134ff8db64..31c4648fe65a 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -667,44 +667,64 @@ Caused by: .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 0); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + let cache_config = engine + .config() + .cache_config + .as_ref() + .expect("Missing cache config"); + assert_eq!(cache_config.cache_hits(), 0); + assert_eq!(cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 1); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 1); + assert_eq!(cache_config.cache_misses(), 1); let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::Speed) .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; + let cache_config = engine + .config() + .cache_config + .as_ref() + .expect("Missing cache config"); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 0); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 0); + assert_eq!(cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 1); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 1); + assert_eq!(cache_config.cache_misses(), 1); let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::SpeedAndSize) .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; + let cache_config = engine + .config() + .cache_config + .as_ref() + .expect("Missing cache config"); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 0); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 0); + assert_eq!(cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 1); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 1); + assert_eq!(cache_config.cache_misses(), 1); let mut cfg = Config::new(); cfg.debug_info(true) .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; + let cache_config = engine + .config() + .cache_config + .as_ref() + .expect("Missing cache config"); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 0); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 0); + assert_eq!(cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 1); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 1); + assert_eq!(cache_config.cache_misses(), 1); Ok(()) } @@ -774,12 +794,17 @@ Caused by: let mut cfg = Config::new(); cfg.cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; + let cache_config = engine + .config() + .cache_config + .as_ref() + .expect("Missing cache config"); Component::new(&engine, "(component (core module (func)))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 0); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 0); + assert_eq!(cache_config.cache_misses(), 1); Component::new(&engine, "(component (core module (func)))")?; - assert_eq!(engine.config().cache_config.cache_hits(), 1); - assert_eq!(engine.config().cache_config.cache_misses(), 1); + assert_eq!(cache_config.cache_hits(), 1); + assert_eq!(cache_config.cache_misses(), 1); Ok(()) } From c28ccca13b70ca0bad2d059a15c334b15798db76 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 28 Apr 2025 14:27:59 +0200 Subject: [PATCH 08/14] Add Cache struct to separate configuration from runtime (wip) --- crates/cache/src/config.rs | 197 ++++++++++++++------ crates/cache/src/lib.rs | 39 ++-- crates/cache/src/tests.rs | 15 +- crates/cli-flags/src/lib.rs | 8 +- crates/wasmtime/src/compile/runtime.rs | 2 +- crates/wasmtime/src/config.rs | 16 +- crates/wasmtime/src/engine.rs | 4 +- crates/wasmtime/src/engine/serialization.rs | 20 +- 8 files changed, 190 insertions(+), 111 deletions(-) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index 3c8bbb7a95d9..3d0e4041c620 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -15,6 +15,88 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use std::sync::Arc; use std::time::Duration; +/// Global configuration for how the cache is managed +#[derive(Debug, Clone)] +pub struct Cache { + directory: PathBuf, + worker_event_queue_size: u64, + baseline_compression_level: i32, + optimized_compression_level: i32, + optimized_compression_usage_counter_threshold: u64, + cleanup_interval: Duration, + optimizing_compression_task_timeout: Duration, + allowed_clock_drift_for_files_from_future: Duration, + file_count_soft_limit: u64, + files_total_size_soft_limit: u64, + file_count_limit_percent_if_deleting: u8, + files_total_size_limit_percent_if_deleting: u8, + worker: Worker, + state: Arc, +} + +macro_rules! generate_setting_getter { + ($setting:ident: $setting_type:ty) => { + /// Returns `$setting`. + /// + /// Panics if the cache is disabled. + pub fn $setting(&self) -> $setting_type { + self.$setting + } + }; +} + +impl Cache { + generate_setting_getter!(worker_event_queue_size: u64); + generate_setting_getter!(baseline_compression_level: i32); + generate_setting_getter!(optimized_compression_level: i32); + generate_setting_getter!(optimized_compression_usage_counter_threshold: u64); + generate_setting_getter!(cleanup_interval: Duration); + generate_setting_getter!(optimizing_compression_task_timeout: Duration); + generate_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); + generate_setting_getter!(file_count_soft_limit: u64); + generate_setting_getter!(files_total_size_soft_limit: u64); + generate_setting_getter!(file_count_limit_percent_if_deleting: u8); + generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); + + /// Returns path to the cache directory. + /// + /// Panics if the cache is disabled. + pub fn directory(&self) -> &PathBuf { + &self.directory + } + + #[cfg(test)] + pub(super) fn worker(&self) -> &Worker { + &self.worker + } + + /// Returns the number of cache hits seen so far + pub fn cache_hits(&self) -> usize { + self.state.hits.load(SeqCst) + } + + /// Returns the number of cache misses seen so far + pub fn cache_misses(&self) -> usize { + self.state.misses.load(SeqCst) + } + + pub(crate) fn on_cache_get_async(&self, path: impl AsRef) { + self.state.hits.fetch_add(1, SeqCst); + self.worker.on_cache_get_async(path) + } + + pub(crate) fn on_cache_update_async(&self, path: impl AsRef) { + self.state.misses.fetch_add(1, SeqCst); + self.worker.on_cache_update_async(path) + } +} + +#[derive(Default, Debug)] +struct CacheState { + hits: AtomicUsize, + misses: AtomicUsize, +} + // wrapped, so we have named section in config, // also, for possible future compatibility #[derive(serde_derive::Deserialize, Debug)] @@ -87,17 +169,6 @@ pub struct CacheConfig { deserialize_with = "deserialize_percent" )] files_total_size_limit_percent_if_deleting: Option, - - #[serde(skip)] - worker: Option, - #[serde(skip)] - state: Arc, -} - -#[derive(Default, Debug)] -struct CacheState { - hits: AtomicUsize, - misses: AtomicUsize, } /// Creates a new configuration file at specified path, or default path if None is passed. @@ -277,7 +348,7 @@ generate_deserializer!(deserialize_percent(num: u8, unit: &str) -> Option { static CACHE_IMPROPER_CONFIG_ERROR_MSG: &str = "Cache system should be enabled and all settings must be validated or defaulted"; -macro_rules! generate_setting_getter { +macro_rules! generate_option_setting_getter { ($setting:ident: $setting_type:ty) => { /// Returns `$setting`. /// @@ -289,17 +360,17 @@ macro_rules! generate_setting_getter { } impl CacheConfig { - generate_setting_getter!(worker_event_queue_size: u64); - generate_setting_getter!(baseline_compression_level: i32); - generate_setting_getter!(optimized_compression_level: i32); - generate_setting_getter!(optimized_compression_usage_counter_threshold: u64); - generate_setting_getter!(cleanup_interval: Duration); - generate_setting_getter!(optimizing_compression_task_timeout: Duration); - generate_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); - generate_setting_getter!(file_count_soft_limit: u64); - generate_setting_getter!(files_total_size_soft_limit: u64); - generate_setting_getter!(file_count_limit_percent_if_deleting: u8); - generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); + generate_option_setting_getter!(worker_event_queue_size: u64); + generate_option_setting_getter!(baseline_compression_level: i32); + generate_option_setting_getter!(optimized_compression_level: i32); + generate_option_setting_getter!(optimized_compression_usage_counter_threshold: u64); + generate_option_setting_getter!(cleanup_interval: Duration); + generate_option_setting_getter!(optimizing_compression_task_timeout: Duration); + generate_option_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); + generate_option_setting_getter!(file_count_soft_limit: u64); + generate_option_setting_getter!(files_total_size_soft_limit: u64); + generate_option_setting_getter!(file_count_limit_percent_if_deleting: u8); + generate_option_setting_getter!(files_total_size_limit_percent_if_deleting: u8); pub fn builder() -> CacheConfigBuilder { CacheConfigBuilder::default() @@ -335,8 +406,6 @@ impl CacheConfig { files_total_size_soft_limit: None, file_count_limit_percent_if_deleting: None, files_total_size_limit_percent_if_deleting: None, - worker: None, - state: Arc::new(CacheState::default()), } } @@ -369,37 +438,6 @@ impl CacheConfig { Ok(config) } - fn spawn_worker(&mut self) { - if self.enabled { - self.worker = Some(Worker::start_new(self)); - } - } - - pub(super) fn worker(&self) -> &Worker { - assert!(self.enabled); - self.worker.as_ref().unwrap() - } - - /// Returns the number of cache hits seen so far - pub fn cache_hits(&self) -> usize { - self.state.hits.load(SeqCst) - } - - /// Returns the number of cache misses seen so far - pub fn cache_misses(&self) -> usize { - self.state.misses.load(SeqCst) - } - - pub(crate) fn on_cache_get_async(&self, path: impl AsRef) { - self.state.hits.fetch_add(1, SeqCst); - self.worker().on_cache_get_async(path) - } - - pub(crate) fn on_cache_update_async(&self, path: impl AsRef) { - self.state.misses.fetch_add(1, SeqCst); - self.worker().on_cache_update_async(path) - } - fn load_and_parse_file(config_file: Option<&Path>) -> Result { // get config file path let (config_file, user_custom_file) = match config_file { @@ -439,7 +477,6 @@ impl CacheConfig { self.validate_files_total_size_soft_limit_or_default(); self.validate_file_count_limit_percent_if_deleting_or_default()?; self.validate_files_total_size_limit_percent_if_deleting_or_default()?; - self.spawn_worker(); Ok(()) } @@ -600,6 +637,50 @@ impl CacheConfig { } Ok(()) } + + /// Builds a [`Cache`] from the configuration and spawns the cache worker. + /// + /// # Errors + /// Returns an error if the configuration is invalid. + pub fn spawn(&mut self) -> Result { + self.validate_or_default()?; + let CacheConfig { + enabled, + directory, + worker_event_queue_size, + baseline_compression_level, + optimized_compression_level, + optimized_compression_usage_counter_threshold, + cleanup_interval, + optimizing_compression_task_timeout, + allowed_clock_drift_for_files_from_future, + file_count_soft_limit, + files_total_size_soft_limit, + file_count_limit_percent_if_deleting, + files_total_size_limit_percent_if_deleting, + } = self; + + // Unwrapping because validation will ensure these are all set + Ok(Cache { + directory: directory.clone().unwrap(), + worker_event_queue_size: worker_event_queue_size.unwrap(), + baseline_compression_level: baseline_compression_level.unwrap(), + optimized_compression_level: optimized_compression_level.unwrap(), + optimized_compression_usage_counter_threshold: + optimized_compression_usage_counter_threshold.unwrap(), + cleanup_interval: cleanup_interval.unwrap(), + optimizing_compression_task_timeout: optimizing_compression_task_timeout.unwrap(), + allowed_clock_drift_for_files_from_future: allowed_clock_drift_for_files_from_future + .unwrap(), + file_count_soft_limit: file_count_soft_limit.unwrap(), + files_total_size_soft_limit: files_total_size_soft_limit.unwrap(), + file_count_limit_percent_if_deleting: file_count_limit_percent_if_deleting.unwrap(), + files_total_size_limit_percent_if_deleting: files_total_size_limit_percent_if_deleting + .unwrap(), + worker: Worker::start_new(self), + state: Default::default(), + }) + } } /// A builder for `CacheConfig`s. @@ -765,8 +846,6 @@ impl CacheConfigBuilder { files_total_size_soft_limit, file_count_limit_percent_if_deleting, files_total_size_limit_percent_if_deleting, - worker: None, - state: Arc::new(CacheState::default()), }; config.validate_or_default()?; diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index 8ddc5e53da91..43fa40f5abe4 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -12,33 +12,27 @@ use std::{fs, io}; mod config; mod worker; -pub use config::{create_new_config, CacheConfig, CacheConfigBuilder}; +pub use config::{create_new_config, Cache, CacheConfig, CacheConfigBuilder}; use worker::Worker; /// Module level cache entry. -pub struct ModuleCacheEntry<'config>(Option>); +pub struct ModuleCacheEntry<'cache>(Option>); -struct ModuleCacheEntryInner<'config> { +struct ModuleCacheEntryInner<'cache> { root_path: PathBuf, - cache_config: &'config CacheConfig, + cache: &'cache Cache, } struct Sha256Hasher(Sha256); -impl<'config> ModuleCacheEntry<'config> { +impl<'cache> ModuleCacheEntry<'cache> { /// Create the cache entry. - pub fn new(compiler_name: &str, cache_config: Option<&'config CacheConfig>) -> Self { - match cache_config { - Some(cache_config) if cache_config.enabled() => Self(Some(ModuleCacheEntryInner::new( - compiler_name, - cache_config, - ))), - Some(_) | None => Self(None), - } + pub fn new(compiler_name: &str, cache: Option<&'cache Cache>) -> Self { + Self(cache.map(|cache| ModuleCacheEntryInner::new(compiler_name, cache))) } #[cfg(test)] - fn from_inner(inner: ModuleCacheEntryInner<'config>) -> Self { + fn from_inner(inner: ModuleCacheEntryInner<'cache>) -> Self { Self(Some(inner)) } @@ -91,7 +85,7 @@ impl<'config> ModuleCacheEntry<'config> { if let Some(cached_val) = inner.get_data(&hash) { if let Some(val) = deserialize(state, cached_val) { let mod_cache_path = inner.root_path.join(&hash); - inner.cache_config.on_cache_get_async(&mod_cache_path); // call on success + inner.cache.on_cache_get_async(&mod_cache_path); // call on success return Ok(val); } } @@ -99,15 +93,15 @@ impl<'config> ModuleCacheEntry<'config> { if let Some(bytes) = serialize(state, &val_to_cache) { if inner.update_data(&hash, &bytes).is_some() { let mod_cache_path = inner.root_path.join(&hash); - inner.cache_config.on_cache_update_async(&mod_cache_path); // call on success + inner.cache.on_cache_update_async(&mod_cache_path); // call on success } } Ok(val_to_cache) } } -impl<'config> ModuleCacheEntryInner<'config> { - fn new(compiler_name: &str, cache_config: &'config CacheConfig) -> Self { +impl<'cache> ModuleCacheEntryInner<'cache> { + fn new(compiler_name: &str, cache: &'cache Cache) -> Self { // If debug assertions are enabled then assume that we're some sort of // local build. We don't want local builds to stomp over caches between // builds, so just use a separate cache directory based on the mtime of @@ -141,12 +135,9 @@ impl<'config> ModuleCacheEntryInner<'config> { comp_ver = env!("GIT_REV"), ) }; - let root_path = cache_config.directory().join("modules").join(compiler_dir); + let root_path = cache.directory().join("modules").join(compiler_dir); - Self { - root_path, - cache_config, - } + Self { root_path, cache } } fn get_data(&self, hash: &str) -> Option> { @@ -164,7 +155,7 @@ impl<'config> ModuleCacheEntryInner<'config> { trace!("update_data() for path: {}", mod_cache_path.display()); let compressed_data = zstd::encode_all( &serialized_data[..], - self.cache_config.baseline_compression_level(), + self.cache.baseline_compression_level(), ) .map_err(|err| warn!("Failed to compress cached code: {}", err)) .ok()?; diff --git a/crates/cache/src/tests.rs b/crates/cache/src/tests.rs index f901a009cd1f..18eeff1eeee4 100644 --- a/crates/cache/src/tests.rs +++ b/crates/cache/src/tests.rs @@ -21,7 +21,7 @@ fn test_cache_init() { ); fs::write(&config_path, config_content).expect("Failed to write test config file"); - let cache_config = CacheConfig::from_file(Some(&config_path)).unwrap(); + let mut cache_config = CacheConfig::from_file(Some(&config_path)).unwrap(); // test if we can use config assert!(cache_config.enabled()); @@ -36,13 +36,17 @@ fn test_cache_init() { ); // test if we can use worker - cache_config.worker().on_cache_update_async(config_path); + cache_config + .spawn() + .unwrap() + .worker() + .on_cache_update_async(config_path); } #[test] fn test_write_read_cache() { let (_tempdir, cache_dir, config_path) = test_prolog(); - let cache_config = load_config!( + let mut cache_config = load_config!( config_path, "[cache]\n\ enabled = true\n\ @@ -51,6 +55,7 @@ fn test_write_read_cache() { cache_dir ); assert!(cache_config.enabled()); + let cache = cache_config.spawn().unwrap(); // assumption: config load creates cache directory and returns canonicalized path assert_eq!( @@ -61,8 +66,8 @@ fn test_write_read_cache() { let compiler1 = "test-1"; let compiler2 = "test-2"; - let entry1 = ModuleCacheEntry::from_inner(ModuleCacheEntryInner::new(compiler1, &cache_config)); - let entry2 = ModuleCacheEntry::from_inner(ModuleCacheEntryInner::new(compiler2, &cache_config)); + let entry1 = ModuleCacheEntry::from_inner(ModuleCacheEntryInner::new(compiler1, &cache)); + let entry2 = ModuleCacheEntry::from_inner(ModuleCacheEntryInner::new(compiler2, &cache)); entry1.get_data::<_, i32, i32>(1, |_| Ok(100)).unwrap(); entry1.get_data::<_, i32, i32>(1, |_| panic!()).unwrap(); diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index beba311db950..dccc7b5fb6ce 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -737,12 +737,12 @@ impl CommonOptions { if self.codegen.cache != Some(false) { match &self.codegen.cache_config { Some(path) => { - config.cache_config(Some(wasmtime::CacheConfig::from_file(Some(Path::new( - path, - )))?)); + config.cache_config(Some(wasmtime::CacheConfig::from_file(Some( + Path::new(path), + ))?))?; } None => { - config.cache_config(Some(wasmtime::CacheConfig::from_file(None)?)); + config.cache_config(Some(wasmtime::CacheConfig::from_file(None)?))?; } } } diff --git a/crates/wasmtime/src/compile/runtime.rs b/crates/wasmtime/src/compile/runtime.rs index d1462e9e3de9..46be42a01729 100644 --- a/crates/wasmtime/src/compile/runtime.rs +++ b/crates/wasmtime/src/compile/runtime.rs @@ -39,7 +39,7 @@ impl<'a> CodeBuilder<'a> { NotHashed(state), ); let (code, info_and_types) = - wasmtime_cache::ModuleCacheEntry::new("wasmtime", self.engine.cache_config()) + wasmtime_cache::ModuleCacheEntry::new("wasmtime", self.engine.cache()) .get_data_raw( &state, // Cache miss, compute the actual artifacts diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index d9887bb541f1..56368a208d67 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -27,7 +27,7 @@ use wasmtime_fiber::RuntimeFiberStackCreator; #[cfg(feature = "runtime")] pub use crate::runtime::code_memory::CustomCodeMemory; #[cfg(feature = "cache")] -pub use wasmtime_cache::{CacheConfig, CacheConfigBuilder}; +pub use wasmtime_cache::{Cache, CacheConfig, CacheConfigBuilder}; #[cfg(all(feature = "incremental-cache", feature = "cranelift"))] pub use wasmtime_environ::CacheStore; @@ -128,7 +128,7 @@ pub struct Config { tunables: ConfigTunables, #[cfg(feature = "cache")] - pub(crate) cache_config: Option, + pub(crate) cache: Option, #[cfg(feature = "runtime")] pub(crate) mem_creator: Option>, #[cfg(feature = "runtime")] @@ -231,7 +231,7 @@ impl Config { #[cfg(feature = "gc")] collector: Collector::default(), #[cfg(feature = "cache")] - cache_config: None, + cache: None, profiling_strategy: ProfilingStrategy::None, #[cfg(feature = "runtime")] mem_creator: None, @@ -1385,9 +1385,13 @@ impl Config { /// /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html #[cfg(feature = "cache")] - pub fn cache_config(&mut self, cache_config: Option) -> &mut Self { - self.cache_config = cache_config; - self + pub fn cache_config(&mut self, cache_config: Option) -> Result<&mut Self> { + self.cache = if let Some(mut cache_config) = cache_config { + Some(cache_config.spawn()?) + } else { + None + }; + Ok(self) } /// Sets a custom memory creator. diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 4195cd067dd5..be7677e5d34b 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -684,8 +684,8 @@ impl Engine { } #[cfg(all(feature = "cache", any(feature = "cranelift", feature = "winch")))] - pub(crate) fn cache_config(&self) -> Option<&wasmtime_cache::CacheConfig> { - self.config().cache_config.as_ref() + pub(crate) fn cache(&self) -> Option<&wasmtime_cache::Cache> { + self.config().cache.as_ref() } pub(crate) fn signatures(&self) -> &TypeRegistry { diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 31c4648fe65a..0721cfffba6d 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -664,12 +664,12 @@ Caused by: )?; let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::None) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; let cache_config = engine .config() - .cache_config + .cache .as_ref() .expect("Missing cache config"); assert_eq!(cache_config.cache_hits(), 0); @@ -680,11 +680,11 @@ Caused by: let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::Speed) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; let engine = Engine::new(&cfg)?; let cache_config = engine .config() - .cache_config + .cache .as_ref() .expect("Missing cache config"); Module::new(&engine, "(module (func))")?; @@ -696,11 +696,11 @@ Caused by: let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::SpeedAndSize) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; let engine = Engine::new(&cfg)?; let cache_config = engine .config() - .cache_config + .cache .as_ref() .expect("Missing cache config"); Module::new(&engine, "(module (func))")?; @@ -712,11 +712,11 @@ Caused by: let mut cfg = Config::new(); cfg.debug_info(true) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); + .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; let engine = Engine::new(&cfg)?; let cache_config = engine .config() - .cache_config + .cache .as_ref() .expect("Missing cache config"); Module::new(&engine, "(module (func))")?; @@ -792,11 +792,11 @@ Caused by: ), )?; let mut cfg = Config::new(); - cfg.cache_config(Some(CacheConfig::from_file(Some(&config_path))?)); + cfg.cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; let engine = Engine::new(&cfg)?; let cache_config = engine .config() - .cache_config + .cache .as_ref() .expect("Missing cache config"); Component::new(&engine, "(component (core module (func)))")?; From 53c917a70e72d1db7f0acdd9632a2a3c2e0eb31c Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 29 Apr 2025 13:05:57 +0200 Subject: [PATCH 09/14] Ensure default values earlier --- crates/bench-api/src/lib.rs | 2 +- crates/cache/src/config.rs | 606 +++++++++++++++++-------------- crates/cache/src/config/tests.rs | 30 +- examples/fast_compilation.rs | 2 +- src/commands/explore.rs | 2 +- 5 files changed, 360 insertions(+), 282 deletions(-) diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index dcabf871f1c1..5f54c247c551 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -436,7 +436,7 @@ impl BenchState { ) -> Result { let mut config = options.config(None)?; // NB: always disable the compilation cache. - config.cache_config(None); + config.cache_config(None)?; let engine = Engine::new(&config)?; let mut linker = Linker::::new(&engine); diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index 3d0e4041c620..aad2f0be3006 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -18,51 +18,49 @@ use std::time::Duration; /// Global configuration for how the cache is managed #[derive(Debug, Clone)] pub struct Cache { - directory: PathBuf, - worker_event_queue_size: u64, - baseline_compression_level: i32, - optimized_compression_level: i32, - optimized_compression_usage_counter_threshold: u64, - cleanup_interval: Duration, - optimizing_compression_task_timeout: Duration, - allowed_clock_drift_for_files_from_future: Duration, - file_count_soft_limit: u64, - files_total_size_soft_limit: u64, - file_count_limit_percent_if_deleting: u8, - files_total_size_limit_percent_if_deleting: u8, + config: CacheConfig, worker: Worker, state: Arc, } -macro_rules! generate_setting_getter { +macro_rules! generate_config_setting_getter { ($setting:ident: $setting_type:ty) => { /// Returns `$setting`. /// /// Panics if the cache is disabled. pub fn $setting(&self) -> $setting_type { - self.$setting + self.config.$setting() } }; } impl Cache { - generate_setting_getter!(worker_event_queue_size: u64); - generate_setting_getter!(baseline_compression_level: i32); - generate_setting_getter!(optimized_compression_level: i32); - generate_setting_getter!(optimized_compression_usage_counter_threshold: u64); - generate_setting_getter!(cleanup_interval: Duration); - generate_setting_getter!(optimizing_compression_task_timeout: Duration); - generate_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); - generate_setting_getter!(file_count_soft_limit: u64); - generate_setting_getter!(files_total_size_soft_limit: u64); - generate_setting_getter!(file_count_limit_percent_if_deleting: u8); - generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); + pub fn new(mut config: CacheConfig) -> Result { + config.validate()?; + Ok(Self { + worker: Worker::start_new(&config), + config, + state: Default::default(), + }) + } + + generate_config_setting_getter!(worker_event_queue_size: u64); + generate_config_setting_getter!(baseline_compression_level: i32); + generate_config_setting_getter!(optimized_compression_level: i32); + generate_config_setting_getter!(optimized_compression_usage_counter_threshold: u64); + generate_config_setting_getter!(cleanup_interval: Duration); + generate_config_setting_getter!(optimizing_compression_task_timeout: Duration); + generate_config_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); + generate_config_setting_getter!(file_count_soft_limit: u64); + generate_config_setting_getter!(files_total_size_soft_limit: u64); + generate_config_setting_getter!(file_count_limit_percent_if_deleting: u8); + generate_config_setting_getter!(files_total_size_limit_percent_if_deleting: u8); /// Returns path to the cache directory. /// - /// Panics if the cache is disabled. + /// Panics if the cache directory is not set. pub fn directory(&self) -> &PathBuf { - &self.directory + &self.config.directory() } #[cfg(test)] @@ -112,63 +110,69 @@ pub struct CacheConfig { enabled: bool, directory: Option, #[serde( - default, + default = "default_worker_event_queue_size", rename = "worker-event-queue-size", deserialize_with = "deserialize_si_prefix" )] - worker_event_queue_size: Option, - #[serde(rename = "baseline-compression-level")] - baseline_compression_level: Option, - #[serde(rename = "optimized-compression-level")] - optimized_compression_level: Option, + worker_event_queue_size: u64, #[serde( - default, + default = "default_baseline_compression_level", + rename = "baseline-compression-level" + )] + baseline_compression_level: i32, + #[serde( + default = "default_optimized_compression_level", + rename = "optimized-compression-level" + )] + optimized_compression_level: i32, + #[serde( + default = "default_optimized_compression_usage_counter_threshold", rename = "optimized-compression-usage-counter-threshold", deserialize_with = "deserialize_si_prefix" )] - optimized_compression_usage_counter_threshold: Option, + optimized_compression_usage_counter_threshold: u64, #[serde( - default, + default = "default_cleanup_interval", rename = "cleanup-interval", deserialize_with = "deserialize_duration" )] - cleanup_interval: Option, + cleanup_interval: Duration, #[serde( - default, + default = "default_optimizing_compression_task_timeout", rename = "optimizing-compression-task-timeout", deserialize_with = "deserialize_duration" )] - optimizing_compression_task_timeout: Option, + optimizing_compression_task_timeout: Duration, #[serde( - default, + default = "default_allowed_clock_drift_for_files_from_future", rename = "allowed-clock-drift-for-files-from-future", deserialize_with = "deserialize_duration" )] - allowed_clock_drift_for_files_from_future: Option, + allowed_clock_drift_for_files_from_future: Duration, #[serde( - default, + default = "default_file_count_soft_limit", rename = "file-count-soft-limit", deserialize_with = "deserialize_si_prefix" )] - file_count_soft_limit: Option, + file_count_soft_limit: u64, #[serde( - default, + default = "default_files_total_size_soft_limit", rename = "files-total-size-soft-limit", deserialize_with = "deserialize_disk_space" )] - files_total_size_soft_limit: Option, + files_total_size_soft_limit: u64, #[serde( - default, + default = "default_file_count_limit_percent_if_deleting", rename = "file-count-limit-percent-if-deleting", deserialize_with = "deserialize_percent" )] - file_count_limit_percent_if_deleting: Option, + file_count_limit_percent_if_deleting: u8, #[serde( - default, + default = "default_files_total_size_limit_percent_if_deleting", rename = "files-total-size-limit-percent-if-deleting", deserialize_with = "deserialize_percent" )] - files_total_size_limit_percent_if_deleting: Option, + files_total_size_limit_percent_if_deleting: u8, } /// Creates a new configuration file at specified path, or default path if None is passed. @@ -227,34 +231,57 @@ const ZSTD_COMPRESSION_LEVELS: std::ops::RangeInclusive = 0..=21; // At the moment of writing, the modules couldn't depend on another, // so we have at most one module per wasmtime instance // if changed, update cli-cache.md -const DEFAULT_WORKER_EVENT_QUEUE_SIZE: u64 = 0x10; -const WORKER_EVENT_QUEUE_SIZE_WARNING_THRESHOLD: u64 = 3; +const fn default_worker_event_queue_size() -> u64 { + 0x10 +} +const fn worker_event_queue_size_warning_threshold() -> u64 { + 3 +} // should be quick and provide good enough compression // if changed, update cli-cache.md -const DEFAULT_BASELINE_COMPRESSION_LEVEL: i32 = zstd::DEFAULT_COMPRESSION_LEVEL; +const fn default_baseline_compression_level() -> i32 { + zstd::DEFAULT_COMPRESSION_LEVEL +} // should provide significantly better compression than baseline // if changed, update cli-cache.md -const DEFAULT_OPTIMIZED_COMPRESSION_LEVEL: i32 = 20; +const fn default_optimized_compression_level() -> i32 { + 20 +} // shouldn't be to low to avoid recompressing too many files // if changed, update cli-cache.md -const DEFAULT_OPTIMIZED_COMPRESSION_USAGE_COUNTER_THRESHOLD: u64 = 0x100; +const fn default_optimized_compression_usage_counter_threshold() -> u64 { + 0x100 +} // if changed, update cli-cache.md -const DEFAULT_CLEANUP_INTERVAL: Duration = Duration::from_secs(60 * 60); +const fn default_cleanup_interval() -> Duration { + Duration::from_secs(60 * 60) +} // if changed, update cli-cache.md -const DEFAULT_OPTIMIZING_COMPRESSION_TASK_TIMEOUT: Duration = Duration::from_secs(30 * 60); +const fn default_optimizing_compression_task_timeout() -> Duration { + Duration::from_secs(30 * 60) +} // the default assumes problems with timezone configuration on network share + some clock drift // please notice 24 timezones = max 23h difference between some of them // if changed, update cli-cache.md -const DEFAULT_ALLOWED_CLOCK_DRIFT_FOR_FILES_FROM_FUTURE: Duration = - Duration::from_secs(60 * 60 * 24); +const fn default_allowed_clock_drift_for_files_from_future() -> Duration { + Duration::from_secs(60 * 60 * 24) +} // if changed, update cli-cache.md -const DEFAULT_FILE_COUNT_SOFT_LIMIT: u64 = 0x10_000; +const fn default_file_count_soft_limit() -> u64 { + 0x10_000 +} // if changed, update cli-cache.md -const DEFAULT_FILES_TOTAL_SIZE_SOFT_LIMIT: u64 = 1024 * 1024 * 512; +const fn default_files_total_size_soft_limit() -> u64 { + 1024 * 1024 * 512 +} // if changed, update cli-cache.md -const DEFAULT_FILE_COUNT_LIMIT_PERCENT_IF_DELETING: u8 = 70; +const fn default_file_count_limit_percent_if_deleting() -> u8 { + 70 +} // if changed, update cli-cache.md -const DEFAULT_FILES_TOTAL_SIZE_LIMIT_PERCENT_IF_DELETING: u8 = 70; +const fn default_files_total_size_limit_percent_if_deleting() -> u8 { + 70 +} fn project_dirs() -> Option { ProjectDirs::from("", "BytecodeAlliance", "wasmtime") @@ -275,11 +302,7 @@ macro_rules! generate_deserializer { where D: Deserializer<'de>, { - let text = Option::::deserialize(deserializer)?; - let text = match text { - None => return Ok(None), - Some(text) => text, - }; + let text = String::deserialize(deserializer)?; let text = text.trim(); let split_point = text.find(|c: char| !c.is_numeric()); let (num, unit) = split_point.map_or_else(|| (text, ""), |p| text.split_at(p)); @@ -288,7 +311,7 @@ macro_rules! generate_deserializer { let $unitname = unit.trim(); $body })(); - if deserialized.is_some() { + if let Some(deserialized) = deserialized { Ok(deserialized) } else { Err(de::Error::custom( @@ -299,7 +322,7 @@ macro_rules! generate_deserializer { }; } -generate_deserializer!(deserialize_duration(num: u64, unit: &str) -> Option { +generate_deserializer!(deserialize_duration(num: u64, unit: &str) -> Duration { match unit { "s" => Some(Duration::from_secs(num)), "m" => Some(Duration::from_secs(num * 60)), @@ -309,7 +332,7 @@ generate_deserializer!(deserialize_duration(num: u64, unit: &str) -> Option Option { +generate_deserializer!(deserialize_si_prefix(num: u64, unit: &str) -> u64 { match unit { "" => Some(num), "K" => num.checked_mul(1_000), @@ -321,7 +344,7 @@ generate_deserializer!(deserialize_si_prefix(num: u64, unit: &str) -> Option Option { +generate_deserializer!(deserialize_disk_space(num: u64, unit: &str) -> u64 { match unit { "" => Some(num), "K" => num.checked_mul(1_000), @@ -338,7 +361,7 @@ generate_deserializer!(deserialize_disk_space(num: u64, unit: &str) -> Option Option { +generate_deserializer!(deserialize_percent(num: u8, unit: &str) -> u8 { match unit { "%" => Some(num), _ => None, @@ -348,33 +371,29 @@ generate_deserializer!(deserialize_percent(num: u8, unit: &str) -> Option { static CACHE_IMPROPER_CONFIG_ERROR_MSG: &str = "Cache system should be enabled and all settings must be validated or defaulted"; -macro_rules! generate_option_setting_getter { +macro_rules! generate_setting_getter { ($setting:ident: $setting_type:ty) => { /// Returns `$setting`. /// /// Panics if the cache is disabled. pub fn $setting(&self) -> $setting_type { - self.$setting.expect(CACHE_IMPROPER_CONFIG_ERROR_MSG) + self.$setting } }; } impl CacheConfig { - generate_option_setting_getter!(worker_event_queue_size: u64); - generate_option_setting_getter!(baseline_compression_level: i32); - generate_option_setting_getter!(optimized_compression_level: i32); - generate_option_setting_getter!(optimized_compression_usage_counter_threshold: u64); - generate_option_setting_getter!(cleanup_interval: Duration); - generate_option_setting_getter!(optimizing_compression_task_timeout: Duration); - generate_option_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); - generate_option_setting_getter!(file_count_soft_limit: u64); - generate_option_setting_getter!(files_total_size_soft_limit: u64); - generate_option_setting_getter!(file_count_limit_percent_if_deleting: u8); - generate_option_setting_getter!(files_total_size_limit_percent_if_deleting: u8); - - pub fn builder() -> CacheConfigBuilder { - CacheConfigBuilder::default() - } + generate_setting_getter!(worker_event_queue_size: u64); + generate_setting_getter!(baseline_compression_level: i32); + generate_setting_getter!(optimized_compression_level: i32); + generate_setting_getter!(optimized_compression_usage_counter_threshold: u64); + generate_setting_getter!(cleanup_interval: Duration); + generate_setting_getter!(optimizing_compression_task_timeout: Duration); + generate_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); + generate_setting_getter!(file_count_soft_limit: u64); + generate_setting_getter!(files_total_size_soft_limit: u64); + generate_setting_getter!(file_count_limit_percent_if_deleting: u8); + generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); /// Returns true if and only if the cache is enabled. pub fn enabled(&self) -> bool { @@ -395,17 +414,20 @@ impl CacheConfig { Self { enabled: false, directory: None, - worker_event_queue_size: None, - baseline_compression_level: None, - optimized_compression_level: None, - optimized_compression_usage_counter_threshold: None, - cleanup_interval: None, - optimizing_compression_task_timeout: None, - allowed_clock_drift_for_files_from_future: None, - file_count_soft_limit: None, - files_total_size_soft_limit: None, - file_count_limit_percent_if_deleting: None, - files_total_size_limit_percent_if_deleting: None, + worker_event_queue_size: default_worker_event_queue_size(), + baseline_compression_level: default_baseline_compression_level(), + optimized_compression_level: default_optimized_compression_level(), + optimized_compression_usage_counter_threshold: + default_optimized_compression_usage_counter_threshold(), + cleanup_interval: default_cleanup_interval(), + optimizing_compression_task_timeout: default_optimizing_compression_task_timeout(), + allowed_clock_drift_for_files_from_future: + default_allowed_clock_drift_for_files_from_future(), + file_count_soft_limit: default_file_count_soft_limit(), + files_total_size_soft_limit: default_files_total_size_soft_limit(), + file_count_limit_percent_if_deleting: default_file_count_limit_percent_if_deleting(), + files_total_size_limit_percent_if_deleting: + default_files_total_size_limit_percent_if_deleting(), } } @@ -434,7 +456,7 @@ impl CacheConfig { /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html pub fn from_file(config_file: Option<&Path>) -> Result { let mut config = Self::load_and_parse_file(config_file)?; - config.validate_or_default()?; + config.validate()?; Ok(config) } @@ -464,19 +486,13 @@ impl CacheConfig { } /// validate values and fill in defaults - fn validate_or_default(&mut self) -> Result<()> { + fn validate(&mut self) -> Result<()> { self.validate_directory_or_default()?; - self.validate_worker_event_queue_size_or_default(); - self.validate_baseline_compression_level_or_default()?; - self.validate_optimized_compression_level_or_default()?; - self.validate_optimized_compression_usage_counter_threshold_or_default(); - self.validate_cleanup_interval_or_default(); - self.validate_optimizing_compression_task_timeout_or_default(); - self.validate_allowed_clock_drift_for_files_from_future_or_default(); - self.validate_file_count_soft_limit_or_default(); - self.validate_files_total_size_soft_limit_or_default(); - self.validate_file_count_limit_percent_if_deleting_or_default()?; - self.validate_files_total_size_limit_percent_if_deleting_or_default()?; + self.validate_worker_event_queue_size(); + self.validate_baseline_compression_level()?; + self.validate_optimized_compression_level()?; + self.validate_file_count_limit_percent_if_deleting()?; + self.validate_files_total_size_limit_percent_if_deleting()?; Ok(()) } @@ -515,25 +531,17 @@ impl CacheConfig { Ok(()) } - fn validate_worker_event_queue_size_or_default(&mut self) { - if self.worker_event_queue_size.is_none() { - self.worker_event_queue_size = Some(DEFAULT_WORKER_EVENT_QUEUE_SIZE); - } - - if self.worker_event_queue_size.unwrap() < WORKER_EVENT_QUEUE_SIZE_WARNING_THRESHOLD { + fn validate_worker_event_queue_size(&self) { + if self.worker_event_queue_size < worker_event_queue_size_warning_threshold() { warn!("Detected small worker event queue size. Some messages might be lost."); } } - fn validate_baseline_compression_level_or_default(&mut self) -> Result<()> { - if self.baseline_compression_level.is_none() { - self.baseline_compression_level = Some(DEFAULT_BASELINE_COMPRESSION_LEVEL); - } - - if !ZSTD_COMPRESSION_LEVELS.contains(&self.baseline_compression_level.unwrap()) { + fn validate_baseline_compression_level(&self) -> Result<()> { + if !ZSTD_COMPRESSION_LEVELS.contains(&self.baseline_compression_level) { bail!( "Invalid baseline compression level: {} not in {:#?}", - self.baseline_compression_level.unwrap(), + self.baseline_compression_level, ZSTD_COMPRESSION_LEVELS ); } @@ -541,98 +549,40 @@ impl CacheConfig { } // assumption: baseline compression level has been verified - fn validate_optimized_compression_level_or_default(&mut self) -> Result<()> { - if self.optimized_compression_level.is_none() { - self.optimized_compression_level = Some(DEFAULT_OPTIMIZED_COMPRESSION_LEVEL); - } - - let opt_lvl = self.optimized_compression_level.unwrap(); - let base_lvl = self.baseline_compression_level.unwrap(); - - if !ZSTD_COMPRESSION_LEVELS.contains(&opt_lvl) { + fn validate_optimized_compression_level(&self) -> Result<()> { + if !ZSTD_COMPRESSION_LEVELS.contains(&self.optimized_compression_level) { bail!( "Invalid optimized compression level: {} not in {:#?}", - opt_lvl, + self.optimized_compression_level, ZSTD_COMPRESSION_LEVELS ); } - if opt_lvl < base_lvl { + if self.optimized_compression_level < self.baseline_compression_level { bail!( "Invalid optimized compression level is lower than baseline: {} < {}", - opt_lvl, - base_lvl + self.optimized_compression_level, + self.baseline_compression_level ); } Ok(()) } - fn validate_optimized_compression_usage_counter_threshold_or_default(&mut self) { - if self.optimized_compression_usage_counter_threshold.is_none() { - self.optimized_compression_usage_counter_threshold = - Some(DEFAULT_OPTIMIZED_COMPRESSION_USAGE_COUNTER_THRESHOLD); - } - } - - fn validate_cleanup_interval_or_default(&mut self) { - if self.cleanup_interval.is_none() { - self.cleanup_interval = Some(DEFAULT_CLEANUP_INTERVAL); - } - } - - fn validate_optimizing_compression_task_timeout_or_default(&mut self) { - if self.optimizing_compression_task_timeout.is_none() { - self.optimizing_compression_task_timeout = - Some(DEFAULT_OPTIMIZING_COMPRESSION_TASK_TIMEOUT); - } - } - - fn validate_allowed_clock_drift_for_files_from_future_or_default(&mut self) { - if self.allowed_clock_drift_for_files_from_future.is_none() { - self.allowed_clock_drift_for_files_from_future = - Some(DEFAULT_ALLOWED_CLOCK_DRIFT_FOR_FILES_FROM_FUTURE); - } - } - - fn validate_file_count_soft_limit_or_default(&mut self) { - if self.file_count_soft_limit.is_none() { - self.file_count_soft_limit = Some(DEFAULT_FILE_COUNT_SOFT_LIMIT); - } - } - - fn validate_files_total_size_soft_limit_or_default(&mut self) { - if self.files_total_size_soft_limit.is_none() { - self.files_total_size_soft_limit = Some(DEFAULT_FILES_TOTAL_SIZE_SOFT_LIMIT); - } - } - - fn validate_file_count_limit_percent_if_deleting_or_default(&mut self) -> Result<()> { - if self.file_count_limit_percent_if_deleting.is_none() { - self.file_count_limit_percent_if_deleting = - Some(DEFAULT_FILE_COUNT_LIMIT_PERCENT_IF_DELETING); - } - - let percent = self.file_count_limit_percent_if_deleting.unwrap(); - if percent > 100 { + fn validate_file_count_limit_percent_if_deleting(&self) -> Result<()> { + if self.file_count_limit_percent_if_deleting > 100 { bail!( "Invalid files count limit percent if deleting: {} not in range 0-100%", - percent + self.file_count_limit_percent_if_deleting ); } Ok(()) } - fn validate_files_total_size_limit_percent_if_deleting_or_default(&mut self) -> Result<()> { - if self.files_total_size_limit_percent_if_deleting.is_none() { - self.files_total_size_limit_percent_if_deleting = - Some(DEFAULT_FILES_TOTAL_SIZE_LIMIT_PERCENT_IF_DELETING); - } - - let percent = self.files_total_size_limit_percent_if_deleting.unwrap(); - if percent > 100 { + fn validate_files_total_size_limit_percent_if_deleting(&self) -> Result<()> { + if self.files_total_size_limit_percent_if_deleting > 100 { bail!( "Invalid files total size limit percent if deleting: {} not in range 0-100%", - percent + self.files_total_size_limit_percent_if_deleting ); } Ok(()) @@ -643,40 +593,11 @@ impl CacheConfig { /// # Errors /// Returns an error if the configuration is invalid. pub fn spawn(&mut self) -> Result { - self.validate_or_default()?; - let CacheConfig { - enabled, - directory, - worker_event_queue_size, - baseline_compression_level, - optimized_compression_level, - optimized_compression_usage_counter_threshold, - cleanup_interval, - optimizing_compression_task_timeout, - allowed_clock_drift_for_files_from_future, - file_count_soft_limit, - files_total_size_soft_limit, - file_count_limit_percent_if_deleting, - files_total_size_limit_percent_if_deleting, - } = self; + self.validate()?; // Unwrapping because validation will ensure these are all set Ok(Cache { - directory: directory.clone().unwrap(), - worker_event_queue_size: worker_event_queue_size.unwrap(), - baseline_compression_level: baseline_compression_level.unwrap(), - optimized_compression_level: optimized_compression_level.unwrap(), - optimized_compression_usage_counter_threshold: - optimized_compression_usage_counter_threshold.unwrap(), - cleanup_interval: cleanup_interval.unwrap(), - optimizing_compression_task_timeout: optimizing_compression_task_timeout.unwrap(), - allowed_clock_drift_for_files_from_future: allowed_clock_drift_for_files_from_future - .unwrap(), - file_count_soft_limit: file_count_soft_limit.unwrap(), - files_total_size_soft_limit: files_total_size_soft_limit.unwrap(), - file_count_limit_percent_if_deleting: file_count_limit_percent_if_deleting.unwrap(), - files_total_size_limit_percent_if_deleting: files_total_size_limit_percent_if_deleting - .unwrap(), + config: self.clone(), worker: Worker::start_new(self), state: Default::default(), }) @@ -684,62 +605,113 @@ impl CacheConfig { } /// A builder for `CacheConfig`s. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CacheConfigBuilder { directory: Option, - worker_event_queue_size: Option, - baseline_compression_level: Option, - optimized_compression_level: Option, - optimized_compression_usage_counter_threshold: Option, - cleanup_interval: Option, - optimizing_compression_task_timeout: Option, - allowed_clock_drift_for_files_from_future: Option, - file_count_soft_limit: Option, - files_total_size_soft_limit: Option, - file_count_limit_percent_if_deleting: Option, - files_total_size_limit_percent_if_deleting: Option, + worker_event_queue_size: u64, + baseline_compression_level: i32, + optimized_compression_level: i32, + optimized_compression_usage_counter_threshold: u64, + optimizing_compression_task_timeout: Duration, + cleanup_interval: Duration, + allowed_clock_drift_for_files_from_future: Duration, + file_count_soft_limit: u64, + files_total_size_soft_limit: u64, + file_count_limit_percent_if_deleting: u8, + files_total_size_limit_percent_if_deleting: u8, +} + +impl Default for CacheConfigBuilder { + fn default() -> Self { + Self { + directory: None, + worker_event_queue_size: default_worker_event_queue_size(), + baseline_compression_level: default_baseline_compression_level(), + optimized_compression_level: default_optimized_compression_level(), + optimized_compression_usage_counter_threshold: + default_optimized_compression_usage_counter_threshold(), + optimizing_compression_task_timeout: default_optimizing_compression_task_timeout(), + cleanup_interval: default_cleanup_interval(), + allowed_clock_drift_for_files_from_future: + default_allowed_clock_drift_for_files_from_future(), + file_count_soft_limit: default_file_count_soft_limit(), + files_total_size_soft_limit: default_files_total_size_soft_limit(), + file_count_limit_percent_if_deleting: default_file_count_limit_percent_if_deleting(), + files_total_size_limit_percent_if_deleting: + default_files_total_size_limit_percent_if_deleting(), + } + } } impl CacheConfigBuilder { - /// Specifies where the cache directory is. Must be an absolute path. - pub fn directory(&mut self, directory: impl Into) -> &mut Self { + /// Specify where the cache directory is. Must be an absolute path. + pub fn new() -> Self { + Self::default() + } + + generate_setting_getter!(worker_event_queue_size: u64); + generate_setting_getter!(baseline_compression_level: i32); + generate_setting_getter!(optimized_compression_level: i32); + generate_setting_getter!(optimized_compression_usage_counter_threshold: u64); + generate_setting_getter!(cleanup_interval: Duration); + generate_setting_getter!(optimizing_compression_task_timeout: Duration); + generate_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); + generate_setting_getter!(file_count_soft_limit: u64); + generate_setting_getter!(files_total_size_soft_limit: u64); + generate_setting_getter!(file_count_limit_percent_if_deleting: u8); + generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); + + /// Returns path to the cache directory. + /// + /// Panics if the directory hasn't been set yet. + pub fn directory(&self) -> &PathBuf { + self.directory + .as_ref() + .expect(CACHE_IMPROPER_CONFIG_ERROR_MSG) + } + + /// Specify where the cache directory is. Must be an absolute path. + pub fn with_directory(&mut self, directory: impl Into) -> &mut Self { self.directory = Some(directory.into()); self } /// Size of cache worker event queue. If the queue is full, incoming cache usage events will be /// dropped. - pub fn worker_event_queue_size(&mut self, size: u64) -> &mut Self { - self.worker_event_queue_size = Some(size); + pub fn with_worker_event_queue_size(&mut self, size: u64) -> &mut Self { + self.worker_event_queue_size = size; self } /// Compression level used when a new cache file is being written by the cache system. Wasmtime /// uses zstd compression. - pub fn baseline_compression_level(&mut self, level: i32) -> &mut Self { - self.baseline_compression_level = Some(level); + pub fn with_baseline_compression_level(&mut self, level: i32) -> &mut Self { + self.baseline_compression_level = level; self } /// Compression level used when the cache worker decides to recompress a cache file. Wasmtime /// uses zstd compression. - pub fn optimized_compression_level(&mut self, level: i32) -> &mut Self { - self.optimized_compression_level = Some(level); + pub fn with_optimized_compression_level(&mut self, level: i32) -> &mut Self { + self.optimized_compression_level = level; self } /// One of the conditions for the cache worker to recompress a cache file is to have usage /// count of the file exceeding this threshold. - pub fn optimized_compression_usage_counter_threshold(&mut self, threshold: u64) -> &mut Self { - self.optimized_compression_usage_counter_threshold = Some(threshold); + pub fn with_optimized_compression_usage_counter_threshold( + &mut self, + threshold: u64, + ) -> &mut Self { + self.optimized_compression_usage_counter_threshold = threshold; self } /// When the cache worker is notified about a cache file being updated by the cache system and /// this interval has already passed since last cleaning up, the worker will attempt a new /// cleanup. - pub fn cleanup_interval(&mut self, interval: Duration) -> &mut Self { - self.cleanup_interval = Some(interval); + pub fn with_cleanup_interval(&mut self, interval: Duration) -> &mut Self { + self.cleanup_interval = interval; self } @@ -747,8 +719,8 @@ impl CacheConfigBuilder { /// worker has started the task for this file within the last /// optimizing-compression-task-timeout interval. If some worker has started working on it, /// other workers are skipping this task. - pub fn optimizing_compression_task_timeout(&mut self, timeout: Duration) -> &mut Self { - self.optimizing_compression_task_timeout = Some(timeout); + pub fn with_optimizing_compression_task_timeout(&mut self, timeout: Duration) -> &mut Self { + self.optimizing_compression_task_timeout = timeout; self } @@ -769,8 +741,8 @@ impl CacheConfigBuilder { /// limits are not reached, the cache files will not be deleted. Otherwise, they will be /// treated as the oldest files, so they might survive. If the user actually uses the cache /// file, the modification time will be updated. - pub fn allowed_clock_drift_for_files_from_future(&mut self, drift: Duration) -> &mut Self { - self.allowed_clock_drift_for_files_from_future = Some(drift); + pub fn with_allowed_clock_drift_for_files_from_future(&mut self, drift: Duration) -> &mut Self { + self.allowed_clock_drift_for_files_from_future = drift; self } @@ -778,8 +750,8 @@ impl CacheConfigBuilder { /// /// This doesn't include files with metadata. To learn more, please refer to the cache system /// section. - pub fn file_count_soft_limit(&mut self, limit: u64) -> &mut Self { - self.file_count_soft_limit = Some(limit); + pub fn with_file_count_soft_limit(&mut self, limit: u64) -> &mut Self { + self.file_count_soft_limit = limit; self } @@ -789,8 +761,8 @@ impl CacheConfigBuilder { /// section. /// /// *this is the file size, not the space physically occupied on the disk. - pub fn files_total_size_soft_limit(&mut self, limit: u64) -> &mut Self { - self.files_total_size_soft_limit = Some(limit); + pub fn with_files_total_size_soft_limit(&mut self, limit: u64) -> &mut Self { + self.files_total_size_soft_limit = limit; self } @@ -800,8 +772,8 @@ impl CacheConfigBuilder { /// /// This doesn't include files with metadata. To learn more, please refer to the cache system /// section. - pub fn file_count_limit_percent_if_deleting(&mut self, percent: u8) -> &mut Self { - self.file_count_limit_percent_if_deleting = Some(percent); + pub fn with_file_count_limit_percent_if_deleting(&mut self, percent: u8) -> &mut Self { + self.file_count_limit_percent_if_deleting = percent; self } @@ -811,12 +783,116 @@ impl CacheConfigBuilder { /// /// This doesn't include files with metadata. To learn more, please refer to the cache system /// section. - pub fn files_total_size_limit_percent_if_deleting(&mut self, percent: u8) -> &mut Self { - self.files_total_size_limit_percent_if_deleting = Some(percent); + pub fn with_files_total_size_limit_percent_if_deleting(&mut self, percent: u8) -> &mut Self { + self.files_total_size_limit_percent_if_deleting = percent; self } - pub fn build(self) -> Result { + /// validate values and fill in defaults + fn validate(&mut self) -> Result<()> { + self.validate_directory()?; + self.validate_worker_event_queue_size(); + self.validate_baseline_compression_level()?; + self.validate_optimized_compression_level()?; + self.validate_file_count_limit_percent_if_deleting()?; + self.validate_files_total_size_limit_percent_if_deleting()?; + Ok(()) + } + + fn validate_directory(&mut self) -> Result<()> { + if self.directory.is_none() { + match project_dirs() { + Some(proj_dirs) => self.directory = Some(proj_dirs.cache_dir().to_path_buf()), + None => { + bail!("Cache directory not specified and failed to get the default"); + } + } + } + + // On Windows, if we want long paths, we need '\\?\' prefix, but it doesn't work + // with relative paths. One way to get absolute path (the only one?) is to use + // fs::canonicalize, but it requires that given path exists. The extra advantage + // of this method is fact that the method prepends '\\?\' on Windows. + let cache_dir = self.directory.as_ref().unwrap(); + + if !cache_dir.is_absolute() { + bail!( + "Cache directory path has to be absolute, path: {}", + cache_dir.display(), + ); + } + + fs::create_dir_all(cache_dir).context(format!( + "failed to create cache directory: {}", + cache_dir.display() + ))?; + let canonical = fs::canonicalize(cache_dir).context(format!( + "failed to canonicalize cache directory: {}", + cache_dir.display() + ))?; + self.directory = Some(canonical); + Ok(()) + } + + fn validate_worker_event_queue_size(&self) { + if self.worker_event_queue_size < worker_event_queue_size_warning_threshold() { + warn!("Detected small worker event queue size. Some messages might be lost."); + } + } + + fn validate_baseline_compression_level(&self) -> Result<()> { + if !ZSTD_COMPRESSION_LEVELS.contains(&self.baseline_compression_level) { + bail!( + "Invalid baseline compression level: {} not in {:#?}", + self.baseline_compression_level, + ZSTD_COMPRESSION_LEVELS + ); + } + Ok(()) + } + + // assumption: baseline compression level has been verified + fn validate_optimized_compression_level(&self) -> Result<()> { + if !ZSTD_COMPRESSION_LEVELS.contains(&self.optimized_compression_level) { + bail!( + "Invalid optimized compression level: {} not in {:#?}", + self.optimized_compression_level, + ZSTD_COMPRESSION_LEVELS + ); + } + + if self.optimized_compression_level < self.baseline_compression_level { + bail!( + "Invalid optimized compression level is lower than baseline: {} < {}", + self.optimized_compression_level, + self.baseline_compression_level + ); + } + Ok(()) + } + + fn validate_file_count_limit_percent_if_deleting(&self) -> Result<()> { + if self.file_count_limit_percent_if_deleting > 100 { + bail!( + "Invalid files count limit percent if deleting: {} not in range 0-100%", + self.file_count_limit_percent_if_deleting + ); + } + Ok(()) + } + + fn validate_files_total_size_limit_percent_if_deleting(&self) -> Result<()> { + if self.files_total_size_limit_percent_if_deleting > 100 { + bail!( + "Invalid files total size limit percent if deleting: {} not in range 0-100%", + self.files_total_size_limit_percent_if_deleting + ); + } + Ok(()) + } + + pub fn build(mut self) -> Result { + self.validate()?; let CacheConfigBuilder { directory, worker_event_queue_size, @@ -848,7 +924,7 @@ impl CacheConfigBuilder { files_total_size_limit_percent_if_deleting, }; - config.validate_or_default()?; + config.validate()?; Ok(config) } diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index e202e13e71e4..bd46f19be2b8 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -1,3 +1,5 @@ +use crate::CacheConfigBuilder; + use super::CacheConfig; use std::fs; use std::path::PathBuf; @@ -527,7 +529,7 @@ fn test_builder_default() { fs::write(&cp, config_content).expect("Failed to write test config file"); let expected_config = CacheConfig::from_file(Some(&cp)).unwrap(); - let config = CacheConfig::builder() + let config = CacheConfigBuilder::new() .build() .expect("Failed to build CacheConfig"); @@ -580,20 +582,20 @@ fn test_builder_default() { fn test_builder_all_settings() { let (_td, cd, _cp) = test_prolog(); - let mut builder = CacheConfig::builder(); + let mut builder = CacheConfigBuilder::new(); builder - .directory(&cd) - .worker_event_queue_size(0x10) - .baseline_compression_level(3) - .optimized_compression_level(20) - .optimized_compression_usage_counter_threshold(0x100) - .cleanup_interval(Duration::from_secs(60 * 60)) - .optimizing_compression_task_timeout(Duration::from_secs(30 * 60)) - .allowed_clock_drift_for_files_from_future(Duration::from_secs(60 * 60 * 24)) - .file_count_soft_limit(0x10_000) - .files_total_size_soft_limit(512 * (1u64 << 20)) - .file_count_limit_percent_if_deleting(70) - .files_total_size_limit_percent_if_deleting(70); + .with_directory(&cd) + .with_worker_event_queue_size(0x10) + .with_baseline_compression_level(3) + .with_optimized_compression_level(20) + .with_optimized_compression_usage_counter_threshold(0x100) + .with_cleanup_interval(Duration::from_secs(60 * 60)) + .with_optimizing_compression_task_timeout(Duration::from_secs(30 * 60)) + .with_allowed_clock_drift_for_files_from_future(Duration::from_secs(60 * 60 * 24)) + .with_file_count_soft_limit(0x10_000) + .with_files_total_size_soft_limit(512 * (1u64 << 20)) + .with_file_count_limit_percent_if_deleting(70) + .with_files_total_size_limit_percent_if_deleting(70); let conf = builder.build().expect("Failed to build config"); check_conf(&conf, &cd); diff --git a/examples/fast_compilation.rs b/examples/fast_compilation.rs index 969b6c15b78c..474e4eb0267f 100644 --- a/examples/fast_compilation.rs +++ b/examples/fast_compilation.rs @@ -10,7 +10,7 @@ fn main() -> Result<()> { // Enable the compilation cache, using the default cache configuration // settings. - config.cache_config(Some(CacheConfig::from_file(None)?)); + config.cache_config(Some(CacheConfig::from_file(None)?))?; // Enable Winch, Wasmtime's baseline compiler. config.strategy(Strategy::Winch); diff --git a/src/commands/explore.rs b/src/commands/explore.rs index b91b922beb13..df5f7bb43f5c 100644 --- a/src/commands/explore.rs +++ b/src/commands/explore.rs @@ -51,7 +51,7 @@ impl ExploreCommand { let clif_dir = if let Some(Strategy::Cranelift) | None = self.common.codegen.compiler { let clif_dir = tempdir()?; config.emit_clif(clif_dir.path()); - config.cache_config(None); // cache does not emit clif + config.cache_config(None)?; // cache does not emit clif Some(clif_dir) } else { None From b20c6198eac93a341dc6997f254eb12d59c44753 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 29 Apr 2025 13:19:07 +0200 Subject: [PATCH 10/14] Consolidate CacheConfig and CacheConfigBuilder --- crates/cache/src/config.rs | 297 +++----------------- crates/cache/src/config/tests.rs | 80 +----- crates/cache/src/lib.rs | 2 +- crates/cache/src/tests.rs | 14 +- crates/cache/src/worker/tests.rs | 20 -- crates/wasmtime/src/config.rs | 6 +- crates/wasmtime/src/engine/serialization.rs | 2 - docs/cli-cache.md | 1 - 8 files changed, 45 insertions(+), 377 deletions(-) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index aad2f0be3006..e6bf0ad016d8 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -35,6 +35,10 @@ macro_rules! generate_config_setting_getter { } impl Cache { + /// Builds a [`Cache`] from the configuration and spawns the cache worker. + /// + /// # Errors + /// Returns an error if the configuration is invalid. pub fn new(mut config: CacheConfig) -> Result { config.validate()?; Ok(Self { @@ -107,7 +111,6 @@ struct Config { #[derive(serde_derive::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] pub struct CacheConfig { - enabled: bool, directory: Option, #[serde( default = "default_worker_event_queue_size", @@ -175,6 +178,28 @@ pub struct CacheConfig { files_total_size_limit_percent_if_deleting: u8, } +impl Default for CacheConfig { + fn default() -> Self { + Self { + directory: None, + worker_event_queue_size: default_worker_event_queue_size(), + baseline_compression_level: default_baseline_compression_level(), + optimized_compression_level: default_optimized_compression_level(), + optimized_compression_usage_counter_threshold: + default_optimized_compression_usage_counter_threshold(), + cleanup_interval: default_cleanup_interval(), + optimizing_compression_task_timeout: default_optimizing_compression_task_timeout(), + allowed_clock_drift_for_files_from_future: + default_allowed_clock_drift_for_files_from_future(), + file_count_soft_limit: default_file_count_soft_limit(), + files_total_size_soft_limit: default_files_total_size_soft_limit(), + file_count_limit_percent_if_deleting: default_file_count_limit_percent_if_deleting(), + files_total_size_limit_percent_if_deleting: + default_files_total_size_limit_percent_if_deleting(), + } + } +} + /// Creates a new configuration file at specified path, or default path if None is passed. /// Fails if file already exists. pub fn create_new_config + Debug>(config_file: Option

) -> Result { @@ -209,7 +234,6 @@ pub fn create_new_config + Debug>(config_file: Option

) -> Resu # https://bytecodealliance.github.io/wasmtime/cli-cache.html [cache] -enabled = true "; fs::write(&config_file, content).with_context(|| { @@ -383,58 +407,9 @@ macro_rules! generate_setting_getter { } impl CacheConfig { - generate_setting_getter!(worker_event_queue_size: u64); - generate_setting_getter!(baseline_compression_level: i32); - generate_setting_getter!(optimized_compression_level: i32); - generate_setting_getter!(optimized_compression_usage_counter_threshold: u64); - generate_setting_getter!(cleanup_interval: Duration); - generate_setting_getter!(optimizing_compression_task_timeout: Duration); - generate_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); - generate_setting_getter!(file_count_soft_limit: u64); - generate_setting_getter!(files_total_size_soft_limit: u64); - generate_setting_getter!(file_count_limit_percent_if_deleting: u8); - generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); - - /// Returns true if and only if the cache is enabled. - pub fn enabled(&self) -> bool { - self.enabled - } - - /// Returns path to the cache directory. - /// - /// Panics if the cache is disabled. - pub fn directory(&self) -> &PathBuf { - self.directory - .as_ref() - .expect(CACHE_IMPROPER_CONFIG_ERROR_MSG) - } - /// Creates a new set of configuration which represents a disabled cache - pub fn new_cache_disabled() -> Self { - Self { - enabled: false, - directory: None, - worker_event_queue_size: default_worker_event_queue_size(), - baseline_compression_level: default_baseline_compression_level(), - optimized_compression_level: default_optimized_compression_level(), - optimized_compression_usage_counter_threshold: - default_optimized_compression_usage_counter_threshold(), - cleanup_interval: default_cleanup_interval(), - optimizing_compression_task_timeout: default_optimizing_compression_task_timeout(), - allowed_clock_drift_for_files_from_future: - default_allowed_clock_drift_for_files_from_future(), - file_count_soft_limit: default_file_count_soft_limit(), - files_total_size_soft_limit: default_files_total_size_soft_limit(), - file_count_limit_percent_if_deleting: default_file_count_limit_percent_if_deleting(), - files_total_size_limit_percent_if_deleting: - default_files_total_size_limit_percent_if_deleting(), - } - } - - fn new_cache_enabled_template() -> Self { - let mut conf = Self::new_cache_disabled(); - conf.enabled = true; - conf + pub fn new() -> Self { + Self::default() } /// Loads cache configuration specified at `path`. @@ -455,12 +430,6 @@ impl CacheConfig { /// /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html pub fn from_file(config_file: Option<&Path>) -> Result { - let mut config = Self::load_and_parse_file(config_file)?; - config.validate()?; - Ok(config) - } - - fn load_and_parse_file(config_file: Option<&Path>) -> Result { // get config file path let (config_file, user_custom_file) = match config_file { Some(path) => (path.to_path_buf(), true), @@ -470,7 +439,7 @@ impl CacheConfig { // read config, or use default one let entity_exists = config_file.exists(); match (entity_exists, user_custom_file) { - (false, false) => Ok(Self::new_cache_enabled_template()), + (false, false) => Ok(Self::new()), _ => { let contents = fs::read_to_string(&config_file).context(format!( "failed to read config file: {}", @@ -485,170 +454,6 @@ impl CacheConfig { } } - /// validate values and fill in defaults - fn validate(&mut self) -> Result<()> { - self.validate_directory_or_default()?; - self.validate_worker_event_queue_size(); - self.validate_baseline_compression_level()?; - self.validate_optimized_compression_level()?; - self.validate_file_count_limit_percent_if_deleting()?; - self.validate_files_total_size_limit_percent_if_deleting()?; - Ok(()) - } - - fn validate_directory_or_default(&mut self) -> Result<()> { - if self.directory.is_none() { - match project_dirs() { - Some(proj_dirs) => self.directory = Some(proj_dirs.cache_dir().to_path_buf()), - None => { - bail!("Cache directory not specified and failed to get the default"); - } - } - } - - // On Windows, if we want long paths, we need '\\?\' prefix, but it doesn't work - // with relative paths. One way to get absolute path (the only one?) is to use - // fs::canonicalize, but it requires that given path exists. The extra advantage - // of this method is fact that the method prepends '\\?\' on Windows. - let cache_dir = self.directory.as_ref().unwrap(); - - if !cache_dir.is_absolute() { - bail!( - "Cache directory path has to be absolute, path: {}", - cache_dir.display(), - ); - } - - fs::create_dir_all(cache_dir).context(format!( - "failed to create cache directory: {}", - cache_dir.display() - ))?; - let canonical = fs::canonicalize(cache_dir).context(format!( - "failed to canonicalize cache directory: {}", - cache_dir.display() - ))?; - self.directory = Some(canonical); - Ok(()) - } - - fn validate_worker_event_queue_size(&self) { - if self.worker_event_queue_size < worker_event_queue_size_warning_threshold() { - warn!("Detected small worker event queue size. Some messages might be lost."); - } - } - - fn validate_baseline_compression_level(&self) -> Result<()> { - if !ZSTD_COMPRESSION_LEVELS.contains(&self.baseline_compression_level) { - bail!( - "Invalid baseline compression level: {} not in {:#?}", - self.baseline_compression_level, - ZSTD_COMPRESSION_LEVELS - ); - } - Ok(()) - } - - // assumption: baseline compression level has been verified - fn validate_optimized_compression_level(&self) -> Result<()> { - if !ZSTD_COMPRESSION_LEVELS.contains(&self.optimized_compression_level) { - bail!( - "Invalid optimized compression level: {} not in {:#?}", - self.optimized_compression_level, - ZSTD_COMPRESSION_LEVELS - ); - } - - if self.optimized_compression_level < self.baseline_compression_level { - bail!( - "Invalid optimized compression level is lower than baseline: {} < {}", - self.optimized_compression_level, - self.baseline_compression_level - ); - } - Ok(()) - } - - fn validate_file_count_limit_percent_if_deleting(&self) -> Result<()> { - if self.file_count_limit_percent_if_deleting > 100 { - bail!( - "Invalid files count limit percent if deleting: {} not in range 0-100%", - self.file_count_limit_percent_if_deleting - ); - } - Ok(()) - } - - fn validate_files_total_size_limit_percent_if_deleting(&self) -> Result<()> { - if self.files_total_size_limit_percent_if_deleting > 100 { - bail!( - "Invalid files total size limit percent if deleting: {} not in range 0-100%", - self.files_total_size_limit_percent_if_deleting - ); - } - Ok(()) - } - - /// Builds a [`Cache`] from the configuration and spawns the cache worker. - /// - /// # Errors - /// Returns an error if the configuration is invalid. - pub fn spawn(&mut self) -> Result { - self.validate()?; - - // Unwrapping because validation will ensure these are all set - Ok(Cache { - config: self.clone(), - worker: Worker::start_new(self), - state: Default::default(), - }) - } -} - -/// A builder for `CacheConfig`s. -#[derive(Debug, Clone)] -pub struct CacheConfigBuilder { - directory: Option, - worker_event_queue_size: u64, - baseline_compression_level: i32, - optimized_compression_level: i32, - optimized_compression_usage_counter_threshold: u64, - optimizing_compression_task_timeout: Duration, - cleanup_interval: Duration, - allowed_clock_drift_for_files_from_future: Duration, - file_count_soft_limit: u64, - files_total_size_soft_limit: u64, - file_count_limit_percent_if_deleting: u8, - files_total_size_limit_percent_if_deleting: u8, -} - -impl Default for CacheConfigBuilder { - fn default() -> Self { - Self { - directory: None, - worker_event_queue_size: default_worker_event_queue_size(), - baseline_compression_level: default_baseline_compression_level(), - optimized_compression_level: default_optimized_compression_level(), - optimized_compression_usage_counter_threshold: - default_optimized_compression_usage_counter_threshold(), - optimizing_compression_task_timeout: default_optimizing_compression_task_timeout(), - cleanup_interval: default_cleanup_interval(), - allowed_clock_drift_for_files_from_future: - default_allowed_clock_drift_for_files_from_future(), - file_count_soft_limit: default_file_count_soft_limit(), - files_total_size_soft_limit: default_files_total_size_soft_limit(), - file_count_limit_percent_if_deleting: default_file_count_limit_percent_if_deleting(), - files_total_size_limit_percent_if_deleting: - default_files_total_size_limit_percent_if_deleting(), - } - } -} - -impl CacheConfigBuilder { - /// Specify where the cache directory is. Must be an absolute path. - pub fn new() -> Self { - Self::default() - } - generate_setting_getter!(worker_event_queue_size: u64); generate_setting_getter!(baseline_compression_level: i32); generate_setting_getter!(optimized_compression_level: i32); @@ -663,7 +468,7 @@ impl CacheConfigBuilder { /// Returns path to the cache directory. /// - /// Panics if the directory hasn't been set yet. + /// Panics if the cache is disabled. pub fn directory(&self) -> &PathBuf { self.directory .as_ref() @@ -790,7 +595,7 @@ impl CacheConfigBuilder { /// validate values and fill in defaults fn validate(&mut self) -> Result<()> { - self.validate_directory()?; + self.validate_directory_or_default()?; self.validate_worker_event_queue_size(); self.validate_baseline_compression_level()?; self.validate_optimized_compression_level()?; @@ -799,7 +604,7 @@ impl CacheConfigBuilder { Ok(()) } - fn validate_directory(&mut self) -> Result<()> { + fn validate_directory_or_default(&mut self) -> Result<()> { if self.directory.is_none() { match project_dirs() { Some(proj_dirs) => self.directory = Some(proj_dirs.cache_dir().to_path_buf()), @@ -890,44 +695,6 @@ impl CacheConfigBuilder { } Ok(()) } - - pub fn build(mut self) -> Result { - self.validate()?; - let CacheConfigBuilder { - directory, - worker_event_queue_size, - baseline_compression_level, - optimized_compression_level, - optimized_compression_usage_counter_threshold, - cleanup_interval, - optimizing_compression_task_timeout, - allowed_clock_drift_for_files_from_future, - file_count_soft_limit, - files_total_size_soft_limit, - file_count_limit_percent_if_deleting, - files_total_size_limit_percent_if_deleting, - } = self; - - let mut config = CacheConfig { - enabled: true, - directory, - worker_event_queue_size, - baseline_compression_level, - optimized_compression_level, - optimized_compression_usage_counter_threshold, - cleanup_interval, - optimizing_compression_task_timeout, - allowed_clock_drift_for_files_from_future, - file_count_soft_limit, - files_total_size_soft_limit, - file_count_limit_percent_if_deleting, - files_total_size_limit_percent_if_deleting, - }; - - config.validate()?; - - Ok(config) - } } #[cfg(test)] diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index bd46f19be2b8..d82bfb434a05 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -1,5 +1,3 @@ -use crate::CacheConfigBuilder; - use super::CacheConfig; use std::fs; use std::path::PathBuf; @@ -36,18 +34,6 @@ macro_rules! bad_config { }}; } -// test without macros to test being disabled -#[test] -fn test_disabled() { - let dir = tempfile::tempdir().expect("Can't create temporary directory"); - let config_path = dir.path().join("cache-config.toml"); - let config_content = "[cache]\n\ - enabled = false\n"; - fs::write(&config_path, config_content).expect("Failed to write test config file"); - let conf = CacheConfig::from_file(Some(&config_path)).unwrap(); - assert!(!conf.enabled()); -} - #[test] fn test_unrecognized_settings() { let (_td, cd, cp) = test_prolog(); @@ -55,7 +41,6 @@ fn test_unrecognized_settings() { cp, "unrecognized-setting = 42\n\ [cache]\n\ - enabled = true\n\ directory = '{cache_dir}'", cd ); @@ -63,7 +48,6 @@ fn test_unrecognized_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ unrecognized-setting = 42", cd @@ -76,7 +60,6 @@ fn test_all_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ baseline-compression-level = 3\n\ @@ -97,7 +80,6 @@ fn test_all_settings() { cp, // added some white spaces "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = ' 16\t'\n\ baseline-compression-level = 3\n\ @@ -115,7 +97,6 @@ fn test_all_settings() { check_conf(&conf, &cd); fn check_conf(conf: &CacheConfig, cd: &PathBuf) { - assert!(conf.enabled()); assert_eq!( conf.directory(), &fs::canonicalize(cd).expect("canonicalize failed") @@ -146,20 +127,17 @@ fn test_compression_level_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ baseline-compression-level = 1\n\ optimized-compression-level = 21", cd ); - assert!(conf.enabled()); assert_eq!(conf.baseline_compression_level(), 1); assert_eq!(conf.optimized_compression_level(), 21); bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ baseline-compression-level = -1\n\ optimized-compression-level = 21", @@ -169,7 +147,6 @@ fn test_compression_level_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ baseline-compression-level = 15\n\ optimized-compression-level = 10", @@ -183,14 +160,12 @@ fn test_si_prefix_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '42'\n\ optimized-compression-usage-counter-threshold = '4K'\n\ file-count-soft-limit = '3M'", cd ); - assert!(conf.enabled()); assert_eq!(conf.worker_event_queue_size(), 42); assert_eq!(conf.optimized_compression_usage_counter_threshold(), 4_000); assert_eq!(conf.file_count_soft_limit(), 3_000_000); @@ -198,14 +173,12 @@ fn test_si_prefix_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '2K'\n\ optimized-compression-usage-counter-threshold = '4444T'\n\ file-count-soft-limit = '1P'", cd ); - assert!(conf.enabled()); assert_eq!(conf.worker_event_queue_size(), 2_000); assert_eq!( conf.optimized_compression_usage_counter_threshold(), @@ -217,7 +190,6 @@ fn test_si_prefix_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '2g'", cd @@ -226,7 +198,6 @@ fn test_si_prefix_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ file-count-soft-limit = 1", cd @@ -235,7 +206,6 @@ fn test_si_prefix_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ file-count-soft-limit = '-31337'", cd @@ -244,7 +214,6 @@ fn test_si_prefix_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ file-count-soft-limit = '3.14M'", cd @@ -257,74 +226,61 @@ fn test_disk_space_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '76'", cd ); - assert!(conf.enabled()); assert_eq!(conf.files_total_size_soft_limit(), 76); let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '42 Mi'", cd ); - assert!(conf.enabled()); assert_eq!(conf.files_total_size_soft_limit(), 42 * (1u64 << 20)); let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '2 Gi'", cd ); - assert!(conf.enabled()); assert_eq!(conf.files_total_size_soft_limit(), 2 * (1u64 << 30)); let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '31337 Ti'", cd ); - assert!(conf.enabled()); assert_eq!(conf.files_total_size_soft_limit(), 31337 * (1u64 << 40)); let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '7 Pi'", cd ); - assert!(conf.enabled()); assert_eq!(conf.files_total_size_soft_limit(), 7 * (1u64 << 50)); let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '7M'", cd ); - assert!(conf.enabled()); assert_eq!(conf.files_total_size_soft_limit(), 7_000_000); // different errors bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '7 mi'", cd @@ -333,7 +289,6 @@ fn test_disk_space_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = 1", cd @@ -342,7 +297,6 @@ fn test_disk_space_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '-31337'", cd @@ -351,7 +305,6 @@ fn test_disk_space_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-soft-limit = '3.14Ki'", cd @@ -364,14 +317,12 @@ fn test_duration_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ cleanup-interval = '100s'\n\ optimizing-compression-task-timeout = '3m'\n\ allowed-clock-drift-for-files-from-future = '4h'", cd ); - assert!(conf.enabled()); assert_eq!(conf.cleanup_interval(), Duration::from_secs(100)); assert_eq!( conf.optimizing_compression_task_timeout(), @@ -385,13 +336,11 @@ fn test_duration_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ cleanup-interval = '2d'\n\ optimizing-compression-task-timeout = '333 m'", cd ); - assert!(conf.enabled()); assert_eq!( conf.cleanup_interval(), Duration::from_secs(2 * 24 * 60 * 60) @@ -405,7 +354,6 @@ fn test_duration_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ optimizing-compression-task-timeout = '333'", cd @@ -414,7 +362,6 @@ fn test_duration_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ optimizing-compression-task-timeout = 333", cd @@ -423,7 +370,6 @@ fn test_duration_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ optimizing-compression-task-timeout = '10 M'", cd @@ -432,7 +378,6 @@ fn test_duration_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ optimizing-compression-task-timeout = '10 min'", cd @@ -441,7 +386,6 @@ fn test_duration_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ optimizing-compression-task-timeout = '-10s'", cd @@ -450,7 +394,6 @@ fn test_duration_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ optimizing-compression-task-timeout = '1.5m'", cd @@ -463,13 +406,11 @@ fn test_percent_settings() { let conf = load_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ file-count-limit-percent-if-deleting = '62%'\n\ files-total-size-limit-percent-if-deleting = '23 %'", cd ); - assert!(conf.enabled()); assert_eq!(conf.file_count_limit_percent_if_deleting(), 62); assert_eq!(conf.files_total_size_limit_percent_if_deleting(), 23); @@ -477,7 +418,6 @@ fn test_percent_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-limit-percent-if-deleting = '23'", cd @@ -486,7 +426,6 @@ fn test_percent_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-limit-percent-if-deleting = '22.5%'", cd @@ -495,7 +434,6 @@ fn test_percent_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-limit-percent-if-deleting = '0.5'", cd @@ -504,7 +442,6 @@ fn test_percent_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-limit-percent-if-deleting = '-1%'", cd @@ -513,7 +450,6 @@ fn test_percent_settings() { bad_config!( cp, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ files-total-size-limit-percent-if-deleting = '101%'", cd @@ -524,16 +460,12 @@ fn test_percent_settings() { #[test] fn test_builder_default() { let (_td, _cd, cp) = test_prolog(); - let config_content = "[cache]\n\ - enabled = true\n"; + let config_content = "[cache]\n"; fs::write(&cp, config_content).expect("Failed to write test config file"); let expected_config = CacheConfig::from_file(Some(&cp)).unwrap(); - let config = CacheConfigBuilder::new() - .build() - .expect("Failed to build CacheConfig"); + let config = CacheConfig::new(); - assert_eq!(config.enabled, expected_config.enabled); assert_eq!(config.directory, expected_config.directory); assert_eq!( config.worker_event_queue_size, @@ -582,9 +514,8 @@ fn test_builder_default() { fn test_builder_all_settings() { let (_td, cd, _cp) = test_prolog(); - let mut builder = CacheConfigBuilder::new(); - builder - .with_directory(&cd) + let mut conf = CacheConfig::new(); + conf.with_directory(&cd) .with_worker_event_queue_size(0x10) .with_baseline_compression_level(3) .with_optimized_compression_level(20) @@ -596,11 +527,10 @@ fn test_builder_all_settings() { .with_files_total_size_soft_limit(512 * (1u64 << 20)) .with_file_count_limit_percent_if_deleting(70) .with_files_total_size_limit_percent_if_deleting(70); - let conf = builder.build().expect("Failed to build config"); + conf.validate().expect("validation failed"); check_conf(&conf, &cd); fn check_conf(conf: &CacheConfig, cd: &PathBuf) { - assert!(conf.enabled()); assert_eq!( conf.directory(), &fs::canonicalize(cd).expect("canonicalize failed") diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index 43fa40f5abe4..ae67f592b34a 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -12,7 +12,7 @@ use std::{fs, io}; mod config; mod worker; -pub use config::{create_new_config, Cache, CacheConfig, CacheConfigBuilder}; +pub use config::{create_new_config, Cache, CacheConfig}; use worker::Worker; /// Module level cache entry. diff --git a/crates/cache/src/tests.rs b/crates/cache/src/tests.rs index 18eeff1eeee4..a8a8c9169b7e 100644 --- a/crates/cache/src/tests.rs +++ b/crates/cache/src/tests.rs @@ -13,7 +13,6 @@ fn test_cache_init() { let baseline_compression_level = 4; let config_content = format!( "[cache]\n\ - enabled = true\n\ directory = '{}'\n\ baseline-compression-level = {}\n", cache_dir.display(), @@ -21,10 +20,8 @@ fn test_cache_init() { ); fs::write(&config_path, config_content).expect("Failed to write test config file"); - let mut cache_config = CacheConfig::from_file(Some(&config_path)).unwrap(); + let cache_config = CacheConfig::from_file(Some(&config_path)).unwrap(); - // test if we can use config - assert!(cache_config.enabled()); // assumption: config init creates cache directory and returns canonicalized path assert_eq!( *cache_config.directory(), @@ -36,8 +33,7 @@ fn test_cache_init() { ); // test if we can use worker - cache_config - .spawn() + Cache::new(cache_config) .unwrap() .worker() .on_cache_update_async(config_path); @@ -46,16 +42,14 @@ fn test_cache_init() { #[test] fn test_write_read_cache() { let (_tempdir, cache_dir, config_path) = test_prolog(); - let mut cache_config = load_config!( + let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ baseline-compression-level = 3\n", cache_dir ); - assert!(cache_config.enabled()); - let cache = cache_config.spawn().unwrap(); + let cache = Cache::new(cache_config.clone()).unwrap(); // assumption: config load creates cache directory and returns canonicalized path assert_eq!( diff --git a/crates/cache/src/worker/tests.rs b/crates/cache/src/worker/tests.rs index 7392962d77d0..2d11366e7bb8 100644 --- a/crates/cache/src/worker/tests.rs +++ b/crates/cache/src/worker/tests.rs @@ -14,11 +14,9 @@ fn test_on_get_create_stats_file() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); @@ -41,12 +39,10 @@ fn test_on_get_update_usage_counter() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); @@ -75,7 +71,6 @@ fn test_on_get_recompress_no_mod_file() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ baseline-compression-level = 3\n\ @@ -83,7 +78,6 @@ fn test_on_get_recompress_no_mod_file() { optimized-compression-usage-counter-threshold = '256'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); @@ -117,7 +111,6 @@ fn test_on_get_recompress_with_mod_file() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ baseline-compression-level = 3\n\ @@ -125,7 +118,6 @@ fn test_on_get_recompress_with_mod_file() { optimized-compression-usage-counter-threshold = '256'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); @@ -192,7 +184,6 @@ fn test_on_get_recompress_lock() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ baseline-compression-level = 3\n\ @@ -202,7 +193,6 @@ fn test_on_get_recompress_lock() { allowed-clock-drift-for-files-from-future = '1d'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); @@ -262,7 +252,6 @@ fn test_on_update_fresh_stats_file() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ baseline-compression-level = 3\n\ @@ -270,7 +259,6 @@ fn test_on_update_fresh_stats_file() { cleanup-interval = '1h'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); @@ -311,7 +299,6 @@ fn test_on_update_cleanup_limits_trash_locks() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ cleanup-interval = '30m'\n\ @@ -324,7 +311,6 @@ fn test_on_update_cleanup_limits_trash_locks() { ", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let content_1k = "a".repeat(1_000); let content_10k = "a".repeat(10_000); @@ -452,7 +438,6 @@ fn test_on_update_cleanup_lru_policy() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ file-count-soft-limit = '5'\n\ @@ -461,7 +446,6 @@ fn test_on_update_cleanup_lru_policy() { files-total-size-limit-percent-if-deleting = '70%'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let content_1k = "a".repeat(1_000); let content_5k = "a".repeat(5_000); @@ -584,7 +568,6 @@ fn test_on_update_cleanup_future_files() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ allowed-clock-drift-for-files-from-future = '1d'\n\ @@ -594,7 +577,6 @@ fn test_on_update_cleanup_future_files() { files-total-size-limit-percent-if-deleting = '70%'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let content_1k = "a".repeat(1_000); @@ -692,14 +674,12 @@ fn test_on_update_cleanup_self_lock() { let cache_config = load_config!( config_path, "[cache]\n\ - enabled = true\n\ directory = '{cache_dir}'\n\ worker-event-queue-size = '16'\n\ cleanup-interval = '30m'\n\ allowed-clock-drift-for-files-from-future = '1d'", cache_dir ); - assert!(cache_config.enabled()); let worker = Worker::start_new(&cache_config); let mod_file = cache_dir.join("some-mod"); diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 56368a208d67..32cedc4ec37f 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -27,7 +27,7 @@ use wasmtime_fiber::RuntimeFiberStackCreator; #[cfg(feature = "runtime")] pub use crate::runtime::code_memory::CustomCodeMemory; #[cfg(feature = "cache")] -pub use wasmtime_cache::{Cache, CacheConfig, CacheConfigBuilder}; +pub use wasmtime_cache::{Cache, CacheConfig}; #[cfg(all(feature = "incremental-cache", feature = "cranelift"))] pub use wasmtime_environ::CacheStore; @@ -1386,8 +1386,8 @@ impl Config { /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html #[cfg(feature = "cache")] pub fn cache_config(&mut self, cache_config: Option) -> Result<&mut Self> { - self.cache = if let Some(mut cache_config) = cache_config { - Some(cache_config.spawn()?) + self.cache = if let Some(cache_config) = cache_config { + Some(Cache::new(cache_config)?) } else { None }; diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 0721cfffba6d..5a46a4eac893 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -656,7 +656,6 @@ Caused by: &format!( " [cache] - enabled = true directory = '{}' ", td.path().join("cache").display() @@ -785,7 +784,6 @@ Caused by: &format!( " [cache] - enabled = true directory = '{}' ", td.path().join("cache").display() diff --git a/docs/cli-cache.md b/docs/cli-cache.md index f8c6622971e3..72cb2886b947 100644 --- a/docs/cli-cache.md +++ b/docs/cli-cache.md @@ -18,7 +18,6 @@ Wasmtime assumes all the options are in the `cache` section. Example config: ```toml [cache] -enabled = true directory = "/nfs-share/wasmtime-cache/" cleanup-interval = "30m" files-total-size-soft-limit = "1Gi" From b1dac37dad8a0a43e660292cf35441cc5fc15c69 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 29 Apr 2025 13:58:42 +0200 Subject: [PATCH 11/14] Set Cache directly on wastime::Config and make it easier to create one from a file --- crates/bench-api/src/lib.rs | 2 +- crates/c-api/src/config.rs | 9 ++++---- crates/cache/src/config.rs | 25 +++++++++++++++++++++ crates/cli-flags/src/lib.rs | 16 +++++-------- crates/wasmtime/src/config.rs | 16 +++++-------- crates/wasmtime/src/engine/serialization.rs | 12 +++++----- examples/fast_compilation.rs | 4 ++-- src/commands/explore.rs | 2 +- 8 files changed, 52 insertions(+), 34 deletions(-) diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index 5f54c247c551..4c0f5de7aed6 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -436,7 +436,7 @@ impl BenchState { ) -> Result { let mut config = options.config(None)?; // NB: always disable the compilation cache. - config.cache_config(None)?; + config.cache(None); let engine = Engine::new(&config)?; let mut linker = Linker::::new(&engine); diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs index 65698b154b48..2ce7cba95928 100644 --- a/crates/c-api/src/config.rs +++ b/crates/c-api/src/config.rs @@ -213,15 +213,16 @@ pub unsafe extern "C" fn wasmtime_config_cache_config_load( ) -> Option> { use std::path::Path; - use wasmtime::CacheConfig; + use wasmtime::Cache; handle_result( if filename.is_null() { - CacheConfig::from_file(None).map(|cfg| c.config.cache_config(Some(cfg))) + Cache::from_file(None).map(|cache| c.config.cache(Some(cache))) } else { match CStr::from_ptr(filename).to_str() { - Ok(s) => CacheConfig::from_file(Some(&Path::new(s))) - .map(|cfg| c.config.cache_config(Some(cfg))), + Ok(s) => { + Cache::from_file(Some(&Path::new(s))).map(|cache| c.config.cache(Some(cache))) + } Err(e) => Err(e.into()), } }, diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index e6bf0ad016d8..10a7a284549f 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -37,6 +37,9 @@ macro_rules! generate_config_setting_getter { impl Cache { /// Builds a [`Cache`] from the configuration and spawns the cache worker. /// + /// If you want to load the cache configuration from a file, use [`CacheConfig::from_file`]. + /// You can call [`CacheConfig::new`] for the default configuration. + /// /// # Errors /// Returns an error if the configuration is invalid. pub fn new(mut config: CacheConfig) -> Result { @@ -48,6 +51,28 @@ impl Cache { }) } + /// Loads cache configuration specified at `path`. + /// + /// This method will read the file specified by `path` on the filesystem and + /// attempt to load cache configuration from it. This method can also fail + /// due to I/O errors, misconfiguration, syntax errors, etc. For expected + /// syntax in the configuration file see the [documentation online][docs]. + /// + /// Passing in `None` loads cache configuration from the system default path. + /// This is located, for example, on Unix at `$HOME/.config/wasmtime/config.toml` + /// and is typically created with the `wasmtime config new` command. + /// + /// # Errors + /// + /// This method can fail due to any error that happens when loading the file + /// pointed to by `path` and attempting to load the cache configuration. + /// + /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html + pub fn from_file(path: Option<&Path>) -> Result { + let config = CacheConfig::from_file(path)?; + Self::new(config) + } + generate_config_setting_getter!(worker_event_queue_size: u64); generate_config_setting_getter!(baseline_compression_level: i32); generate_config_setting_getter!(optimized_compression_level: i32); diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index dccc7b5fb6ce..d61db2d55369 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -735,16 +735,12 @@ impl CommonOptions { #[cfg(feature = "cache")] if self.codegen.cache != Some(false) { - match &self.codegen.cache_config { - Some(path) => { - config.cache_config(Some(wasmtime::CacheConfig::from_file(Some( - Path::new(path), - ))?))?; - } - None => { - config.cache_config(Some(wasmtime::CacheConfig::from_file(None)?))?; - } - } + use wasmtime::Cache; + let cache = match &self.codegen.cache_config { + Some(path) => Cache::from_file(Some(Path::new(path)))?, + None => Cache::from_file(None)?, + }; + config.cache(Some(cache)); } #[cfg(not(feature = "cache"))] if self.codegen.cache == Some(true) { diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 32cedc4ec37f..539e93d473ca 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1369,10 +1369,10 @@ impl Config { self } - /// Set a custom cache configuration. + /// Set a custom [`Cache`]. /// - /// If you want to load the cache configuration from a file, use [`CacheConfig::from_file`]. - /// You can call [`CacheConfig::from_file(None)`] for the default, enabled configuration. + /// To load a cache from a file, use [`Cache::from_file`]. Otherwise, you can create a new + /// cache config using [`CacheConfig::new`] and passing that to [`Cache::new`]. /// /// If you want to disable the cache, you can call this method with `None`. /// @@ -1385,13 +1385,9 @@ impl Config { /// /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html #[cfg(feature = "cache")] - pub fn cache_config(&mut self, cache_config: Option) -> Result<&mut Self> { - self.cache = if let Some(cache_config) = cache_config { - Some(Cache::new(cache_config)?) - } else { - None - }; - Ok(self) + pub fn cache(&mut self, cache: Option) -> &mut Self { + self.cache = cache; + self } /// Sets a custom memory creator. diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 5a46a4eac893..d3f62bdb3516 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -446,7 +446,7 @@ impl Metadata<'_> { #[cfg(test)] mod test { use super::*; - use crate::{CacheConfig, Config, Module, OptLevel}; + use crate::{Cache, Config, Module, OptLevel}; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, @@ -663,7 +663,7 @@ Caused by: )?; let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::None) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; + .cache(Some(Cache::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; let cache_config = engine @@ -679,7 +679,7 @@ Caused by: let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::Speed) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; + .cache(Some(Cache::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; let cache_config = engine .config() @@ -695,7 +695,7 @@ Caused by: let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::SpeedAndSize) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; + .cache(Some(Cache::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; let cache_config = engine .config() @@ -711,7 +711,7 @@ Caused by: let mut cfg = Config::new(); cfg.debug_info(true) - .cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; + .cache(Some(Cache::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; let cache_config = engine .config() @@ -790,7 +790,7 @@ Caused by: ), )?; let mut cfg = Config::new(); - cfg.cache_config(Some(CacheConfig::from_file(Some(&config_path))?))?; + cfg.cache(Some(Cache::from_file(Some(&config_path))?)); let engine = Engine::new(&cfg)?; let cache_config = engine .config() diff --git a/examples/fast_compilation.rs b/examples/fast_compilation.rs index 474e4eb0267f..eae3887a69a9 100644 --- a/examples/fast_compilation.rs +++ b/examples/fast_compilation.rs @@ -3,14 +3,14 @@ //! If your application design is compatible with pre-compiling Wasm programs, //! prefer doing that. -use wasmtime::{CacheConfig, Config, Engine, Result, Strategy}; +use wasmtime::{Cache, Config, Engine, Result, Strategy}; fn main() -> Result<()> { let mut config = Config::new(); // Enable the compilation cache, using the default cache configuration // settings. - config.cache_config(Some(CacheConfig::from_file(None)?))?; + config.cache(Some(Cache::from_file(None)?)); // Enable Winch, Wasmtime's baseline compiler. config.strategy(Strategy::Winch); diff --git a/src/commands/explore.rs b/src/commands/explore.rs index df5f7bb43f5c..f0cf2f155eaa 100644 --- a/src/commands/explore.rs +++ b/src/commands/explore.rs @@ -51,7 +51,7 @@ impl ExploreCommand { let clif_dir = if let Some(Strategy::Cranelift) | None = self.common.codegen.compiler { let clif_dir = tempdir()?; config.emit_clif(clif_dir.path()); - config.cache_config(None)?; // cache does not emit clif + config.cache(None); // cache does not emit clif Some(clif_dir) } else { None From be4511587b8421c1e6abcb616edc30b418bf8b2f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 29 Apr 2025 14:03:24 +0200 Subject: [PATCH 12/14] Validate after loading file again --- crates/cache/src/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index 10a7a284549f..e29f6e2ea671 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -455,6 +455,12 @@ impl CacheConfig { /// /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html pub fn from_file(config_file: Option<&Path>) -> Result { + let mut config = Self::load_and_parse_file(config_file)?; + config.validate()?; + Ok(config) + } + + fn load_and_parse_file(config_file: Option<&Path>) -> Result { // get config file path let (config_file, user_custom_file) = match config_file { Some(path) => (path.to_path_buf(), true), From be091b3567948b291fc135289959b72529ceb976 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 29 Apr 2025 14:07:55 +0200 Subject: [PATCH 13/14] Move cache to top-level module --- crates/cache/src/config.rs | 114 +----------------------------------- crates/cache/src/lib.rs | 115 ++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 114 deletions(-) diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index e29f6e2ea671..278ca3e7ad0a 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -1,6 +1,5 @@ //! Module for configuring the cache system. -use super::Worker; use anyhow::{anyhow, bail, Context, Result}; use directories_next::ProjectDirs; use log::{trace, warn}; @@ -11,119 +10,8 @@ use serde::{ use std::fmt::Debug; use std::fs; use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; -use std::sync::Arc; use std::time::Duration; -/// Global configuration for how the cache is managed -#[derive(Debug, Clone)] -pub struct Cache { - config: CacheConfig, - worker: Worker, - state: Arc, -} - -macro_rules! generate_config_setting_getter { - ($setting:ident: $setting_type:ty) => { - /// Returns `$setting`. - /// - /// Panics if the cache is disabled. - pub fn $setting(&self) -> $setting_type { - self.config.$setting() - } - }; -} - -impl Cache { - /// Builds a [`Cache`] from the configuration and spawns the cache worker. - /// - /// If you want to load the cache configuration from a file, use [`CacheConfig::from_file`]. - /// You can call [`CacheConfig::new`] for the default configuration. - /// - /// # Errors - /// Returns an error if the configuration is invalid. - pub fn new(mut config: CacheConfig) -> Result { - config.validate()?; - Ok(Self { - worker: Worker::start_new(&config), - config, - state: Default::default(), - }) - } - - /// Loads cache configuration specified at `path`. - /// - /// This method will read the file specified by `path` on the filesystem and - /// attempt to load cache configuration from it. This method can also fail - /// due to I/O errors, misconfiguration, syntax errors, etc. For expected - /// syntax in the configuration file see the [documentation online][docs]. - /// - /// Passing in `None` loads cache configuration from the system default path. - /// This is located, for example, on Unix at `$HOME/.config/wasmtime/config.toml` - /// and is typically created with the `wasmtime config new` command. - /// - /// # Errors - /// - /// This method can fail due to any error that happens when loading the file - /// pointed to by `path` and attempting to load the cache configuration. - /// - /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html - pub fn from_file(path: Option<&Path>) -> Result { - let config = CacheConfig::from_file(path)?; - Self::new(config) - } - - generate_config_setting_getter!(worker_event_queue_size: u64); - generate_config_setting_getter!(baseline_compression_level: i32); - generate_config_setting_getter!(optimized_compression_level: i32); - generate_config_setting_getter!(optimized_compression_usage_counter_threshold: u64); - generate_config_setting_getter!(cleanup_interval: Duration); - generate_config_setting_getter!(optimizing_compression_task_timeout: Duration); - generate_config_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); - generate_config_setting_getter!(file_count_soft_limit: u64); - generate_config_setting_getter!(files_total_size_soft_limit: u64); - generate_config_setting_getter!(file_count_limit_percent_if_deleting: u8); - generate_config_setting_getter!(files_total_size_limit_percent_if_deleting: u8); - - /// Returns path to the cache directory. - /// - /// Panics if the cache directory is not set. - pub fn directory(&self) -> &PathBuf { - &self.config.directory() - } - - #[cfg(test)] - pub(super) fn worker(&self) -> &Worker { - &self.worker - } - - /// Returns the number of cache hits seen so far - pub fn cache_hits(&self) -> usize { - self.state.hits.load(SeqCst) - } - - /// Returns the number of cache misses seen so far - pub fn cache_misses(&self) -> usize { - self.state.misses.load(SeqCst) - } - - pub(crate) fn on_cache_get_async(&self, path: impl AsRef) { - self.state.hits.fetch_add(1, SeqCst); - self.worker.on_cache_get_async(path) - } - - pub(crate) fn on_cache_update_async(&self, path: impl AsRef) { - self.state.misses.fetch_add(1, SeqCst); - self.worker.on_cache_update_async(path) - } -} - -#[derive(Default, Debug)] -struct CacheState { - hits: AtomicUsize, - misses: AtomicUsize, -} - // wrapped, so we have named section in config, // also, for possible future compatibility #[derive(serde_derive::Deserialize, Debug)] @@ -625,7 +513,7 @@ impl CacheConfig { } /// validate values and fill in defaults - fn validate(&mut self) -> Result<()> { + pub(crate) fn validate(&mut self) -> Result<()> { self.validate_directory_or_default()?; self.validate_worker_event_queue_size(); self.validate_baseline_compression_level()?; diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index ae67f592b34a..bdf34e41a987 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use base64::Engine; use log::{debug, trace, warn}; use serde::{Deserialize, Serialize}; @@ -6,15 +7,127 @@ use std::hash::Hash; use std::hash::Hasher; use std::io::Write; use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; +use std::sync::Arc; +use std::time::Duration; use std::{fs, io}; #[macro_use] // for tests mod config; mod worker; -pub use config::{create_new_config, Cache, CacheConfig}; +pub use config::{create_new_config, CacheConfig}; use worker::Worker; +/// Global configuration for how the cache is managed +#[derive(Debug, Clone)] +pub struct Cache { + config: CacheConfig, + worker: Worker, + state: Arc, +} + +macro_rules! generate_config_setting_getter { + ($setting:ident: $setting_type:ty) => { + /// Returns `$setting`. + /// + /// Panics if the cache is disabled. + pub fn $setting(&self) -> $setting_type { + self.config.$setting() + } + }; +} + +impl Cache { + /// Builds a [`Cache`] from the configuration and spawns the cache worker. + /// + /// If you want to load the cache configuration from a file, use [`CacheConfig::from_file`]. + /// You can call [`CacheConfig::new`] for the default configuration. + /// + /// # Errors + /// Returns an error if the configuration is invalid. + pub fn new(mut config: CacheConfig) -> Result { + config.validate()?; + Ok(Self { + worker: Worker::start_new(&config), + config, + state: Default::default(), + }) + } + + /// Loads cache configuration specified at `path`. + /// + /// This method will read the file specified by `path` on the filesystem and + /// attempt to load cache configuration from it. This method can also fail + /// due to I/O errors, misconfiguration, syntax errors, etc. For expected + /// syntax in the configuration file see the [documentation online][docs]. + /// + /// Passing in `None` loads cache configuration from the system default path. + /// This is located, for example, on Unix at `$HOME/.config/wasmtime/config.toml` + /// and is typically created with the `wasmtime config new` command. + /// + /// # Errors + /// + /// This method can fail due to any error that happens when loading the file + /// pointed to by `path` and attempting to load the cache configuration. + /// + /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html + pub fn from_file(path: Option<&Path>) -> Result { + let config = CacheConfig::from_file(path)?; + Self::new(config) + } + + generate_config_setting_getter!(worker_event_queue_size: u64); + generate_config_setting_getter!(baseline_compression_level: i32); + generate_config_setting_getter!(optimized_compression_level: i32); + generate_config_setting_getter!(optimized_compression_usage_counter_threshold: u64); + generate_config_setting_getter!(cleanup_interval: Duration); + generate_config_setting_getter!(optimizing_compression_task_timeout: Duration); + generate_config_setting_getter!(allowed_clock_drift_for_files_from_future: Duration); + generate_config_setting_getter!(file_count_soft_limit: u64); + generate_config_setting_getter!(files_total_size_soft_limit: u64); + generate_config_setting_getter!(file_count_limit_percent_if_deleting: u8); + generate_config_setting_getter!(files_total_size_limit_percent_if_deleting: u8); + + /// Returns path to the cache directory. + /// + /// Panics if the cache directory is not set. + pub fn directory(&self) -> &PathBuf { + &self.config.directory() + } + + #[cfg(test)] + fn worker(&self) -> &Worker { + &self.worker + } + + /// Returns the number of cache hits seen so far + pub fn cache_hits(&self) -> usize { + self.state.hits.load(SeqCst) + } + + /// Returns the number of cache misses seen so far + pub fn cache_misses(&self) -> usize { + self.state.misses.load(SeqCst) + } + + pub(crate) fn on_cache_get_async(&self, path: impl AsRef) { + self.state.hits.fetch_add(1, SeqCst); + self.worker.on_cache_get_async(path) + } + + pub(crate) fn on_cache_update_async(&self, path: impl AsRef) { + self.state.misses.fetch_add(1, SeqCst); + self.worker.on_cache_update_async(path) + } +} + +#[derive(Default, Debug)] +struct CacheState { + hits: AtomicUsize, + misses: AtomicUsize, +} + /// Module level cache entry. pub struct ModuleCacheEntry<'cache>(Option>); From dd1f91d9d549c2f10ced170febbd7d978b12fc24 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 29 Apr 2025 14:10:50 +0200 Subject: [PATCH 14/14] Fix tests --- crates/cache/src/config/tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index d82bfb434a05..45e50edbe421 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -464,7 +464,10 @@ fn test_builder_default() { fs::write(&cp, config_content).expect("Failed to write test config file"); let expected_config = CacheConfig::from_file(Some(&cp)).unwrap(); - let config = CacheConfig::new(); + let mut config = CacheConfig::new(); + config + .validate() + .expect("Failed to validate default config"); assert_eq!(config.directory, expected_config.directory); assert_eq!(