elif_core/container/
lifecycle.rs1use async_trait::async_trait;
2use std::sync::Arc;
3
4use crate::errors::CoreError;
5
6#[async_trait]
8pub trait AsyncInitializable: Send + Sync {
9 async fn initialize(&self) -> Result<(), CoreError>;
11
12 fn is_initialized(&self) -> bool {
14 true }
16}
17
18#[async_trait]
20pub trait Disposable: Send + Sync {
21 async fn dispose(&self) -> Result<(), CoreError>;
23}
24
25#[async_trait]
27pub trait LifecycleManaged: AsyncInitializable + Disposable {}
28
29impl<T> LifecycleManaged for T where T: AsyncInitializable + Disposable {}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ServiceState {
35 Registered,
37 Created,
39 Initialized,
41 Disposing,
43 Disposed,
45}
46
47pub struct ServiceLifecycleManager {
49 initializable_services: Vec<Arc<dyn AsyncInitializable>>,
51 disposable_services: Vec<Arc<dyn Disposable>>,
53 initializable_service_types: Vec<String>,
55 state: ServiceState,
57 disposal_handle: Option<tokio::task::JoinHandle<()>>,
59}
60
61impl std::fmt::Debug for ServiceLifecycleManager {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("ServiceLifecycleManager")
64 .field(
65 "initializable_services_count",
66 &self.initializable_services.len(),
67 )
68 .field("disposable_services_count", &self.disposable_services.len())
69 .field("state", &self.state)
70 .field("has_disposal_handle", &self.disposal_handle.is_some())
71 .finish()
72 }
73}
74
75impl ServiceLifecycleManager {
76 pub fn new() -> Self {
78 Self {
79 initializable_services: Vec::new(),
80 disposable_services: Vec::new(),
81 initializable_service_types: Vec::new(),
82 state: ServiceState::Registered,
83 disposal_handle: None,
84 }
85 }
86
87 pub fn add_initializable<T: AsyncInitializable + 'static>(&mut self, service: Arc<T>) {
89 self.initializable_services.push(service);
90 self.initializable_service_types
91 .push(std::any::type_name::<T>().to_string());
92 }
93
94 pub fn add_disposable<T: Disposable + 'static>(&mut self, service: Arc<T>) {
96 self.disposable_services.push(service);
97 }
98
99 pub fn add_lifecycle_managed<T: LifecycleManaged + 'static>(&mut self, service: Arc<T>) {
101 let service_clone = service.clone();
102 self.initializable_services.push(service_clone);
103 self.initializable_service_types
104 .push(std::any::type_name::<T>().to_string());
105 self.disposable_services.push(service);
106 }
107
108 pub async fn initialize_all(&mut self) -> Result<(), CoreError> {
110 if self.state != ServiceState::Registered {
111 return Err(CoreError::InvalidServiceDescriptor {
112 message: format!("Cannot initialize services in state: {:?}", self.state),
113 });
114 }
115
116 self.state = ServiceState::Created;
117
118 for (index, service) in self.initializable_services.iter().enumerate() {
120 let service_type = self
121 .initializable_service_types
122 .get(index)
123 .map(|s| s.as_str())
124 .unwrap_or("unknown");
125
126 service
127 .initialize()
128 .await
129 .map_err(|e| CoreError::ServiceInitializationFailed {
130 service_type: service_type.to_string(),
131 source: Box::new(e),
132 })?;
133 }
134
135 self.state = ServiceState::Initialized;
136 Ok(())
137 }
138
139 pub async fn initialize_all_with_timeout(
141 &mut self,
142 timeout: std::time::Duration,
143 ) -> Result<(), CoreError> {
144 let init_future = self.initialize_all();
145
146 match tokio::time::timeout(timeout, init_future).await {
147 Ok(result) => result,
148 Err(_) => Err(CoreError::ServiceInitializationFailed {
149 service_type: "timeout".to_string(),
150 source: Box::new(CoreError::InvalidServiceDescriptor {
151 message: format!("Service initialization timed out after {:?}", timeout),
152 }),
153 }),
154 }
155 }
156
157 pub async fn dispose_all(&mut self) -> Result<(), CoreError> {
159 if self.state == ServiceState::Disposed || self.state == ServiceState::Disposing {
160 return Ok(()); }
162
163 self.state = ServiceState::Disposing;
164
165 for service in self.disposable_services.iter().rev() {
167 if let Err(e) = service.dispose().await {
168 eprintln!("Error disposing service: {:?}", e);
170 }
171 }
172
173 self.state = ServiceState::Disposed;
174 self.disposal_handle = None; Ok(())
176 }
177
178 pub fn schedule_disposal(&mut self) {
181 if self.is_disposed() || self.disposal_handle.is_some() {
182 return; }
184
185 let services = std::mem::take(&mut self.disposable_services);
187 self.state = ServiceState::Disposing;
188
189 let handle = tokio::spawn(async move {
191 for service in services.iter().rev() {
192 if let Err(e) = service.dispose().await {
193 eprintln!("Error disposing service in background: {:?}", e);
194 }
195 }
196 });
197
198 self.disposal_handle = Some(handle);
199 }
200
201 pub async fn wait_for_disposal(&mut self) -> Result<(), CoreError> {
203 if let Some(handle) = self.disposal_handle.take() {
204 handle.await.map_err(|e| CoreError::SystemError {
205 message: format!("Disposal task failed: {}", e),
206 source: None,
207 })?;
208 self.state = ServiceState::Disposed;
209 }
210 Ok(())
211 }
212
213 pub fn state(&self) -> ServiceState {
215 self.state
216 }
217
218 pub fn is_initialized(&self) -> bool {
220 self.state == ServiceState::Initialized
221 }
222
223 pub fn is_disposed(&self) -> bool {
225 self.state == ServiceState::Disposed
226 }
227
228 pub fn initializable_count(&self) -> usize {
230 self.initializable_services.len()
231 }
232
233 pub fn disposable_count(&self) -> usize {
235 self.disposable_services.len()
236 }
237}
238
239impl Default for ServiceLifecycleManager {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245impl Drop for ServiceLifecycleManager {
246 fn drop(&mut self) {
247 if !self.is_disposed() && !self.disposable_services.is_empty() {
248 if let Ok(_handle) = tokio::runtime::Handle::try_current() {
250 self.schedule_disposal();
252 } else {
253 eprintln!(
255 "Warning: ServiceLifecycleManager dropped with {} undisposed services. \
256 No tokio runtime available for async disposal. \
257 Call dispose_all() explicitly before dropping.",
258 self.disposable_services.len()
259 );
260 }
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use std::sync::atomic::{AtomicBool, Ordering};
269 use std::sync::Arc;
270
271 #[derive(Default)]
272 struct TestService {
273 initialized: AtomicBool,
274 disposed: AtomicBool,
275 }
276
277 #[async_trait]
278 impl AsyncInitializable for TestService {
279 async fn initialize(&self) -> Result<(), CoreError> {
280 self.initialized.store(true, Ordering::SeqCst);
281 Ok(())
282 }
283
284 fn is_initialized(&self) -> bool {
285 self.initialized.load(Ordering::SeqCst)
286 }
287 }
288
289 #[async_trait]
290 impl Disposable for TestService {
291 async fn dispose(&self) -> Result<(), CoreError> {
292 self.disposed.store(true, Ordering::SeqCst);
293 Ok(())
294 }
295 }
296
297 #[tokio::test]
298 async fn test_lifecycle_manager_initialization() {
299 let mut manager = ServiceLifecycleManager::new();
300 let service = Arc::new(TestService::default());
301
302 assert!(!service.is_initialized());
303 manager.add_lifecycle_managed(service.clone());
304
305 manager.initialize_all().await.unwrap();
306
307 assert!(service.is_initialized());
308 assert!(manager.is_initialized());
309 }
310
311 #[tokio::test]
312 async fn test_lifecycle_manager_disposal() {
313 let mut manager = ServiceLifecycleManager::new();
314 let service = Arc::new(TestService::default());
315
316 manager.add_lifecycle_managed(service.clone());
317 manager.initialize_all().await.unwrap();
318
319 assert!(!service.disposed.load(Ordering::SeqCst));
320
321 manager.dispose_all().await.unwrap();
322
323 assert!(service.disposed.load(Ordering::SeqCst));
324 assert!(manager.is_disposed());
325 }
326
327 #[tokio::test]
328 async fn test_initialization_timeout() {
329 #[derive(Default)]
330 struct SlowService;
331
332 #[async_trait]
333 impl AsyncInitializable for SlowService {
334 async fn initialize(&self) -> Result<(), CoreError> {
335 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
337 Ok(())
338 }
339 }
340
341 let mut manager = ServiceLifecycleManager::new();
342 let service = Arc::new(SlowService::default());
343
344 manager.add_initializable(service);
345
346 let result = manager
348 .initialize_all_with_timeout(std::time::Duration::from_millis(50))
349 .await;
350
351 assert!(result.is_err());
352 }
353
354 #[tokio::test]
355 async fn test_background_disposal() {
356 let mut manager = ServiceLifecycleManager::new();
357 let service = Arc::new(TestService::default());
358
359 manager.add_lifecycle_managed(service.clone());
360 manager.initialize_all().await.unwrap();
361
362 assert!(!service.disposed.load(Ordering::SeqCst));
363
364 manager.schedule_disposal();
366
367 manager.wait_for_disposal().await.unwrap();
369
370 assert!(service.disposed.load(Ordering::SeqCst));
371 assert!(manager.is_disposed());
372 }
373
374 #[tokio::test]
375 async fn test_drop_with_runtime() {
376 let service = Arc::new(TestService::default());
377
378 {
379 let mut manager = ServiceLifecycleManager::new();
380 manager.add_lifecycle_managed(service.clone());
381 manager.initialize_all().await.unwrap();
382
383 }
386
387 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
389
390 assert!(service.disposed.load(Ordering::SeqCst));
392 }
393
394 #[tokio::test]
395 async fn test_initialization_error_with_service_type() {
396 #[derive(Default)]
397 struct FailingService;
398
399 #[async_trait]
400 impl AsyncInitializable for FailingService {
401 async fn initialize(&self) -> Result<(), CoreError> {
402 Err(CoreError::InvalidServiceDescriptor {
403 message: "Test initialization failure".to_string(),
404 })
405 }
406 }
407
408 let mut manager = ServiceLifecycleManager::new();
409 let service = Arc::new(FailingService::default());
410
411 manager.add_initializable(service);
412
413 let result = manager.initialize_all().await;
415
416 assert!(result.is_err());
417 let error = result.unwrap_err();
418
419 if let CoreError::ServiceInitializationFailed { service_type, .. } = error {
420 assert!(service_type.contains("FailingService"));
422 assert!(!service_type.eq("unknown"));
423 } else {
424 panic!(
425 "Expected ServiceInitializationFailed error, got: {:?}",
426 error
427 );
428 }
429 }
430}