zng_ext_config/
swap.rs

1use super::*;
2
3use crate::task::parking_lot::Mutex;
4use zng_var::VarHandle;
5
6/// Represents a config source that can swap its backing config source without disconnecting any bound keys.
7///
8/// Note that the [`CONFIG`] service already uses this type internally.
9pub struct SwapConfig {
10    cfg: Mutex<Box<dyn AnyConfig>>,
11    shared: ConfigVars,
12
13    source_status: BoxedVar<ConfigStatus>,
14    status: ArcVar<ConfigStatus>,
15    status_binding: VarHandle,
16}
17impl AnyConfig for SwapConfig {
18    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool, shared: bool) -> BoxedVar<RawConfigValue> {
19        if shared {
20            self.shared
21                .get_or_bind(key, |key| self.cfg.get_mut().get_raw(key.clone(), default, insert, false))
22        } else {
23            self.cfg.get_mut().get_raw(key, default, insert, false)
24        }
25    }
26
27    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool> {
28        self.shared
29            .get_or_bind_contains(key, |key| self.cfg.get_mut().contains_key(key.clone()))
30    }
31
32    fn status(&self) -> BoxedVar<ConfigStatus> {
33        self.status.read_only().boxed()
34    }
35
36    fn remove(&mut self, key: &ConfigKey) -> bool {
37        self.cfg.get_mut().remove(key)
38    }
39
40    fn low_memory(&mut self) {
41        self.cfg.get_mut().low_memory();
42        self.shared.low_memory();
43    }
44}
45impl Config for SwapConfig {
46    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> BoxedVar<T> {
47        self.shared.get_or_bind(key.into(), |key| {
48            // not in shared, bind with source json var.
49            self.cfg.get_mut().get_raw_serde_bidi(key.clone(), default, insert, false)
50        })
51    }
52}
53impl SwapConfig {
54    /// New with [`MemoryConfig`] backend.
55    pub fn new() -> Self {
56        Self {
57            cfg: Mutex::new(Box::<MemoryConfig>::default()),
58            shared: ConfigVars::default(),
59            source_status: LocalVar(ConfigStatus::Loaded).boxed(),
60            status: var(ConfigStatus::Loaded),
61            status_binding: VarHandle::dummy(),
62        }
63    }
64
65    /// Load the config.
66    ///
67    /// The previous source will be dropped and all active config variables are set and rebound to the new config.
68    pub fn load(&mut self, cfg: impl AnyConfig) {
69        self.replace_source(Box::new(cfg))
70    }
71
72    fn replace_source(&mut self, source: Box<dyn AnyConfig>) {
73        self.source_status = source.status();
74        self.status.set_from(&self.source_status);
75        self.status_binding = self.source_status.bind(&self.status);
76
77        *self.cfg.get_mut() = source; // drop previous source first
78
79        self.shared.rebind(&mut **self.cfg.get_mut());
80    }
81}
82impl Default for SwapConfig {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use zng_app::APP;
91    use zng_ext_fs_watcher::FsWatcherManager;
92
93    use super::*;
94
95    #[test]
96    fn swap_config_in_memory() {
97        let mut app = APP
98            .minimal()
99            .extend(FsWatcherManager::default())
100            .extend(ConfigManager::default())
101            .run_headless(false);
102
103        let mut cfg = SwapConfig::new();
104
105        let v = cfg.get("key", true, false);
106        assert!(v.get());
107        v.set(false).unwrap();
108        app.update(false).assert_wait();
109
110        let v2 = cfg.get("key", true, false);
111        assert!(!v2.get() && !v.get());
112        assert_eq!(v.var_ptr(), v2.var_ptr());
113    }
114
115    #[test]
116    fn swap_config_swap() {
117        let mut app = APP
118            .minimal()
119            .extend(FsWatcherManager::default())
120            .extend(ConfigManager::default())
121            .run_headless(false);
122
123        let mut inner1 = MemoryConfig::default();
124        let c1 = inner1.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
125        c1.set(RawConfigValue::serialize(32).unwrap()).unwrap();
126        app.update(false).assert_wait();
127
128        let mut test = SwapConfig::new();
129        test.replace_source(Box::new(inner1));
130
131        let c1 = test.get("key", 0, false);
132
133        assert_eq!(32, c1.get());
134    }
135
136    #[test]
137    fn swap_config_swap_load() {
138        let mut app = APP
139            .minimal()
140            .extend(FsWatcherManager::default())
141            .extend(ConfigManager::default())
142            .run_headless(false);
143
144        let mut inner1 = MemoryConfig::default();
145        let inner_v1 = inner1.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
146        inner_v1.set(RawConfigValue::serialize(32).unwrap()).unwrap();
147        app.update(false).assert_wait();
148
149        let mut test = SwapConfig::new();
150        test.replace_source(Box::new(inner1));
151
152        let cfg = test.get("key", 0, false);
153
154        assert_eq!(32, cfg.get());
155
156        let mut inner2 = MemoryConfig::default();
157        let inner_v2 = inner2.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
158        inner_v2.set(RawConfigValue::serialize(42).unwrap()).unwrap();
159        app.update(false).assert_wait();
160
161        test.replace_source(Box::new(inner2));
162        app.update(false).assert_wait();
163
164        assert_eq!(42, cfg.get());
165    }
166
167    #[test]
168    fn swap_config_swap_load_delayed() {
169        let mut app = APP
170            .minimal()
171            .extend(FsWatcherManager::default())
172            .extend(ConfigManager::default())
173            .run_headless(false);
174
175        let mut inner1 = MemoryConfig::default();
176        let inner_v1 = inner1.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
177        inner_v1.set(RawConfigValue::serialize(32).unwrap()).unwrap();
178        app.update(false).assert_wait();
179
180        let mut test = SwapConfig::new();
181        test.replace_source(Box::new(inner1));
182
183        let cfg = test.get("key", 0, false);
184
185        assert_eq!(32, cfg.get());
186
187        let mut inner2 = MemoryConfig::default();
188        let inner_v2 = inner2.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
189        app.update(false).assert_wait();
190
191        test.replace_source(Box::new(inner2));
192        app.update(false).assert_wait();
193
194        assert_eq!(0, cfg.get());
195
196        inner_v2.set(RawConfigValue::serialize(42).unwrap()).unwrap();
197        app.update(false).assert_wait();
198        assert_eq!(42, cfg.get());
199    }
200
201    #[test]
202    fn swap_config_swap_fallback_delayed() {
203        let mut app = APP
204            .minimal()
205            .extend(FsWatcherManager::default())
206            .extend(ConfigManager::default())
207            .run_headless(false);
208
209        let mut fallback = MemoryConfig::default();
210        fallback
211            .get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true)
212            .set(RawConfigValue::serialize(100).unwrap())
213            .unwrap();
214
215        let mut inner1 = MemoryConfig::default();
216        let inner_v1 = inner1.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
217        inner_v1.set(RawConfigValue::serialize(32).unwrap()).unwrap();
218        app.update(false).assert_wait();
219
220        let mut test = SwapConfig::new();
221        test.replace_source(Box::new(FallbackConfig::new(inner1, fallback)));
222
223        let cfg = test.get("key", -1, false);
224
225        assert_eq!(32, cfg.get());
226
227        let mut fallback = MemoryConfig::default();
228        fallback
229            .get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true)
230            .set(RawConfigValue::serialize(100).unwrap())
231            .unwrap();
232        let mut inner2 = MemoryConfig::default();
233        let inner_v2 = inner2.get_raw("key".into(), RawConfigValue::serialize(0).unwrap(), false, true);
234        app.update(false).assert_wait();
235
236        test.replace_source(Box::new(FallbackConfig::new(inner2, fallback)));
237        app.update(false).assert_wait();
238
239        assert_eq!(0, cfg.get());
240
241        inner_v2.set(RawConfigValue::serialize(42).unwrap()).unwrap();
242        app.update(false).assert_wait();
243        assert_eq!(42, cfg.get());
244    }
245}