Skip to main content

nidus_core/container/
request_scope.rs

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