nidus_core/container/
request_scope.rs1use 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
13pub 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
25pub 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 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 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 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 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 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}