1use std::sync::{Arc, OnceLock};
4
5use arc_swap::ArcSwap;
6
7type Hook<T> = Arc<dyn Fn(&Arc<T>, &Arc<T>) + Send + Sync>;
9
10struct Registered<T> {
14 token: u64,
15 hook: Hook<T>,
16}
17
18impl<T> Clone for Registered<T> {
19 fn clone(&self) -> Self {
20 Self {
21 token: self.token,
22 hook: Arc::clone(&self.hook),
23 }
24 }
25}
26
27pub struct ConfigCell<T> {
51 inner: OnceLock<ArcSwap<T>>,
52
53 hooks: OnceLock<ArcSwap<Vec<Registered<T>>>>,
56
57 next_token: std::sync::atomic::AtomicU64,
60
61 #[cfg(feature = "async")]
64 notify: crate::asynchronous::Notify,
65}
66
67impl<T> ConfigCell<T> {
68 #[must_use]
70 pub const fn new() -> Self {
71 Self {
72 inner: OnceLock::new(),
73 hooks: OnceLock::new(),
74 next_token: std::sync::atomic::AtomicU64::new(0),
75 #[cfg(feature = "async")]
76 notify: crate::asynchronous::Notify::new(),
77 }
78 }
79
80 pub fn store(&self, value: T) {
86 let value = Arc::new(value);
87
88 let slot = self.inner.get_or_init(|| ArcSwap::new(Arc::clone(&value)));
92 let previous = slot.swap(Arc::clone(&value));
93
94 #[cfg(feature = "async")]
104 self.notify.bump();
105
106 if !Arc::ptr_eq(&previous, &value) {
107 self.dispatch(&previous, &value);
108 }
109 }
110
111 pub fn on_reload(&self, hook: impl Fn(&Arc<T>, &Arc<T>) + Send + Sync + 'static) {
128 let _ = self.register(Arc::new(hook));
129 }
130
131 #[must_use = "dropping the guard unregisters the hook; bind it for as long \
137 as the hook should fire, or use `on_reload` for a permanent one"]
138 pub fn on_reload_scoped(
139 &'static self,
140 hook: impl Fn(&Arc<T>, &Arc<T>) + Send + Sync + 'static,
141 ) -> HookGuard<T> {
142 HookGuard {
143 cell: self,
144 token: self.register(Arc::new(hook)),
145 }
146 }
147
148 fn register(&self, hook: Hook<T>) -> u64 {
149 let token = self
150 .next_token
151 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
152
153 self.hooks
154 .get_or_init(|| ArcSwap::from_pointee(Vec::new()))
155 .rcu(|current| {
156 let mut next = Vec::with_capacity(current.len() + 1);
157
158 next.extend(current.iter().cloned());
159 next.push(Registered {
160 token,
161 hook: Arc::clone(&hook),
162 });
163
164 next
165 });
166
167 token
168 }
169
170 fn unregister(&self, token: u64) {
171 let Some(hooks) = self.hooks.get() else {
172 return;
173 };
174
175 hooks.rcu(|current| {
176 current
177 .iter()
178 .filter(|registered| registered.token != token)
179 .cloned()
180 .collect::<Vec<_>>()
181 });
182 }
183
184 fn dispatch(&self, previous: &Arc<T>, current: &Arc<T>) {
185 let Some(hooks) = self.hooks.get() else {
186 return;
187 };
188
189 for registered in hooks.load().iter() {
192 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
198 (registered.hook)(previous, current);
199 }));
200
201 if outcome.is_err() {
202 crate::log::warning!(
203 "a reload hook panicked; it stays registered and the \
204 remaining hooks still run"
205 );
206 }
207 }
208 }
209
210 pub fn load(&self) -> Option<Arc<T>> {
212 self.inner.get().map(ArcSwap::load_full)
213 }
214
215 pub fn get_or_panic(&self, type_name: &str) -> Arc<T> {
224 self.load().unwrap_or_else(|| {
225 panic!(
226 "{type_name} has no snapshot installed; configure and install \
227 one first: `{type_name}::builder(\"..\")...init()?`"
228 )
229 })
230 }
231
232 #[cfg(feature = "async")]
240 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
241 pub fn changes(&'static self) -> crate::Changes<T>
242 where
243 T: Send + Sync,
244 {
245 crate::Changes::new(self)
246 }
247
248 #[cfg(feature = "async")]
249 pub(crate) fn notify(&self) -> &crate::asynchronous::Notify {
250 &self.notify
251 }
252}
253
254pub struct HookGuard<T: 'static> {
257 cell: &'static ConfigCell<T>,
258 token: u64,
259}
260
261impl<T> Drop for HookGuard<T> {
262 fn drop(&mut self) {
263 self.cell.unregister(self.token);
264 }
265}
266
267impl<T> std::fmt::Debug for HookGuard<T> {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 f.debug_struct("HookGuard")
270 .field("token", &self.token)
271 .finish_non_exhaustive()
272 }
273}
274
275impl<T> Default for ConfigCell<T> {
276 fn default() -> Self {
277 Self::new()
278 }
279}
280
281impl<T: std::fmt::Debug> std::fmt::Debug for ConfigCell<T> {
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283 match self.load() {
284 Some(value) => f.debug_tuple("ConfigCell").field(&value).finish(),
285 None => f.write_str("ConfigCell(uninitialized)"),
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use std::sync::Mutex;
294 use std::thread;
295
296 #[test]
297 fn a_fresh_cell_is_empty() {
298 let cell = ConfigCell::<u16>::new();
299
300 assert!(cell.load().is_none());
301 }
302
303 #[test]
304 fn a_reader_keeps_the_generation_it_took() {
305 let cell = ConfigCell::new();
306 cell.store(String::from("first"));
307
308 let held = cell.load().unwrap();
309 cell.store(String::from("second"));
310
311 assert_eq!(*held, "first");
312 assert_eq!(*cell.load().unwrap(), "second");
313 }
314
315 #[test]
316 fn concurrent_first_writes_do_not_lose_the_cell() {
317 let cell: &'static ConfigCell<usize> = Box::leak(Box::new(ConfigCell::new()));
318
319 let writers: Vec<_> = (0..8)
320 .map(|value| thread::spawn(move || cell.store(value)))
321 .collect();
322
323 for writer in writers {
324 writer.join().unwrap();
325 }
326
327 let final_value = *cell.load().expect("some writer must have won");
328 assert!(final_value < 8);
329 }
330
331 #[test]
332 fn the_first_store_is_an_initialization_not_a_reload() {
333 let seen = Arc::new(Mutex::new(Vec::new()));
334 let cell = ConfigCell::new();
335
336 let recorder = Arc::clone(&seen);
337 cell.on_reload(move |previous, current| {
338 recorder.lock().unwrap().push((**previous, **current));
339 });
340
341 cell.store(1u16);
342 assert!(
343 seen.lock().unwrap().is_empty(),
344 "there is nothing to compare the first snapshot against"
345 );
346
347 cell.store(2u16);
348 cell.store(3u16);
349
350 assert_eq!(*seen.lock().unwrap(), [(1, 2), (2, 3)]);
351 }
352
353 #[test]
354 fn every_registered_callback_runs() {
355 let count = Arc::new(Mutex::new(0usize));
356 let cell = ConfigCell::new();
357
358 for _ in 0..3 {
359 let counter = Arc::clone(&count);
360 cell.on_reload(move |_, _| *counter.lock().unwrap() += 1);
361 }
362
363 cell.store(1u16);
364 cell.store(2u16);
365
366 assert_eq!(*count.lock().unwrap(), 3);
367 }
368
369 #[test]
370 fn a_panicking_hook_silences_neither_the_rest_nor_the_next_reload() {
371 let count = Arc::new(Mutex::new(0usize));
372 let cell = ConfigCell::new();
373
374 cell.on_reload(|_, _| panic!("a bug in somebody's hook"));
375 {
376 let counter = Arc::clone(&count);
377 cell.on_reload(move |_, _| *counter.lock().unwrap() += 1);
378 }
379
380 cell.store(1u16);
381 cell.store(2u16);
382 cell.store(3u16);
383
384 assert_eq!(
385 *count.lock().unwrap(),
386 2,
387 "the hook after the panicking one must run on every reload"
388 );
389 }
390
391 #[test]
392 fn dropping_the_guard_unregisters_the_hook() {
393 let count = Arc::new(Mutex::new(0usize));
394 let cell: &'static ConfigCell<u16> = Box::leak(Box::new(ConfigCell::new()));
395
396 cell.store(1);
397
398 let guard = {
399 let counter = Arc::clone(&count);
400 cell.on_reload_scoped(move |_, _| *counter.lock().unwrap() += 1)
401 };
402
403 cell.store(2);
404 assert_eq!(*count.lock().unwrap(), 1);
405
406 drop(guard);
407 cell.store(3);
408 assert_eq!(
409 *count.lock().unwrap(),
410 1,
411 "an unregistered hook must not fire"
412 );
413 }
414
415 #[test]
416 #[should_panic(expected = "`DbConfig::builder(")]
417 fn get_or_panic_points_at_the_builder() {
418 ConfigCell::<u16>::new().get_or_panic("DbConfig");
419 }
420}