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