Skip to main content

dynamic_config_web_core/
lib.rs

1//! One reading of each configuration section, taken when a request begins.
2//!
3//! `Config::current()` is an atomic load, and its own documentation says to
4//! call it once per request: a reload landing between two calls lets one
5//! request observe two configurations. With one section that is easy to
6//! honour. With two it is not, because "the same generation" is a property
7//! of a pair of reads that no single call site can see.
8//!
9//! This crate is the pair. A [`Sections`] list is read once into a
10//! [`Snapshot`], the framework adapter puts that snapshot where the request
11//! can reach it, and every handler read comes back out of it.
12//!
13//! ```
14//! use std::sync::Arc;
15//! use dynamic_config_web_core::Sections;
16//!
17//! #[derive(Debug, PartialEq)]
18//! struct Server { port: u16 }
19//! #[derive(Debug, PartialEq)]
20//! struct Features { cache: bool }
21//!
22//! # let server = Arc::new(Server { port: 8080 });
23//! # let features = Arc::new(Features { cache: true });
24//! let sections = Sections::new()
25//!     .section({ let it = Arc::clone(&server); move || Some(Arc::clone(&it)) })
26//!     .section({ let it = Arc::clone(&features); move || Some(Arc::clone(&it)) });
27//!
28//! let snapshot = sections.take();
29//!
30//! assert_eq!(snapshot.get::<Server>().unwrap().port, 8080);
31//! assert!(snapshot.get::<Features>().unwrap().cache);
32//! ```
33//!
34//! In a service the closures are `|| ServerConfig::try_current()`, which the
35//! [`sections!`] macro writes for you.
36//!
37//! # How one reading stays one reading
38//!
39//! Each configuration has its own atomic cell and the engine keeps no
40//! epoch across them, so reading N sections is N independent loads — and a
41//! reload landing between two of them would put two generations in one
42//! snapshot, which is the bug this crate exists to prevent.
43//!
44//! [`Sections::take`] therefore reads the install counters, reads the
45//! sections, and reads the counters again; if anything moved it starts
46//! over. A section registered through [`Sections::section`] supplies no
47//! counter, and a list containing one reads without the check —
48//! [`Sections::is_consistent`] says which kind you have, and the
49//! [`sections!`] macro always produces the checked kind.
50//!
51//! # Why closures rather than a trait
52//!
53//! `#[dynamic_config]` writes `try_current()` as an inherent method, so a
54//! generic function cannot call it. A closure can, and the same shape covers
55//! a `Dynamic<T>` instance — `move || handle.current()` — which a trait
56//! implemented on the type could not reach.
57
58#![forbid(unsafe_code)]
59#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
60#![cfg_attr(docsrs, feature(doc_cfg))]
61
62use std::any::{Any, TypeId};
63use std::collections::HashMap;
64use std::fmt;
65use std::sync::Arc;
66
67/// What one request may read: one `Arc` per section, taken together.
68///
69/// Built by [`Sections::take`] when the request begins, and read by an
70/// adapter's extractor. Two reads of the same section answer the same
71/// `Arc`, however far apart in the handler they are and whatever lands in
72/// between.
73#[derive(Clone, Default)]
74pub struct Snapshot {
75    sections: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
76    /// Every type that was *registered*, whether or not it had loaded.
77    ///
78    /// Keyed by `TypeId` rather than by name because that is the identity
79    /// that cannot collide: `type_name` is a diagnostic string the language
80    /// makes no uniqueness promise about. The name rides along because a
81    /// `TypeId` cannot be turned back into one, and an error that could
82    /// only say "some type" would not be worth printing.
83    registered: Vec<(TypeId, &'static str)>,
84}
85
86impl Snapshot {
87    /// The section of type `T` this request began with.
88    ///
89    /// `None` when `T` was not among the sections, or was among them and
90    /// had not loaded yet. [`require`](Self::require) tells those apart.
91    #[must_use]
92    pub fn get<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
93        self.sections
94            .get(&TypeId::of::<T>())
95            .cloned()
96            .and_then(|section| section.downcast::<T>().ok())
97    }
98
99    /// The section of type `T`, or why it is not here.
100    ///
101    /// # Errors
102    ///
103    /// [`NotInScope::NotListed`] when `T` was never registered — a wiring
104    /// mistake, fixed where the layer is built. [`NotInScope::NotLoaded`]
105    /// when it was registered and nothing has installed a value yet — a
106    /// startup-order mistake, fixed by loading before serving. The two have
107    /// different fixes, which is why they are different variants.
108    pub fn require<T: Any + Send + Sync>(&self) -> Result<Arc<T>, NotInScope> {
109        match self.get::<T>() {
110            Some(section) => Ok(section),
111            None => {
112                let id = TypeId::of::<T>();
113                let name = std::any::type_name::<T>();
114
115                if self.registered.iter().any(|(known, _)| *known == id) {
116                    Err(NotInScope::NotLoaded(name))
117                } else {
118                    Err(NotInScope::NotListed(name))
119                }
120            }
121        }
122    }
123
124    /// How many sections this request may read.
125    #[must_use]
126    pub fn len(&self) -> usize {
127        self.sections.len()
128    }
129
130    /// Whether it carries nothing at all.
131    #[must_use]
132    pub fn is_empty(&self) -> bool {
133        self.sections.is_empty()
134    }
135
136    /// The type names registered, whether or not each had loaded.
137    #[must_use]
138    pub fn names(&self) -> Vec<&'static str> {
139        self.registered.iter().map(|(_, name)| *name).collect()
140    }
141
142    /// This snapshot with `inner`'s sections laid over it.
143    ///
144    /// What an adapter uses when layers nest and each carries its own
145    /// list. The inner one wins where both registered a type, because it
146    /// is the more specific of the two — and nothing is lost, which is the
147    /// point: a handler under both layers can read either's sections.
148    #[must_use]
149    pub fn merged_with(mut self, inner: Self) -> Self {
150        for (id, name) in inner.registered {
151            if !self.registered.iter().any(|(known, _)| *known == id) {
152                self.registered.push((id, name));
153            }
154        }
155
156        self.sections.extend(inner.sections);
157        self
158    }
159}
160
161impl fmt::Debug for Snapshot {
162    /// The section names, never their contents.
163    ///
164    /// A configuration section holds credentials, and `{:?}` on a request
165    /// is the kind of thing that reaches a log line.
166    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
167        formatter
168            .debug_struct("Snapshot")
169            .field("sections", &self.names())
170            .finish()
171    }
172}
173
174/// Why a section is not in this request's snapshot.
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum NotInScope {
177    /// The type was never registered on the layer.
178    NotListed(&'static str),
179    /// It was registered, and nothing had installed a value when the
180    /// request began.
181    NotLoaded(&'static str),
182}
183
184impl NotInScope {
185    /// The type name this is about.
186    #[must_use]
187    pub fn type_name(&self) -> &'static str {
188        match self {
189            Self::NotListed(name) | Self::NotLoaded(name) => name,
190        }
191    }
192}
193
194impl fmt::Display for NotInScope {
195    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self {
197            Self::NotListed(name) => write!(
198                formatter,
199                "`{name}` is not one of this request's sections; add it where the \
200                 layer is built, with `sections![.., {name}]`"
201            ),
202            Self::NotLoaded(name) => write!(
203                formatter,
204                "`{name}` is one of this request's sections but nothing had loaded \
205                 it when the request began; call `init()` before serving"
206            ),
207        }
208    }
209}
210
211impl std::error::Error for NotInScope {}
212
213/// The sections a request reads, and how to read each one.
214///
215/// Built once, at startup, and handed to the framework adapter. Each entry
216/// is a closure the adapter calls when a request begins — never during it,
217/// which is what makes the reads agree.
218#[derive(Default)]
219pub struct Sections {
220    readers: Vec<Registered>,
221}
222
223struct Registered {
224    id: TypeId,
225    name: &'static str,
226    read: Reader,
227    /// This section's install counter, when the caller could supply one.
228    ///
229    /// Each configuration has its own atomic cell, and the engine has no
230    /// epoch across them — so reading N sections is N independent loads,
231    /// and a reload landing between two of them would put two generations
232    /// in one snapshot. Comparing this counter before and after the read
233    /// is what detects that; see [`Sections::take`].
234    generation: Option<Generation>,
235}
236
237type Reader = Box<dyn Fn() -> Option<Arc<dyn Any + Send + Sync>> + Send + Sync>;
238type Generation = Box<dyn Fn() -> u64 + Send + Sync>;
239
240/// How many times `take` re-reads before giving up on a quiet moment.
241///
242/// A reload is rare and a read is microseconds, so one retry is almost
243/// always enough. The bound exists so that a pathological reload loop
244/// cannot stall a request: after it, the last read is served, which is no
245/// worse than having no check at all.
246const ATTEMPTS: usize = 8;
247
248impl Sections {
249    /// An empty list.
250    #[must_use]
251    pub fn new() -> Self {
252        Self::default()
253    }
254
255    /// Adds one section, read by `read`.
256    ///
257    /// ```
258    /// # use std::sync::Arc;
259    /// # use dynamic_config_web_core::Sections;
260    /// # struct Database;
261    /// # fn try_current() -> Option<Arc<Database>> { None }
262    /// let sections = Sections::new().section(try_current);
263    /// ```
264    ///
265    /// In a service `read` is `|| Database::try_current()`, or
266    /// `move || handle.current()` for a `Dynamic<T>`.
267    ///
268    /// Registering the same type twice keeps the last reader, so a
269    /// composed list can override one entry without rebuilding it.
270    #[must_use]
271    pub fn section<T, F>(self, read: F) -> Self
272    where
273        T: Any + Send + Sync,
274        F: Fn() -> Option<Arc<T>> + Send + Sync + 'static,
275    {
276        self.push::<T>(read, None)
277    }
278
279    /// Adds one section, with the install counter that says when it moved.
280    ///
281    /// What [`sections!`] uses. The counter lets [`take`](Self::take) tell
282    /// a snapshot that straddled a reload from one that did not; a section
283    /// registered through [`section`](Self::section) has none, and a list
284    /// containing one cannot make that check.
285    #[must_use]
286    pub fn section_with_generation<T, F, G>(self, read: F, generation: G) -> Self
287    where
288        T: Any + Send + Sync,
289        F: Fn() -> Option<Arc<T>> + Send + Sync + 'static,
290        G: Fn() -> u64 + Send + Sync + 'static,
291    {
292        self.push::<T>(read, Some(Box::new(generation)))
293    }
294
295    fn push<T>(
296        mut self,
297        read: impl Fn() -> Option<Arc<T>> + Send + Sync + 'static,
298        generation: Option<Generation>,
299    ) -> Self
300    where
301        T: Any + Send + Sync,
302    {
303        let id = TypeId::of::<T>();
304        self.readers.retain(|existing| existing.id != id);
305        self.readers.push(Registered {
306            id,
307            name: std::any::type_name::<T>(),
308            read: Box::new(move || read().map(|section| section as Arc<dyn Any + Send + Sync>)),
309            generation,
310        });
311
312        self
313    }
314
315    /// Whether every section can say when it last moved.
316    ///
317    /// `false` when any was registered through [`section`](Self::section),
318    /// which takes a reader and nothing else — [`take`](Self::take) then
319    /// reads once without checking.
320    #[must_use]
321    pub fn is_consistent(&self) -> bool {
322        self.readers
323            .iter()
324            .all(|section| section.generation.is_some())
325    }
326
327    /// Reads every section once.
328    ///
329    /// Called by the adapter when a request begins. A section that has not
330    /// loaded is left out rather than failing the request: the handler that
331    /// asks for it gets [`NotInScope::NotLoaded`], and a handler that does
332    /// not ask is unaffected.
333    #[must_use]
334    pub fn take(&self) -> Snapshot {
335        // One section cannot straddle anything, and a list that cannot
336        // report its generations has nothing to compare.
337        if self.readers.len() < 2 || !self.is_consistent() {
338            return self.read_once();
339        }
340
341        // Each configuration has its own atomic cell and the engine has no
342        // epoch across them, so reading N sections is N independent loads.
343        // Read the counters, read the sections, read the counters again: if
344        // nothing moved, nothing could have landed in between, and the
345        // snapshot is one generation throughout.
346        for _ in 0..ATTEMPTS {
347            let before = self.generations();
348            let snapshot = self.read_once();
349
350            if self.generations() == before {
351                return snapshot;
352            }
353        }
354
355        // Reloading faster than a read completes, eight times running. The
356        // last read is served: no worse than not checking, which is what
357        // every caller had before.
358        self.read_once()
359    }
360
361    /// One pass over the readers, with no consistency check.
362    fn read_once(&self) -> Snapshot {
363        let mut sections = HashMap::with_capacity(self.readers.len());
364        let mut registered = Vec::with_capacity(self.readers.len());
365
366        for section in &self.readers {
367            registered.push((section.id, section.name));
368
369            if let Some(value) = (section.read)() {
370                sections.insert(section.id, value);
371            }
372        }
373
374        Snapshot {
375            sections,
376            registered,
377        }
378    }
379
380    /// Every section's install counter, in registration order.
381    fn generations(&self) -> Vec<u64> {
382        self.readers
383            .iter()
384            .map(|section| section.generation.as_ref().map_or(0, |read| read()))
385            .collect()
386    }
387
388    /// How many sections are registered.
389    #[must_use]
390    pub fn len(&self) -> usize {
391        self.readers.len()
392    }
393
394    /// Whether nothing is registered.
395    #[must_use]
396    pub fn is_empty(&self) -> bool {
397        self.readers.is_empty()
398    }
399
400    /// The registered type names, in order.
401    #[must_use]
402    pub fn names(&self) -> Vec<&'static str> {
403        self.readers.iter().map(|section| section.name).collect()
404    }
405}
406
407impl fmt::Debug for Sections {
408    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
409        formatter
410            .debug_struct("Sections")
411            .field("sections", &self.names())
412            .finish()
413    }
414}
415
416/// The sections a request reads, by type.
417///
418/// ```ignore
419/// let app = Router::new()
420///     .route("/", get(handler))
421///     .layer(SnapshotLayer::new(sections![ServerConfig, FeaturesConfig]));
422/// ```
423///
424/// Each name expands to `|| Type::try_current()`, which is what
425/// `#[dynamic_config]` generates. For a `Dynamic<T>` instance, call
426/// [`Sections::section`] with a closure instead.
427#[macro_export]
428macro_rules! sections {
429    () => {
430        $crate::Sections::new()
431    };
432    ($($section:ty),+ $(,)?) => {{
433        $crate::Sections::new()
434            $(.section_with_generation(
435                || <$section>::try_current(),
436                || <$section>::generation(),
437            ))+
438    }};
439}
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444
445    #[derive(Debug, PartialEq)]
446    struct Server {
447        port: u16,
448    }
449
450    #[derive(Debug, PartialEq)]
451    struct Features {
452        cache: bool,
453    }
454
455    #[derive(Debug)]
456    struct NeverLoaded;
457
458    fn server(port: u16) -> impl Fn() -> Option<Arc<Server>> + Send + Sync {
459        move || Some(Arc::new(Server { port }))
460    }
461
462    #[test]
463    fn a_snapshot_answers_every_section_it_took() {
464        let snapshot = Sections::new()
465            .section(server(8080))
466            .section(|| Some(Arc::new(Features { cache: true })))
467            .take();
468
469        assert_eq!(snapshot.len(), 2);
470        assert_eq!(snapshot.get::<Server>().unwrap().port, 8080);
471        assert!(snapshot.get::<Features>().unwrap().cache);
472    }
473
474    #[test]
475    fn two_reads_of_one_snapshot_are_the_same_arc() {
476        // The property the whole crate exists for: whatever happens between
477        // two reads, they answer the same value.
478        let snapshot = Sections::new().section(server(8080)).take();
479
480        let first = snapshot.get::<Server>().unwrap();
481        let second = snapshot.get::<Server>().unwrap();
482
483        assert!(Arc::ptr_eq(&first, &second));
484    }
485
486    #[test]
487    fn a_snapshot_does_not_move_when_the_source_does() {
488        use std::sync::atomic::{AtomicU16, Ordering};
489
490        static PORT: AtomicU16 = AtomicU16::new(8080);
491
492        let sections = Sections::new().section(|| {
493            Some(Arc::new(Server {
494                port: PORT.load(Ordering::Relaxed),
495            }))
496        });
497
498        let taken = sections.take();
499
500        // A reload lands between the two reads.
501        PORT.store(9090, Ordering::Relaxed);
502
503        assert_eq!(taken.get::<Server>().unwrap().port, 8080);
504        // And the next request sees it, because a scope is not a cache.
505        assert_eq!(sections.take().get::<Server>().unwrap().port, 9090);
506    }
507
508    #[test]
509    fn a_section_that_never_loaded_is_named_as_such() {
510        let snapshot = Sections::new()
511            .section(server(1))
512            .section(|| None::<Arc<NeverLoaded>>)
513            .take();
514
515        assert_eq!(snapshot.len(), 1, "the unloaded one is not in the map");
516        assert_eq!(snapshot.names().len(), 2, "but it is still registered");
517
518        match snapshot.require::<NeverLoaded>() {
519            Err(NotInScope::NotLoaded(name)) => assert!(name.ends_with("NeverLoaded")),
520            other => panic!("expected NotLoaded, got {other:?}"),
521        }
522    }
523
524    #[test]
525    fn two_types_with_the_same_name_are_told_apart() {
526        // `type_name` carries no uniqueness promise, so the registered set
527        // is keyed by `TypeId`. Two `Server`s in different modules are two
528        // sections, and asking for the unregistered one says so.
529        mod other {
530            #[derive(Debug)]
531            pub struct Server;
532        }
533
534        let snapshot = Sections::new()
535            .section(server(8080))
536            .section(|| Some(Arc::new(other::Server)))
537            .take();
538
539        // Both are present, and each answers as itself.
540        assert_eq!(snapshot.get::<Server>().unwrap().port, 8080);
541        assert!(snapshot.get::<other::Server>().is_some());
542        assert_eq!(snapshot.len(), 2, "one name, two sections");
543    }
544
545    #[test]
546    fn a_section_nobody_registered_is_a_different_error() {
547        let snapshot = Sections::new().section(server(1)).take();
548
549        match snapshot.require::<Features>() {
550            Err(NotInScope::NotListed(name)) => assert!(name.ends_with("Features")),
551            other => panic!("expected NotListed, got {other:?}"),
552        }
553    }
554
555    #[test]
556    fn the_two_errors_say_what_to_do_about_them() {
557        let listed = NotInScope::NotLoaded("Server").to_string();
558        let missing = NotInScope::NotListed("Server").to_string();
559
560        assert!(listed.contains("init()"), "{listed}");
561        assert!(missing.contains("sections!"), "{missing}");
562    }
563
564    #[test]
565    fn registering_a_type_twice_keeps_the_last_reader() {
566        let snapshot = Sections::new().section(server(1)).section(server(2)).take();
567
568        assert_eq!(snapshot.len(), 1);
569        assert_eq!(snapshot.get::<Server>().unwrap().port, 2);
570    }
571
572    #[test]
573    fn debug_prints_the_names_and_not_the_values() {
574        let snapshot = Sections::new().section(server(5432)).take();
575        let rendered = format!("{snapshot:?}");
576
577        assert!(rendered.contains("Server"), "{rendered}");
578        assert!(
579            !rendered.contains("5432"),
580            "a value reached Debug: {rendered}"
581        );
582    }
583
584    #[test]
585    fn take_refuses_a_snapshot_that_straddled_a_reload() {
586        // The race the whole crate turns on. Each section has its own
587        // atomic cell, so reading two of them is two loads — and a reload
588        // landing between them would put two generations in one snapshot.
589        //
590        // The first reader here *is* that reload: it moves both sections
591        // while the read is in progress, which is the worst possible
592        // timing, and it does so only on the first few attempts.
593        use std::sync::atomic::{AtomicU64, Ordering};
594
595        static A: AtomicU64 = AtomicU64::new(1);
596        static B: AtomicU64 = AtomicU64::new(1);
597        static DISTURB: AtomicU64 = AtomicU64::new(3);
598
599        let sections = Sections::new()
600            .section_with_generation(
601                || {
602                    // Reading `A` is where the reload lands.
603                    if DISTURB.load(Ordering::SeqCst) > 0 {
604                        DISTURB.fetch_sub(1, Ordering::SeqCst);
605                        A.fetch_add(1, Ordering::SeqCst);
606                        B.fetch_add(1, Ordering::SeqCst);
607                    }
608
609                    Some(Arc::new(Server {
610                        port: A.load(Ordering::SeqCst) as u16,
611                    }))
612                },
613                || A.load(Ordering::SeqCst),
614            )
615            .section_with_generation(
616                || {
617                    Some(Arc::new(Features {
618                        cache: B.load(Ordering::SeqCst) % 2 == 0,
619                    }))
620                },
621                || B.load(Ordering::SeqCst),
622            );
623
624        let snapshot = sections.take();
625
626        // Both sections came from the same generation: `A` and `B` move
627        // together, so `port` and `cache` must agree about which one.
628        let port = u64::from(snapshot.get::<Server>().unwrap().port);
629        let cache = snapshot.get::<Features>().unwrap().cache;
630
631        assert_eq!(
632            cache,
633            port % 2 == 0,
634            "the snapshot mixed generations: port={port}, cache={cache}"
635        );
636        assert_eq!(DISTURB.load(Ordering::SeqCst), 0, "it should have retried");
637    }
638
639    #[test]
640    fn a_list_that_cannot_report_generations_still_reads() {
641        // `section()` takes a reader and nothing else, so there is no
642        // counter to compare. That list reads once, as it always did.
643        let sections = Sections::new().section(server(8080));
644
645        assert!(!sections.is_consistent());
646        assert_eq!(sections.take().get::<Server>().unwrap().port, 8080);
647    }
648
649    #[test]
650    fn a_snapshot_crosses_threads() {
651        let snapshot = Sections::new().section(server(8080)).take();
652
653        let moved = std::thread::spawn(move || snapshot.get::<Server>().unwrap().port);
654
655        assert_eq!(moved.join().unwrap(), 8080);
656    }
657}