Skip to main content

ntex_service/
cfg.rs

1//! Typed, shared configuration for services.
2#![allow(clippy::should_implement_trait, clippy::new_ret_no_self)]
3use std::any::{Any, TypeId};
4use std::cell::{RefCell, UnsafeCell};
5use std::sync::{Arc, atomic::AtomicUsize, atomic::Ordering};
6use std::{fmt, hash::Hash, hash::Hasher, marker::PhantomData, mem, ops, ptr, rc};
7
8type Key = (usize, TypeId);
9type HashMap<K, V> = std::collections::HashMap<K, V, foldhash::fast::RandomState>;
10
11thread_local! {
12    static DEFAULT_CFG: Arc<Storage> = {
13        let mut st = Arc::new(Storage::new("--", "", false, CfgContext(ptr::null())));
14        let p = Arc::as_ptr(&st);
15        Arc::get_mut(&mut st).unwrap().ctx.update(p);
16        st
17    };
18    static MAPPING: RefCell<HashMap<Key, Arc<dyn Any + Send + Sync>>> = {
19        RefCell::new(HashMap::default())
20    };
21}
22static IDX: AtomicUsize = AtomicUsize::new(0);
23const KIND_ARC: usize = 1;
24const KIND_UNMASK: usize = !KIND_ARC;
25
26/// A value that can be stored in [`SharedCfg`].
27pub trait Configuration: Default + Send + Sync + fmt::Debug + 'static {
28    /// Human-readable configuration name used in diagnostics.
29    const NAME: &'static str;
30
31    /// Returns the shared context associated with this value.
32    fn ctx(&self) -> &CfgContext;
33
34    /// Associates this value with a shared configuration context.
35    fn set_ctx(&mut self, ctx: CfgContext);
36}
37
38#[derive(Debug)]
39struct Storage {
40    id: usize,
41    tag: &'static str,
42    service: &'static str,
43    ctx: CfgContext,
44    building: bool,
45    data: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
46}
47
48impl Storage {
49    fn new(tag: &'static str, service: &'static str, building: bool, ctx: CfgContext) -> Self {
50        let id = IDX.fetch_add(1, Ordering::SeqCst);
51        Storage {
52            id,
53            ctx,
54            tag,
55            service,
56            building,
57            data: HashMap::default(),
58        }
59    }
60}
61
62#[derive(Debug)]
63/// Metadata and lookup access for a shared configuration.
64pub struct CfgContext(*const Storage);
65
66unsafe impl Send for CfgContext {}
67unsafe impl Sync for CfgContext {}
68
69impl CfgContext {
70    fn update(&mut self, new_p: *const Storage) {
71        self.0 = new_p;
72    }
73
74    /// Returns the unique identifier of the shared configuration.
75    pub fn id(&self) -> usize {
76        self.get_ref().id
77    }
78
79    #[inline]
80    /// Returns the configuration tag.
81    pub fn tag(&self) -> &'static str {
82        self.get_ref().tag
83    }
84
85    /// Returns the configured service name.
86    pub fn service(&self) -> &'static str {
87        self.get_ref().service
88    }
89
90    /// Returns a typed configuration value from the same shared configuration.
91    pub fn get<T>(&self) -> Cfg<T>
92    where
93        T: Configuration,
94    {
95        let inner: Arc<Storage> = unsafe { Arc::from_raw(self.0) };
96        let cfg = get(&inner);
97        mem::forget(inner);
98        cfg
99    }
100
101    /// Returns a handle to the complete shared configuration.
102    pub fn shared(&self) -> SharedCfg {
103        let inner: Arc<Storage> = unsafe { Arc::from_raw(self.0) };
104        let shared = SharedCfg(inner.clone());
105        mem::forget(inner);
106        shared
107    }
108
109    fn get_ref(&self) -> &Storage {
110        unsafe { self.0.as_ref().unwrap() }
111    }
112}
113
114impl Default for CfgContext {
115    #[inline]
116    fn default() -> Self {
117        CfgContext(DEFAULT_CFG.with(Arc::as_ptr))
118    }
119}
120
121#[derive(Debug)]
122/// A typed handle to a value in a [`SharedCfg`].
123pub struct Cfg<T: Configuration>(UnsafeCell<*const T>, PhantomData<rc::Rc<T>>);
124
125impl<T: Configuration> Cfg<T> {
126    fn new(ptr: *const T) -> Self {
127        Self(UnsafeCell::new(ptr), PhantomData)
128    }
129
130    #[inline]
131    /// Returns the unique identifier of the shared configuration.
132    pub fn id(&self) -> usize {
133        self.get_ref().ctx().id()
134    }
135
136    #[inline]
137    /// Returns the configuration tag.
138    pub fn tag(&self) -> &'static str {
139        self.get_ref().ctx().tag()
140    }
141
142    /// Returns the configured service name.
143    pub fn service(&self) -> &'static str {
144        self.get_ref().ctx().service()
145    }
146
147    /// Returns a handle to the complete shared configuration.
148    pub fn shared(&self) -> SharedCfg {
149        self.get_ref().ctx().shared()
150    }
151
152    /// Returns another typed value from the same shared configuration.
153    pub fn get_shared<U>(&self) -> Cfg<U>
154    where
155        U: Configuration,
156    {
157        self.get_ref().ctx().get::<U>()
158    }
159
160    fn get_ref(&self) -> &T {
161        unsafe {
162            (*self.0.get())
163                .map_addr(|addr| addr & KIND_UNMASK)
164                .as_ref()
165                .unwrap()
166        }
167    }
168
169    #[allow(clippy::needless_pass_by_value)]
170    /// Replaces the inner value.
171    ///
172    /// # Safety
173    ///
174    /// The caller must guarantee that no references to the inner `T` value
175    /// exist at the time this function is called.
176    pub unsafe fn replace(&self, cfg: Cfg<T>) {
177        unsafe {
178            ptr::swap(self.0.get(), cfg.0.get());
179        }
180    }
181}
182
183impl<T: Configuration> Drop for Cfg<T> {
184    fn drop(&mut self) {
185        unsafe {
186            let addr = (*self.0.get()).map_addr(|addr| addr & KIND_UNMASK);
187            Arc::decrement_strong_count(addr.as_ref().unwrap().ctx().0);
188
189            if ((*self.0.get()).addr() & KIND_ARC) != 0 {
190                Arc::from_raw(addr);
191            }
192        }
193    }
194}
195
196impl<T: Configuration> Clone for Cfg<T> {
197    #[inline]
198    fn clone(&self) -> Self {
199        self.ctx().get()
200    }
201}
202
203impl<T: Configuration> From<T> for Cfg<T> {
204    #[inline]
205    fn from(cfg: T) -> Self {
206        SharedCfg::new("--").add(cfg).build().get()
207    }
208}
209
210impl<'a, T: Configuration> From<&'a T> for Cfg<T> {
211    #[inline]
212    fn from(cfg: &'a T) -> Self {
213        cfg.ctx().get()
214    }
215}
216
217impl<T: Configuration> ops::Deref for Cfg<T> {
218    type Target = T;
219
220    #[inline]
221    fn deref(&self) -> &T {
222        self.get_ref()
223    }
224}
225
226impl<T: Configuration> Default for Cfg<T> {
227    #[inline]
228    fn default() -> Self {
229        SharedCfg::default().get()
230    }
231}
232
233#[derive(Clone, Debug)]
234/// An immutable collection of typed configuration values.
235pub struct SharedCfg(Arc<Storage>);
236
237#[derive(Debug)]
238/// Builder for [`SharedCfg`].
239pub struct SharedCfgBuilder {
240    ctx: CfgContext,
241    storage: Arc<Storage>,
242}
243
244impl Eq for SharedCfg {}
245
246impl PartialEq for SharedCfg {
247    fn eq(&self, other: &Self) -> bool {
248        ptr::from_ref(self.0.as_ref()) == ptr::from_ref(other.0.as_ref())
249    }
250}
251
252impl Hash for SharedCfg {
253    fn hash<H: Hasher>(&self, state: &mut H) {
254        self.0.id.hash(state);
255    }
256}
257
258impl SharedCfg {
259    /// Starts building a shared configuration with the given tag.
260    pub fn new(tag: &'static str) -> SharedCfgBuilder {
261        SharedCfgBuilder::new(tag)
262    }
263
264    #[inline]
265    /// Returns the unique identifier of this configuration.
266    pub fn id(&self) -> usize {
267        self.0.id
268    }
269
270    #[inline]
271    /// Returns the configuration tag.
272    pub fn tag(&self) -> &'static str {
273        self.0.tag
274    }
275
276    /// Returns the configured service name.
277    pub fn service(&self) -> &'static str {
278        self.0.service
279    }
280
281    /// Returns the configuration value of type `T`.
282    ///
283    /// # Panics
284    ///
285    /// Panics if the shared configuration is still being built.
286    pub fn get<T>(&self) -> Cfg<T>
287    where
288        T: Configuration,
289    {
290        get(&self.0)
291    }
292}
293
294impl Default for SharedCfg {
295    #[inline]
296    fn default() -> Self {
297        Self(DEFAULT_CFG.with(Clone::clone))
298    }
299}
300
301impl<'a> From<&'a SharedCfg> for SharedCfg {
302    fn from(cfg: &'a SharedCfg) -> SharedCfg {
303        cfg.clone()
304    }
305}
306
307impl<T: Configuration> From<SharedCfg> for Cfg<T> {
308    #[inline]
309    fn from(cfg: SharedCfg) -> Self {
310        cfg.get()
311    }
312}
313
314impl<'a, T: Configuration> From<&'a SharedCfg> for Cfg<T> {
315    #[inline]
316    fn from(cfg: &'a SharedCfg) -> Self {
317        cfg.get()
318    }
319}
320
321impl<T: Configuration> From<SharedCfgBuilder> for Cfg<T> {
322    #[inline]
323    fn from(cfg: SharedCfgBuilder) -> Self {
324        cfg.build().get()
325    }
326}
327
328impl<T: Configuration> From<T> for SharedCfg {
329    #[inline]
330    fn from(cfg: T) -> Self {
331        SharedCfg::new("--").add(cfg).build()
332    }
333}
334
335impl<T: Configuration> From<Cfg<T>> for SharedCfg {
336    #[inline]
337    fn from(cfg: Cfg<T>) -> Self {
338        cfg.shared()
339    }
340}
341
342impl SharedCfgBuilder {
343    fn new(tag: &'static str) -> SharedCfgBuilder {
344        let mut storage = Arc::new(Storage::new(tag, tag, true, CfgContext::default()));
345        let ctx = CfgContext(Arc::as_ptr(&storage));
346        Arc::get_mut(&mut storage).unwrap().ctx.update(ctx.0);
347
348        SharedCfgBuilder { ctx, storage }
349    }
350
351    #[must_use]
352    /// Sets the service name.
353    pub fn service(mut self, name: &'static str) -> Self {
354        Arc::get_mut(&mut self.storage).unwrap().service = name;
355        self
356    }
357
358    #[must_use]
359    /// Inserts a typed value into this configuration.
360    ///
361    /// Replaces the existing value when the same type was already inserted.
362    pub fn add<T: Configuration>(mut self, mut val: T) -> Self {
363        val.set_ctx(CfgContext(self.ctx.0));
364        Arc::get_mut(&mut self.storage)
365            .unwrap()
366            .data
367            .insert(TypeId::of::<T>(), Box::new(val));
368        self
369    }
370
371    #[must_use]
372    /// Finishes building and returns the shared configuration.
373    pub fn build(self) -> SharedCfg {
374        self.into()
375    }
376}
377
378impl From<SharedCfgBuilder> for SharedCfg {
379    fn from(mut cfg: SharedCfgBuilder) -> SharedCfg {
380        let st = Arc::get_mut(&mut cfg.storage).unwrap();
381        st.building = false;
382        SharedCfg(cfg.storage)
383    }
384}
385
386fn get<T>(st: &Arc<Storage>) -> Cfg<T>
387where
388    T: Configuration,
389{
390    assert!(
391        !st.building,
392        "{}: Cannot access shared config while building",
393        st.tag
394    );
395
396    // increase arc refs for storage instead of actual item
397    // CfgContext and Cfg::shared() rely on Arc<Storage>.
398    mem::forget(st.clone());
399
400    let tp = TypeId::of::<T>();
401    if let Some(arc) = st.data.get(&tp) {
402        Cfg::new(arc.as_ref().downcast_ref::<T>().unwrap())
403    } else {
404        MAPPING.with(|store| {
405            let key = (st.id, tp);
406            if let Some(arc) = store.borrow().get(&key) {
407                Cfg::new(
408                    Arc::into_raw(arc.clone())
409                        .cast::<T>()
410                        .map_addr(|addr| addr ^ KIND_ARC),
411                )
412            } else {
413                log::info!(
414                    "{}: Configuration {:?} does not exist, using default",
415                    st.tag,
416                    T::NAME
417                );
418                let mut val = T::default();
419                val.set_ctx(CfgContext(st.ctx.0));
420                let arc = Arc::new(val);
421                store.borrow_mut().insert(key, arc.clone());
422                Cfg::new(
423                    Arc::into_raw(arc)
424                        .cast::<T>()
425                        .map_addr(|addr| addr ^ KIND_ARC),
426                )
427            }
428        })
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    #[test]
437    #[allow(clippy::should_panic_without_expect)]
438    #[should_panic]
439    fn access_cfg_in_building_state() {
440        #[derive(Debug)]
441        struct TestCfg {
442            config: CfgContext,
443        }
444        impl TestCfg {
445            fn new() -> Self {
446                Self {
447                    config: CfgContext::default(),
448                }
449            }
450        }
451        impl Default for TestCfg {
452            fn default() -> Self {
453                panic!()
454            }
455        }
456        impl Configuration for TestCfg {
457            const NAME: &str = "TEST";
458            fn ctx(&self) -> &CfgContext {
459                &self.config
460            }
461            fn set_ctx(&mut self, ctx: CfgContext) {
462                let _ = ctx.shared().get::<TestCfg>();
463                self.config = ctx;
464            }
465        }
466        let _ = TestCfg::new().ctx();
467        let _ = SharedCfg::new("TEST").add(TestCfg::new());
468    }
469
470    #[test]
471    fn shared_cfg() {
472        #[derive(Default, Debug)]
473        struct TestCfg {
474            config: CfgContext,
475        }
476        impl Configuration for TestCfg {
477            const NAME: &str = "TEST";
478            fn ctx(&self) -> &CfgContext {
479                &self.config
480            }
481            fn set_ctx(&mut self, ctx: CfgContext) {
482                self.config = ctx;
483            }
484        }
485
486        let cfg: SharedCfg = SharedCfg::new("TEST")
487            .add(TestCfg::default())
488            .service("SVC")
489            .into();
490
491        assert_eq!(cfg.tag(), "TEST");
492        assert_eq!(cfg.service(), "SVC");
493        let t = cfg.get::<TestCfg>();
494        assert_eq!(t.tag(), "TEST");
495        assert_eq!(t.service(), "SVC");
496        assert_eq!(t.shared(), cfg);
497        let t: Cfg<TestCfg> = Cfg::default();
498        assert_eq!(t.tag(), "--");
499        assert_eq!(t.service(), "");
500        assert_eq!(t.ctx().id(), t.id());
501
502        let t: Cfg<TestCfg> = t.ctx().get();
503        assert_eq!(t.tag(), "--");
504        assert_eq!(t.ctx().id(), t.id());
505
506        let cfg = SharedCfg::new("TEST2").build();
507        let cfg = SharedCfg::from(&cfg);
508        let t = cfg.get::<TestCfg>();
509        assert_eq!(t.tag(), "TEST2");
510        assert_eq!(t.id(), cfg.id());
511        drop(cfg);
512
513        let cfg2 = t.shared();
514        let t2 = cfg2.get::<TestCfg>();
515        assert_eq!(t2.tag(), "TEST2");
516        assert_eq!(t2.id(), cfg2.id());
517        unsafe { t2.replace(SharedCfg::from(SharedCfg::new("TEST3")).get::<TestCfg>()) };
518
519        let cfg2 = t2.shared();
520        let t3 = cfg2.get::<TestCfg>();
521        assert_eq!(t3.tag(), "TEST3");
522        assert_eq!(t3.id(), cfg2.id());
523
524        let t = SharedCfg::from(SharedCfg::new("TEST4").add(TestCfg::default())).get::<TestCfg>();
525        let cfg = t.shared();
526        assert_eq!(t.id(), cfg.id());
527        let t2 = t.clone();
528        assert_eq!(t2.id(), cfg.id());
529        assert_eq!(t2.tag(), "TEST4");
530
531        let t3 = t.ctx().get::<TestCfg>();
532        let t3 = Cfg::from(t3.get_ref());
533        let t3 = t3.get_shared::<TestCfg>();
534        assert_eq!(t3.id(), cfg.id());
535        assert_eq!(t3.tag(), "TEST4");
536
537        let t1 = SharedCfg::from(t3);
538        assert_eq!(t.id(), t1.id());
539    }
540
541    #[test]
542    fn cfg_conversions() {
543        use std::hash::BuildHasher;
544
545        #[derive(Default, Debug)]
546        struct TestCfg {
547            config: CfgContext,
548            val: usize,
549        }
550        impl Configuration for TestCfg {
551            const NAME: &str = "TEST";
552            fn ctx(&self) -> &CfgContext {
553                &self.config
554            }
555            fn set_ctx(&mut self, ctx: CfgContext) {
556                self.config = ctx;
557            }
558        }
559        let new = |val| TestCfg {
560            val,
561            ..Default::default()
562        };
563
564        let t = Cfg::from(new(1));
565        assert_eq!(t.val, 1);
566        assert_eq!(t.tag(), "--");
567
568        let shared = SharedCfg::from(new(2));
569        assert_eq!(shared.get::<TestCfg>().val, 2);
570        assert_eq!(Cfg::<TestCfg>::from(&shared).val, 2);
571        assert_eq!(Cfg::<TestCfg>::from(shared.clone()).val, 2);
572
573        let t: Cfg<TestCfg> = SharedCfg::new("T").add(new(3)).into();
574        assert_eq!(t.val, 3);
575        assert_eq!(t.tag(), "T");
576
577        let hasher = foldhash::fast::FixedState::default();
578        assert_eq!(hasher.hash_one(&shared), hasher.hash_one(shared.clone()));
579        assert_eq!(hasher.hash_one(&shared), hasher.hash_one(shared.id()));
580    }
581}