1use std::sync::{Arc, OnceLock};
4
5use arc_swap::ArcSwap;
6
7type Hook<T> = Arc<dyn Fn(&Arc<T>, &Arc<T>) + Send + Sync>;
9
10pub struct ConfigCell<T> {
34 inner: OnceLock<ArcSwap<T>>,
35
36 hooks: OnceLock<ArcSwap<Vec<Hook<T>>>>,
39
40 #[cfg(feature = "async")]
43 notify: crate::asynchronous::Notify,
44}
45
46impl<T> ConfigCell<T> {
47 #[must_use]
49 pub const fn new() -> Self {
50 Self {
51 inner: OnceLock::new(),
52 hooks: OnceLock::new(),
53 #[cfg(feature = "async")]
54 notify: crate::asynchronous::Notify::new(),
55 }
56 }
57
58 pub fn store(&self, value: T) {
64 let value = Arc::new(value);
65
66 let slot = self.inner.get_or_init(|| ArcSwap::new(Arc::clone(&value)));
70 let previous = slot.swap(Arc::clone(&value));
71
72 if !Arc::ptr_eq(&previous, &value) {
78 self.dispatch(&previous, &value);
79 }
80
81 #[cfg(feature = "async")]
82 self.notify.bump();
83 }
84
85 pub fn on_reload(&self, hook: impl Fn(&Arc<T>, &Arc<T>) + Send + Sync + 'static) {
96 let hook: Hook<T> = Arc::new(hook);
97
98 self.hooks
99 .get_or_init(|| ArcSwap::from_pointee(Vec::new()))
100 .rcu(|current| {
101 let mut next = Vec::with_capacity(current.len() + 1);
102
103 next.extend(current.iter().map(Arc::clone));
104 next.push(Arc::clone(&hook));
105
106 next
107 });
108 }
109
110 fn dispatch(&self, previous: &Arc<T>, current: &Arc<T>) {
111 let Some(hooks) = self.hooks.get() else {
112 return;
113 };
114
115 for hook in hooks.load().iter() {
118 hook(previous, current);
119 }
120 }
121
122 pub fn load(&self) -> Option<Arc<T>> {
124 self.inner.get().map(ArcSwap::load_full)
125 }
126
127 pub fn get_or_panic(&self, type_name: &str) -> Arc<T> {
136 self.load().unwrap_or_else(|| {
137 panic!("{type_name} has not been initialized; call `{type_name}::init()` first")
138 })
139 }
140
141 #[cfg(feature = "async")]
149 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
150 pub fn changes(&'static self) -> crate::Changes<T>
151 where
152 T: Send + Sync,
153 {
154 crate::Changes::new(self)
155 }
156
157 #[cfg(feature = "async")]
158 pub(crate) fn notify(&self) -> &crate::asynchronous::Notify {
159 &self.notify
160 }
161}
162
163impl<T> Default for ConfigCell<T> {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169impl<T: std::fmt::Debug> std::fmt::Debug for ConfigCell<T> {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 match self.load() {
172 Some(value) => f.debug_tuple("ConfigCell").field(&value).finish(),
173 None => f.write_str("ConfigCell(uninitialized)"),
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use std::sync::Mutex;
182 use std::thread;
183
184 #[test]
185 fn a_fresh_cell_is_empty() {
186 let cell = ConfigCell::<u16>::new();
187
188 assert!(cell.load().is_none());
189 }
190
191 #[test]
192 fn a_reader_keeps_the_generation_it_took() {
193 let cell = ConfigCell::new();
194 cell.store(String::from("first"));
195
196 let held = cell.load().unwrap();
197 cell.store(String::from("second"));
198
199 assert_eq!(*held, "first");
200 assert_eq!(*cell.load().unwrap(), "second");
201 }
202
203 #[test]
204 fn concurrent_first_writes_do_not_lose_the_cell() {
205 let cell: &'static ConfigCell<usize> = Box::leak(Box::new(ConfigCell::new()));
206
207 let writers: Vec<_> = (0..8)
208 .map(|value| thread::spawn(move || cell.store(value)))
209 .collect();
210
211 for writer in writers {
212 writer.join().unwrap();
213 }
214
215 let final_value = *cell.load().expect("some writer must have won");
216 assert!(final_value < 8);
217 }
218
219 #[test]
220 fn the_first_store_is_an_initialization_not_a_reload() {
221 let seen = Arc::new(Mutex::new(Vec::new()));
222 let cell = ConfigCell::new();
223
224 let recorder = Arc::clone(&seen);
225 cell.on_reload(move |previous, current| {
226 recorder.lock().unwrap().push((**previous, **current));
227 });
228
229 cell.store(1u16);
230 assert!(
231 seen.lock().unwrap().is_empty(),
232 "there is nothing to compare the first snapshot against"
233 );
234
235 cell.store(2u16);
236 cell.store(3u16);
237
238 assert_eq!(*seen.lock().unwrap(), [(1, 2), (2, 3)]);
239 }
240
241 #[test]
242 fn every_registered_callback_runs() {
243 let count = Arc::new(Mutex::new(0usize));
244 let cell = ConfigCell::new();
245
246 for _ in 0..3 {
247 let counter = Arc::clone(&count);
248 cell.on_reload(move |_, _| *counter.lock().unwrap() += 1);
249 }
250
251 cell.store(1u16);
252 cell.store(2u16);
253
254 assert_eq!(*count.lock().unwrap(), 3);
255 }
256
257 #[test]
258 #[should_panic(expected = "`DbConfig::init()`")]
259 fn get_or_panic_points_at_init() {
260 ConfigCell::<u16>::new().get_or_panic("DbConfig");
261 }
262}