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 #[cfg(not(loom))]
71 pub const fn new() -> Self {
72 Self {
73 inner: OnceLock::new(),
74 hooks: OnceLock::new(),
75 next_token: std::sync::atomic::AtomicU64::new(0),
76 #[cfg(feature = "async")]
77 notify: crate::asynchronous::Notify::new(),
78 }
79 }
80
81 #[must_use]
83 #[cfg(loom)]
84 pub fn new() -> Self {
85 Self {
86 inner: OnceLock::new(),
87 hooks: OnceLock::new(),
88 next_token: std::sync::atomic::AtomicU64::new(0),
89 #[cfg(feature = "async")]
90 notify: crate::asynchronous::Notify::new(),
91 }
92 }
93
94 pub fn store(&self, value: T) {
100 let value = Arc::new(value);
101
102 let slot = self.inner.get_or_init(|| ArcSwap::new(Arc::clone(&value)));
106 let previous = slot.swap(Arc::clone(&value));
107
108 #[cfg(feature = "async")]
118 self.notify.bump();
119
120 if !Arc::ptr_eq(&previous, &value) {
121 self.dispatch(&previous, &value);
122 }
123 }
124
125 pub fn on_reload(&self, hook: impl Fn(&Arc<T>, &Arc<T>) + Send + Sync + 'static) {
142 let _ = self.register(Arc::new(hook));
143 }
144
145 #[must_use = "dropping the guard unregisters the hook; bind it for as long \
151 as the hook should fire, or use `on_reload` for a permanent one"]
152 pub fn on_reload_scoped(
153 &'static self,
154 hook: impl Fn(&Arc<T>, &Arc<T>) + Send + Sync + 'static,
155 ) -> HookGuard<T> {
156 HookGuard {
157 token: self.register(Arc::new(hook)),
158 cell: GuardCell::Static(self),
159 }
160 }
161
162 pub(crate) fn on_reload_scoped_shared(
167 cell: &Arc<Self>,
168 hook: impl Fn(&Arc<T>, &Arc<T>) + Send + Sync + 'static,
169 ) -> HookGuard<T> {
170 HookGuard {
171 token: cell.register(Arc::new(hook)),
172 cell: GuardCell::Shared(Arc::clone(cell)),
173 }
174 }
175
176 fn register(&self, hook: Hook<T>) -> u64 {
177 let token = self
178 .next_token
179 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
180
181 self.hooks
182 .get_or_init(|| ArcSwap::from_pointee(Vec::new()))
183 .rcu(|current| {
184 let mut next = Vec::with_capacity(current.len() + 1);
185
186 next.extend(current.iter().cloned());
187 next.push(Registered {
188 token,
189 hook: Arc::clone(&hook),
190 });
191
192 next
193 });
194
195 token
196 }
197
198 fn unregister(&self, token: u64) {
199 let Some(hooks) = self.hooks.get() else {
200 return;
201 };
202
203 hooks.rcu(|current| {
204 current
205 .iter()
206 .filter(|registered| registered.token != token)
207 .cloned()
208 .collect::<Vec<_>>()
209 });
210 }
211
212 fn dispatch(&self, previous: &Arc<T>, current: &Arc<T>) {
213 let Some(hooks) = self.hooks.get() else {
214 return;
215 };
216
217 for registered in hooks.load().iter() {
220 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
226 (registered.hook)(previous, current);
227 }));
228
229 if outcome.is_err() {
230 crate::log::warning!(
231 "a reload hook panicked; it stays registered and the \
232 remaining hooks still run"
233 );
234 }
235 }
236 }
237
238 pub fn load(&self) -> Option<Arc<T>> {
240 self.inner.get().map(ArcSwap::load_full)
241 }
242
243 pub fn get_or_panic(&self, type_name: &str) -> Arc<T> {
252 self.load().unwrap_or_else(|| {
253 panic!(
254 "{type_name} has no snapshot installed; configure and install \
255 one first: `{type_name}::builder(\"..\")...init()?`"
256 )
257 })
258 }
259
260 #[cfg(feature = "async")]
268 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
269 pub fn changes(&'static self) -> crate::Changes<T>
270 where
271 T: Send + Sync,
272 {
273 crate::Changes::new(self)
274 }
275
276 #[cfg(feature = "async")]
277 pub(crate) fn notify(&self) -> &crate::asynchronous::Notify {
278 &self.notify
279 }
280}
281
282pub struct HookGuard<T: 'static> {
285 cell: GuardCell<T>,
286 token: u64,
287}
288
289enum GuardCell<T: 'static> {
292 Static(&'static ConfigCell<T>),
293 Shared(Arc<ConfigCell<T>>),
294}
295
296impl<T> Drop for HookGuard<T> {
297 fn drop(&mut self) {
298 match &self.cell {
299 GuardCell::Static(cell) => cell.unregister(self.token),
300 GuardCell::Shared(cell) => cell.unregister(self.token),
301 }
302 }
303}
304
305impl<T> std::fmt::Debug for HookGuard<T> {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 f.debug_struct("HookGuard")
308 .field("token", &self.token)
309 .finish_non_exhaustive()
310 }
311}
312
313impl<T> Default for ConfigCell<T> {
314 fn default() -> Self {
315 Self::new()
316 }
317}
318
319impl<T: std::fmt::Debug> std::fmt::Debug for ConfigCell<T> {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 match self.load() {
322 Some(value) => f.debug_tuple("ConfigCell").field(&value).finish(),
323 None => f.write_str("ConfigCell(uninitialized)"),
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use std::sync::Mutex;
332 use std::thread;
333
334 #[test]
335 fn a_fresh_cell_is_empty() {
336 let cell = ConfigCell::<u16>::new();
337
338 assert!(cell.load().is_none());
339 }
340
341 #[test]
342 fn a_reader_keeps_the_generation_it_took() {
343 let cell = ConfigCell::new();
344 cell.store(String::from("first"));
345
346 let held = cell.load().unwrap();
347 cell.store(String::from("second"));
348
349 assert_eq!(*held, "first");
350 assert_eq!(*cell.load().unwrap(), "second");
351 }
352
353 #[test]
354 fn concurrent_first_writes_do_not_lose_the_cell() {
355 let cell: &'static ConfigCell<usize> = Box::leak(Box::new(ConfigCell::new()));
356
357 let writers: Vec<_> = (0..8)
358 .map(|value| thread::spawn(move || cell.store(value)))
359 .collect();
360
361 for writer in writers {
362 writer.join().unwrap();
363 }
364
365 let final_value = *cell.load().expect("some writer must have won");
366 assert!(final_value < 8);
367 }
368
369 #[test]
370 fn the_first_store_is_an_initialization_not_a_reload() {
371 let seen = Arc::new(Mutex::new(Vec::new()));
372 let cell = ConfigCell::new();
373
374 let recorder = Arc::clone(&seen);
375 cell.on_reload(move |previous, current| {
376 recorder.lock().unwrap().push((**previous, **current));
377 });
378
379 cell.store(1u16);
380 assert!(
381 seen.lock().unwrap().is_empty(),
382 "there is nothing to compare the first snapshot against"
383 );
384
385 cell.store(2u16);
386 cell.store(3u16);
387
388 assert_eq!(*seen.lock().unwrap(), [(1, 2), (2, 3)]);
389 }
390
391 #[test]
392 fn every_registered_callback_runs() {
393 let count = Arc::new(Mutex::new(0usize));
394 let cell = ConfigCell::new();
395
396 for _ in 0..3 {
397 let counter = Arc::clone(&count);
398 cell.on_reload(move |_, _| *counter.lock().unwrap() += 1);
399 }
400
401 cell.store(1u16);
402 cell.store(2u16);
403
404 assert_eq!(*count.lock().unwrap(), 3);
405 }
406
407 #[test]
408 fn a_panicking_hook_silences_neither_the_rest_nor_the_next_reload() {
409 let count = Arc::new(Mutex::new(0usize));
410 let cell = ConfigCell::new();
411
412 cell.on_reload(|_, _| panic!("a bug in somebody's hook"));
413 {
414 let counter = Arc::clone(&count);
415 cell.on_reload(move |_, _| *counter.lock().unwrap() += 1);
416 }
417
418 cell.store(1u16);
419 cell.store(2u16);
420 cell.store(3u16);
421
422 assert_eq!(
423 *count.lock().unwrap(),
424 2,
425 "the hook after the panicking one must run on every reload"
426 );
427 }
428
429 #[test]
430 fn dropping_the_guard_unregisters_the_hook() {
431 let count = Arc::new(Mutex::new(0usize));
432 let cell: &'static ConfigCell<u16> = Box::leak(Box::new(ConfigCell::new()));
433
434 cell.store(1);
435
436 let guard = {
437 let counter = Arc::clone(&count);
438 cell.on_reload_scoped(move |_, _| *counter.lock().unwrap() += 1)
439 };
440
441 cell.store(2);
442 assert_eq!(*count.lock().unwrap(), 1);
443
444 drop(guard);
445 cell.store(3);
446 assert_eq!(
447 *count.lock().unwrap(),
448 1,
449 "an unregistered hook must not fire"
450 );
451 }
452
453 #[test]
454 #[should_panic(expected = "`DbConfig::builder(")]
455 fn get_or_panic_points_at_the_builder() {
456 ConfigCell::<u16>::new().get_or_panic("DbConfig");
457 }
458}