diff --git a/Cargo.lock b/Cargo.lock index 470952488..9eba6baf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,6 +1433,26 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.3" @@ -1550,6 +1570,26 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1776,6 +1816,22 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "notify" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +dependencies = [ + "bitflags", + "filetime", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2926,6 +2982,7 @@ dependencies = [ "memchr", "memmap2", "mime", + "notify", "nix 0.28.0", "number_prefix", "object 0.37.1", diff --git a/Cargo.toml b/Cargo.toml index 6c6113baa..5803339e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,7 @@ walkdir = "2" which = { version = "6", default-features = false } zip = { version = "0.6", default-features = false } zstd = "0.13" +notify = { version = "5", default-features = false, features = [] } # dist-server only nix = { version = "0.28.0", optional = true, features = [ diff --git a/src/config.rs b/src/config.rs index 8edaeb099..7547af712 100644 --- a/src/config.rs +++ b/src/config.rs @@ -978,6 +978,7 @@ pub struct Config { pub fallback_cache: DiskCacheConfig, pub dist: DistConfig, pub server_startup_timeout: Option, + pub file_conf_path: PathBuf, } impl Config { @@ -989,10 +990,18 @@ impl Config { .context("Failed to load config file")? .unwrap_or_default(); - Ok(Self::from_env_and_file_configs(env_conf, file_conf)) + Ok(Self::from_env_and_file_configs( + env_conf, + file_conf, + file_conf_path, + )) } - fn from_env_and_file_configs(env_conf: EnvConfig, file_conf: FileConfig) -> Self { + fn from_env_and_file_configs( + env_conf: EnvConfig, + file_conf: FileConfig, + file_conf_path: PathBuf, + ) -> Self { let mut conf_caches: CacheConfigs = Default::default(); let FileConfig { @@ -1014,6 +1023,7 @@ impl Config { fallback_cache, dist, server_startup_timeout, + file_conf_path, } } } @@ -1036,28 +1046,56 @@ pub struct CachedFileConfig { pub struct CachedConfig(()); impl CachedConfig { + fn setup_watcher() -> Result<()> { + use notify::{recommended_watcher, Event, EventKind, RecursiveMode, Watcher}; + + let path = Self::file_config_path(); + let mut watcher = recommended_watcher({ + let path = path.clone(); + move |res: std::result::Result| match res { + Ok(ref event) => match &event.kind { + EventKind::Modify(_) => { + info!("Reloading {} due to modification", path.display()); + let _res = Self::reload(); + } + EventKind::Create(_) => { + info!("Reloading {} due to creation", path.display()); + let _res = Self::reload(); + } + _ => {} + }, + Err(e) => warn!("Failed to watch due to an error: {:?}", e), + } + })?; + watcher.watch(&path, RecursiveMode::NonRecursive)?; + Ok(()) + } + pub fn load() -> Result { let mut cached_file_config = CACHED_CONFIG.lock().unwrap(); if cached_file_config.is_none() { let cfg = Self::load_file_config().context("Unable to initialise cached config")?; *cached_file_config = Some(cfg); + Self::setup_watcher()?; } Ok(CachedConfig(())) } + pub fn reload() -> Result { - { - let mut cached_file_config = CACHED_CONFIG.lock().unwrap(); - *cached_file_config = None; - }; - Self::load() + let mut cached_file_config = CACHED_CONFIG.lock().unwrap(); + let cfg = Self::load_file_config().context("Unable to initialise cached config")?; + cached_file_config.replace(cfg); + Ok(CachedConfig(())) } + pub fn with T, T>(&self, f: F) -> T { let cached_file_config = CACHED_CONFIG.lock().unwrap(); let cached_file_config = cached_file_config.as_ref().unwrap(); f(cached_file_config) } + pub fn with_mut(&self, f: F) -> Result<()> { let mut cached_file_config = CACHED_CONFIG.lock().unwrap(); let cached_file_config = cached_file_config.as_mut().unwrap(); @@ -1316,7 +1354,7 @@ fn config_overrides() { }; assert_eq!( - Config::from_env_and_file_configs(env_conf, file_conf), + Config::from_env_and_file_configs(env_conf, file_conf, PathBuf::new()), Config { cache: Some(CacheType::Redis(RedisCacheConfig { endpoint: Some("myotherredisurl".to_owned()), @@ -1335,6 +1373,7 @@ fn config_overrides() { }, dist: Default::default(), server_startup_timeout: None, + file_conf_path: PathBuf::new(), // Not testing config reloading here } ); }