saluki_config/dynamic/
watcher.rs1use std::future::pending as pending_forever;
4
5use serde::de::DeserializeOwned;
6use tokio::sync::broadcast;
7use tracing::warn;
8
9use crate::dynamic::ConfigChangeEvent;
10
11pub struct FieldUpdateWatcher {
18 pub(crate) key: String,
20 pub(crate) rx: Option<broadcast::Receiver<ConfigChangeEvent>>,
22}
23
24impl FieldUpdateWatcher {
25 pub async fn changed<T>(&mut self) -> (Option<T>, Option<T>)
27 where
28 T: DeserializeOwned,
29 {
30 if self.rx.is_none() {
31 pending_forever::<()>().await;
32 unreachable!();
33 }
34
35 let rx = self.rx.as_mut().unwrap();
36 loop {
37 match rx.recv().await {
38 Ok(event) if event.key == self.key => {
39 let old_ref = event.old_value.as_ref();
40 let new_ref = event.new_value.as_ref();
41
42 let old_t = old_ref.and_then(|ov| serde_json::from_value::<T>(ov.clone()).ok());
43 let new_t = new_ref.and_then(|nv| serde_json::from_value::<T>(nv.clone()).ok());
44
45 if new_t.is_some() || old_t.is_some() {
46 return (old_t, new_t);
47 }
48
49 if let Some(new_ref) = new_ref {
51 warn!(
52 key = %self.key,
53 expected = %std::any::type_name::<T>(),
54 actual = %get_type_name(new_ref),
55 "FieldUpdateWatcher failed to deserialize new value. Skipping update."
56 );
57 }
58 }
59 Ok(_) => continue,
61 Err(broadcast::error::RecvError::Lagged(_)) => {
62 saluki_antithesis::unreachable!(
63 "config filter update dropped (broadcast Lagged); live filtering may stay stale",
64 { "key": self.key.to_string() }
65 );
66 warn!(
67 "FieldUpdateWatcher dropped events for key: {}. Continuing to wait for the next event.",
68 self.key
69 );
70 continue;
71 }
72 Err(broadcast::error::RecvError::Closed) => {
73 pending_forever::<()>().await;
75 unreachable!();
76 }
77 }
78 }
79 }
80}
81
82fn get_type_name(value: &serde_json::Value) -> &'static str {
83 match value {
84 serde_json::Value::Null => "null",
85 serde_json::Value::Bool(_) => "bool",
86 serde_json::Value::Number(_) => "number",
87 serde_json::Value::String(_) => "string",
88 serde_json::Value::Array(_) => "array",
89 serde_json::Value::Object(_) => "object",
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use crate::dynamic::event::ConfigUpdate;
96 use crate::ConfigurationLoader;
97
98 #[tokio::test]
99 async fn test_basic_field_update_watcher() {
100 let (cfg, sender) = ConfigurationLoader::for_tests(
101 Some(serde_json::json!({ "foobar": { "a": false, "b": "c" } })),
102 None,
103 true,
104 )
105 .await;
106 let sender = sender.expect("sender should exist");
107
108 sender
109 .send(ConfigUpdate::Snapshot(serde_json::json!({})))
110 .await
111 .unwrap();
112 cfg.ready().await;
113
114 let mut watcher = cfg.watch_for_updates("watched_key");
115
116 sender
117 .send(ConfigUpdate::Partial {
118 key: "watched_key".to_string(),
119 value: serde_json::json!("hello"),
120 })
121 .await
122 .unwrap();
123
124 let (old, new) = tokio::time::timeout(std::time::Duration::from_secs(2), watcher.changed::<String>())
125 .await
126 .expect("timed out waiting for watched_key update");
127
128 assert_eq!(old, None);
129 assert_eq!(new, Some("hello".to_string()));
130 }
131
132 #[tokio::test]
133 async fn test_field_update_watcher_nested_key() {
134 let (cfg, sender) = ConfigurationLoader::for_tests(
135 Some(serde_json::json!({ "foobar": { "a": false, "b": "c" } })),
136 None,
137 true,
138 )
139 .await;
140 let sender = sender.expect("sender should exist");
141
142 sender
143 .send(ConfigUpdate::Snapshot(serde_json::json!({})))
144 .await
145 .unwrap();
146 cfg.ready().await;
147
148 let mut watcher = cfg.watch_for_updates("foobar.a");
149
150 sender
152 .send(ConfigUpdate::Partial {
153 key: "foobar.a".to_string(),
154 value: serde_json::json!(true),
155 })
156 .await
157 .unwrap();
158
159 let (old, new) = tokio::time::timeout(std::time::Duration::from_secs(2), watcher.changed::<bool>())
160 .await
161 .expect("timed out waiting for foobar.a update");
162
163 assert_eq!(old, Some(false));
164 assert_eq!(new, Some(true));
165 assert!(cfg.get_typed::<bool>("foobar.a").unwrap());
166
167 assert_eq!(cfg.get_typed::<String>("foobar.b").unwrap(), "c");
169 }
170
171 #[tokio::test]
172 async fn test_field_update_watcher_parent_update() {
173 let (cfg, sender) = ConfigurationLoader::for_tests(
174 Some(serde_json::json!({ "foobar": { "a": false, "b": "c" } })),
175 None,
176 true,
177 )
178 .await;
179 let sender = sender.expect("sender should exist");
180
181 sender
182 .send(ConfigUpdate::Snapshot(serde_json::json!({})))
183 .await
184 .unwrap();
185 cfg.ready().await;
186
187 let mut watcher = cfg.watch_for_updates("foobar.a");
188
189 sender
191 .send(ConfigUpdate::Partial {
192 key: "foobar".to_string(),
193 value: serde_json::json!({ "a": true }),
194 })
195 .await
196 .unwrap();
197
198 let (old, new) = tokio::time::timeout(std::time::Duration::from_secs(2), watcher.changed::<bool>())
199 .await
200 .expect("timed out waiting for foobar.a update");
201
202 assert_eq!(old, Some(false));
203 assert_eq!(new, Some(true));
204 assert!(cfg.get_typed::<bool>("foobar.a").unwrap());
205
206 assert_eq!(cfg.get_typed::<String>("foobar.b").unwrap(), "c");
208 }
209}