From a78e3bf37406b267d3bef96f8494b12533589de6 Mon Sep 17 00:00:00 2001 From: "glm-5.1" Date: Fri, 12 Jun 2026 04:35:20 +0000 Subject: [PATCH] Fix ConfigReloadHandle static config drift causing stale diff warnings Change ConfigReloadHandle.static_config from StaticConfig to ArcSwap so that after each reload, the stored static config is updated with the new value. This prevents repeated stale warnings about the same static config fields on every reload. --- src/config/dynamic_config.rs | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/config/dynamic_config.rs b/src/config/dynamic_config.rs index b7c9d7a..a558cb5 100644 --- a/src/config/dynamic_config.rs +++ b/src/config/dynamic_config.rs @@ -104,7 +104,7 @@ pub struct BodyConfig { pub struct ConfigReloadHandle { config: Arc>, - static_config: StaticConfig, + static_config: ArcSwap, reload_mutex: Mutex<()>, } @@ -112,7 +112,7 @@ impl ConfigReloadHandle { pub fn new(config: Arc>, static_config: StaticConfig) -> Self { Self { config, - static_config, + static_config: ArcSwap::from_pointee(static_config), reload_mutex: Mutex::new(()), } } @@ -121,6 +121,10 @@ impl ConfigReloadHandle { self.config.load_full() } + pub fn static_config(&self) -> Arc { + self.static_config.load_full() + } + pub async fn reload( &self, new_static: StaticConfig, @@ -139,9 +143,10 @@ impl ConfigReloadHandle { ) })?; - let changed_fields = diff_static_config(&self.static_config, &new_static); + let changed_fields = diff_static_config(&self.static_config.load(), &new_static); self.config.store(Arc::new(new_dynamic)); + self.static_config.store(Arc::new(new_static)); Ok(changed_fields) } @@ -283,6 +288,58 @@ mod tests { assert_eq!(changes.len(), 2); } + #[test] + fn reload_second_time_no_further_static_changes() { + let initial = test_fixtures::test_dynamic_config(); + let config_arc = Arc::new(ArcSwap::from_pointee(initial.clone())); + let original_static = test_fixtures::test_static_config(); + let handle = ConfigReloadHandle::new(config_arc.clone(), original_static.clone()); + + let mut changed_static = original_static.clone(); + changed_static.health_check_port = 8080; + + let rt = tokio::runtime::Runtime::new().unwrap(); + + let changes1 = rt + .block_on(handle.reload(changed_static.clone(), initial.clone())) + .unwrap(); + assert!(changes1.contains(&"health_check_port".to_string())); + + let changes2 = rt + .block_on(handle.reload(changed_static.clone(), initial.clone())) + .unwrap(); + assert!(changes2.is_empty()); + } + + #[test] + fn reload_second_time_different_static_changes() { + let initial = test_fixtures::test_dynamic_config(); + let config_arc = Arc::new(ArcSwap::from_pointee(initial.clone())); + let original_static = test_fixtures::test_static_config(); + let handle = ConfigReloadHandle::new(config_arc.clone(), original_static.clone()); + + let mut changed_static = original_static.clone(); + changed_static.health_check_port = 8080; + + let rt = tokio::runtime::Runtime::new().unwrap(); + + let changes1 = rt + .block_on(handle.reload(changed_static.clone(), initial.clone())) + .unwrap(); + assert!(changes1.contains(&"health_check_port".to_string())); + assert_eq!(changes1.len(), 1); + + let mut further_changed = changed_static.clone(); + further_changed.shutdown_timeout_secs = 60; + + let changes2 = rt + .block_on(handle.reload(further_changed.clone(), initial.clone())) + .unwrap(); + assert!(!changes2.contains(&"health_check_port".to_string())); + assert!(changes2.contains(&"shutdown_timeout_secs".to_string())); + assert_eq!(changes2.len(), 1); + } + #[test] fn normalize_host_converts_to_lowercase() { assert_eq!(normalize_host("Git.Alk.DEV"), "git.alk.dev");