Skip to main content

nidus_core/provider/
mod.rs

1//! Provider registration primitives.
2
3use std::{
4    any::{Any, TypeId},
5    panic::{AssertUnwindSafe, catch_unwind},
6    sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock},
7};
8
9use crate::{Container, NidusError, RequestScope, Result, resolution};
10
11/// Provider creation and reuse strategy.
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum ProviderLifetime {
14    /// Create once and reuse for all resolutions.
15    Singleton,
16    /// Create a fresh value on every resolution.
17    Transient,
18    /// Create per request when request scopes are enabled.
19    Request,
20}
21
22/// Marker trait for injectable provider values.
23pub trait Provider: Send + Sync + 'static {}
24
25impl<T> Provider for T where T: Send + Sync + 'static {}
26
27type ErasedProvider = dyn Any + Send + Sync;
28type ProviderFactory = dyn Fn(&Container) -> Result<Arc<ErasedProvider>> + Send + Sync;
29type RequestProviderFactory =
30    dyn for<'scope> Fn(&RequestScope<'scope>) -> Result<Arc<ErasedProvider>> + Send + Sync;
31
32/// A typed provider registration stored by the container.
33pub struct ProviderEntry {
34    type_id: TypeId,
35    type_name: &'static str,
36    lifetime: ProviderLifetime,
37    factory: Arc<ProviderFactory>,
38    request_factory: Option<Arc<RequestProviderFactory>>,
39    singleton: Mutex<SingletonState>,
40    singleton_ready: Condvar,
41    // Lock-free read path for constructed singletons. Set exactly once, after a
42    // factory succeeds; failed or panicking factories leave it empty so the
43    // `singleton` state machine keeps its retry semantics.
44    singleton_cache: OnceLock<Arc<ErasedProvider>>,
45}
46
47enum SingletonState {
48    Empty,
49    Initializing,
50    Ready(Arc<ErasedProvider>),
51}
52
53impl ProviderEntry {
54    /// Creates a provider entry from an erased factory.
55    pub fn new(
56        type_id: TypeId,
57        type_name: &'static str,
58        lifetime: ProviderLifetime,
59        factory: Arc<ProviderFactory>,
60    ) -> Self {
61        Self {
62            type_id,
63            type_name,
64            lifetime,
65            factory,
66            request_factory: None,
67            singleton: Mutex::new(SingletonState::Empty),
68            singleton_ready: Condvar::new(),
69            singleton_cache: OnceLock::new(),
70        }
71    }
72
73    /// Creates a request-scoped provider entry from an erased request-scope factory.
74    pub fn new_request_scoped(
75        type_id: TypeId,
76        type_name: &'static str,
77        factory: Arc<ProviderFactory>,
78        request_factory: Arc<RequestProviderFactory>,
79    ) -> Self {
80        Self {
81            type_id,
82            type_name,
83            lifetime: ProviderLifetime::Request,
84            factory,
85            request_factory: Some(request_factory),
86            singleton: Mutex::new(SingletonState::Empty),
87            singleton_ready: Condvar::new(),
88            singleton_cache: OnceLock::new(),
89        }
90    }
91
92    /// Returns the registered provider type name.
93    pub fn type_name(&self) -> &'static str {
94        self.type_name
95    }
96
97    /// Returns the configured provider lifetime.
98    pub fn lifetime(&self) -> ProviderLifetime {
99        self.lifetime
100    }
101
102    pub(crate) fn resolve_erased(&self, container: &Container) -> Result<Arc<ErasedProvider>> {
103        match self.lifetime {
104            ProviderLifetime::Singleton => self.resolve_singleton(container),
105            ProviderLifetime::Transient | ProviderLifetime::Request => {
106                self.create_erased(container)
107            }
108        }
109    }
110
111    pub(crate) fn resolve_erased_in_scope(
112        &self,
113        scope: &RequestScope<'_>,
114    ) -> Result<Arc<ErasedProvider>> {
115        match self.lifetime {
116            ProviderLifetime::Request => self.create_erased_in_scope(scope),
117            ProviderLifetime::Singleton | ProviderLifetime::Transient => {
118                self.resolve_erased(scope.container())
119            }
120        }
121    }
122
123    fn create_erased(&self, container: &Container) -> Result<Arc<ErasedProvider>> {
124        (self.factory)(container).map_err(|source| NidusError::ProviderFactory {
125            type_name: self.type_name,
126            source: Box::new(source),
127        })
128    }
129
130    fn resolve_singleton(&self, container: &Container) -> Result<Arc<ErasedProvider>> {
131        if let Some(instance) = self.singleton_cache.get() {
132            return Ok(Arc::clone(instance));
133        }
134        loop {
135            let mut singleton = lock_unpoisoned(&self.singleton);
136            match &*singleton {
137                SingletonState::Ready(instance) => return Ok(Arc::clone(instance)),
138                SingletonState::Initializing => {
139                    if resolution::is_active(self.type_id) {
140                        return Err(NidusError::CircularProviderResolution {
141                            type_name: self.type_name,
142                        });
143                    }
144                    drop(wait_unpoisoned(&self.singleton_ready, singleton));
145                }
146                SingletonState::Empty => {
147                    let _guard = resolution::enter(self.type_id, self.type_name)?;
148                    *singleton = SingletonState::Initializing;
149                    drop(singleton);
150
151                    let instance =
152                        match catch_unwind(AssertUnwindSafe(|| self.create_erased(container))) {
153                            Ok(outcome) => outcome,
154                            Err(panic_payload) => {
155                                let mut singleton = lock_unpoisoned(&self.singleton);
156                                *singleton = SingletonState::Empty;
157                                self.singleton_ready.notify_all();
158                                drop(singleton);
159                                std::panic::resume_unwind(panic_payload);
160                            }
161                        };
162                    let mut singleton = lock_unpoisoned(&self.singleton);
163                    match instance {
164                        Ok(instance) => {
165                            *singleton = SingletonState::Ready(Arc::clone(&instance));
166                            let _ = self.singleton_cache.set(Arc::clone(&instance));
167                            self.singleton_ready.notify_all();
168                            return Ok(instance);
169                        }
170                        Err(error) => {
171                            *singleton = SingletonState::Empty;
172                            self.singleton_ready.notify_all();
173                            return Err(error);
174                        }
175                    }
176                }
177            }
178        }
179    }
180
181    fn create_erased_in_scope(&self, scope: &RequestScope<'_>) -> Result<Arc<ErasedProvider>> {
182        if let Some(factory) = &self.request_factory {
183            factory(scope).map_err(|source| NidusError::ProviderFactory {
184                type_name: self.type_name,
185                source: Box::new(source),
186            })
187        } else {
188            self.create_erased(scope.container())
189        }
190    }
191}
192
193fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
194    mutex
195        .lock()
196        .unwrap_or_else(|poisoned| poisoned.into_inner())
197}
198
199fn wait_unpoisoned<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
200    condvar
201        .wait(guard)
202        .unwrap_or_else(|poisoned| poisoned.into_inner())
203}
204
205#[cfg(test)]
206mod tests {
207    use std::{
208        any::{Any, type_name},
209        sync::Arc,
210        thread,
211    };
212
213    use super::{ProviderEntry, ProviderLifetime};
214    use crate::Container;
215
216    #[test]
217    fn singleton_provider_reuses_the_constructed_instance() {
218        let provider = ProviderEntry::new(
219            std::any::TypeId::of::<String>(),
220            type_name::<String>(),
221            ProviderLifetime::Singleton,
222            Arc::new(|_container| Ok(Arc::new("ready".to_owned()) as Arc<dyn Any + Send + Sync>)),
223        );
224        let container = Container::new();
225
226        let first = provider.resolve_erased(&container).unwrap();
227        let second = provider.resolve_erased(&container).unwrap();
228        assert!(Arc::ptr_eq(&first, &second));
229    }
230
231    #[test]
232    fn singleton_provider_retries_after_factory_error() {
233        use std::sync::atomic::{AtomicBool, Ordering};
234
235        let failed_once = Arc::new(AtomicBool::new(false));
236        let provider = ProviderEntry::new(
237            std::any::TypeId::of::<String>(),
238            type_name::<String>(),
239            ProviderLifetime::Singleton,
240            Arc::new({
241                let failed_once = Arc::clone(&failed_once);
242                move |_container| {
243                    if failed_once.swap(true, Ordering::SeqCst) {
244                        Ok(Arc::new("recovered".to_owned()) as Arc<dyn Any + Send + Sync>)
245                    } else {
246                        Err(crate::NidusError::MissingProvider {
247                            type_name: "transient failure",
248                        })
249                    }
250                }
251            }),
252        );
253        let container = Container::new();
254
255        assert!(provider.resolve_erased(&container).is_err());
256        let value = provider
257            .resolve_erased(&container)
258            .unwrap()
259            .downcast::<String>()
260            .unwrap();
261        assert_eq!(&*value, "recovered");
262    }
263
264    #[test]
265    fn singleton_provider_recovers_from_poisoned_cache() {
266        let provider = Arc::new(ProviderEntry::new(
267            std::any::TypeId::of::<String>(),
268            type_name::<String>(),
269            ProviderLifetime::Singleton,
270            Arc::new(|_container| Ok(Arc::new("ready".to_owned()) as Arc<dyn Any + Send + Sync>)),
271        ));
272        let poisoned_provider = Arc::clone(&provider);
273
274        let panic = thread::spawn(move || {
275            let _singleton = poisoned_provider.singleton.lock().unwrap();
276            panic!("poison singleton cache");
277        });
278        assert!(panic.join().is_err());
279
280        let value = provider
281            .resolve_erased(&Container::new())
282            .unwrap()
283            .downcast::<String>()
284            .unwrap();
285        assert_eq!(&*value, "ready");
286    }
287}