1use 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum ProviderLifetime {
14 Singleton,
16 Transient,
18 Request,
20}
21
22pub 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
32pub 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 singleton_cache: OnceLock<Arc<ErasedProvider>>,
45}
46
47enum SingletonState {
48 Empty,
49 Initializing,
50 Ready(Arc<ErasedProvider>),
51}
52
53impl ProviderEntry {
54 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 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 pub fn type_name(&self) -> &'static str {
94 self.type_name
95 }
96
97 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}