Skip to main content

nidus_core/container/
request_scope.rs

1use std::{
2    any::{Any, TypeId, type_name},
3    sync::{Arc, Condvar, Mutex, MutexGuard},
4};
5
6use crate::{
7    Container, Inject, NidusError, Optional, ProviderLifetime, Result, Scoped, resolution,
8};
9
10use super::{TypeIdMap, downcast};
11
12/// Per-request dependency scope.
13pub struct RequestScope<'a> {
14    container: RequestScopeContainer<'a>,
15    request_instances: Mutex<TypeIdMap<RequestInstanceState>>,
16    request_instance_ready: Condvar,
17}
18
19enum RequestInstanceState {
20    Initializing,
21    Ready(Arc<dyn Any + Send + Sync>),
22}
23
24/// Shared request scope handle suitable for HTTP request extensions.
25pub type SharedRequestScope = Arc<RequestScope<'static>>;
26
27enum RequestScopeContainer<'a> {
28    Borrowed(&'a Container),
29    Shared(Arc<Container>),
30}
31
32impl RequestScopeContainer<'_> {
33    fn as_ref(&self) -> &Container {
34        match self {
35            Self::Borrowed(container) => container,
36            Self::Shared(container) => container,
37        }
38    }
39}
40
41impl<'a> RequestScope<'a> {
42    pub(super) fn borrowed(container: &'a Container) -> Self {
43        Self {
44            container: RequestScopeContainer::Borrowed(container),
45            request_instances: Mutex::new(TypeIdMap::default()),
46            request_instance_ready: Condvar::new(),
47        }
48    }
49}
50
51impl RequestScope<'_> {
52    pub(crate) fn container(&self) -> &Container {
53        self.container.as_ref()
54    }
55
56    /// Resolves a dependency in this request scope.
57    pub fn resolve<T>(&self) -> Result<Arc<T>>
58    where
59        T: Send + Sync + 'static,
60    {
61        let entry = self.container().entry::<T>()?;
62        let erased = match entry.lifetime() {
63            ProviderLifetime::Request => {
64                let type_id = TypeId::of::<T>();
65                self.resolve_request_instance(type_id, type_name::<T>(), || {
66                    entry.resolve_erased_in_scope(self)
67                })?
68            }
69            ProviderLifetime::Singleton | ProviderLifetime::Transient => {
70                entry.resolve_erased(self.container())?
71            }
72        };
73
74        downcast::<T>(erased)
75    }
76
77    /// Resolves a typed dependency reference in this request scope.
78    pub fn inject<T>(&self) -> Result<Inject<T>>
79    where
80        T: Send + Sync + 'static,
81    {
82        self.resolve::<T>().map(Inject::new)
83    }
84
85    /// Resolves an optional typed dependency reference in this request scope.
86    ///
87    /// Missing providers become `Optional::new(None)`, while registered providers
88    /// that fail to construct still return their original error.
89    pub fn optional<T>(&self) -> Result<Optional<T>>
90    where
91        T: Send + Sync + 'static,
92    {
93        match self.inject::<T>() {
94            Ok(value) => Ok(Optional::new(Some(value))),
95            Err(NidusError::MissingProvider { .. }) => Ok(Optional::new(None)),
96            Err(error) => Err(error),
97        }
98    }
99
100    /// Resolves a request-scoped dependency wrapper in this request scope.
101    pub fn scoped<T>(&self) -> Result<Scoped<T>>
102    where
103        T: Send + Sync + 'static,
104    {
105        self.inject::<T>().map(Scoped::new)
106    }
107
108    fn resolve_request_instance(
109        &self,
110        type_id: TypeId,
111        type_name: &'static str,
112        create: impl FnOnce() -> Result<Arc<dyn Any + Send + Sync>>,
113    ) -> Result<Arc<dyn Any + Send + Sync>> {
114        let mut create = Some(create);
115        loop {
116            let mut instances = lock_unpoisoned(&self.request_instances);
117            match instances.get(&type_id) {
118                Some(RequestInstanceState::Ready(instance)) => return Ok(Arc::clone(instance)),
119                Some(RequestInstanceState::Initializing) => {
120                    if resolution::is_active(type_id) {
121                        return Err(NidusError::CircularProviderResolution { type_name });
122                    }
123                    drop(wait_unpoisoned(&self.request_instance_ready, instances));
124                }
125                None => {
126                    let _guard = resolution::enter(type_id, type_name)?;
127                    instances.insert(type_id, RequestInstanceState::Initializing);
128                    drop(instances);
129
130                    let initializer = create
131                        .take()
132                        .expect("request instance factory can only be used by initializer");
133                    let instance = initializer();
134                    let mut instances = lock_unpoisoned(&self.request_instances);
135                    match instance {
136                        Ok(instance) => {
137                            instances.insert(
138                                type_id,
139                                RequestInstanceState::Ready(Arc::clone(&instance)),
140                            );
141                            self.request_instance_ready.notify_all();
142                            return Ok(instance);
143                        }
144                        Err(error) => {
145                            instances.remove(&type_id);
146                            self.request_instance_ready.notify_all();
147                            return Err(error);
148                        }
149                    }
150                }
151            }
152        }
153    }
154}
155
156impl RequestScope<'static> {
157    /// Creates a request scope that owns a shared container handle.
158    pub fn from_shared_container(container: Arc<Container>) -> Self {
159        Self {
160            container: RequestScopeContainer::Shared(container),
161            request_instances: Mutex::new(TypeIdMap::default()),
162            request_instance_ready: Condvar::new(),
163        }
164    }
165}
166
167fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
168    mutex
169        .lock()
170        .unwrap_or_else(|poisoned| poisoned.into_inner())
171}
172
173fn wait_unpoisoned<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
174    condvar
175        .wait(guard)
176        .unwrap_or_else(|poisoned| poisoned.into_inner())
177}
178
179#[cfg(test)]
180mod tests {
181    use std::{sync::Arc, thread};
182
183    use super::RequestScope;
184    use crate::Container;
185
186    #[derive(Debug, Eq, PartialEq)]
187    struct RequestValue(u64);
188
189    #[test]
190    fn request_scope_recovers_from_poisoned_instance_cache() {
191        let mut container = Container::new();
192        container
193            .register_request_scoped::<RequestValue, _>(|_scope| Ok(RequestValue(42)))
194            .unwrap();
195        let scope = Arc::new(RequestScope::from_shared_container(Arc::new(container)));
196        let poisoned_scope = Arc::clone(&scope);
197
198        let panic = thread::spawn(move || {
199            let _instances = poisoned_scope.request_instances.lock().unwrap();
200            panic!("poison request scope cache");
201        });
202        assert!(panic.join().is_err());
203
204        assert_eq!(*scope.resolve::<RequestValue>().unwrap(), RequestValue(42));
205    }
206}