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}