elif_core/container/
lifecycle.rs

1use async_trait::async_trait;
2use std::sync::Arc;
3
4use crate::errors::CoreError;
5
6/// Trait for services that need async initialization
7#[async_trait]
8pub trait AsyncInitializable: Send + Sync {
9    /// Initialize the service asynchronously
10    async fn initialize(&self) -> Result<(), CoreError>;
11
12    /// Check if the service is initialized
13    fn is_initialized(&self) -> bool {
14        true // Default implementation assumes immediate initialization
15    }
16}
17
18/// Trait for services that need proper disposal/cleanup
19#[async_trait]
20pub trait Disposable: Send + Sync {
21    /// Dispose of the service and clean up resources
22    async fn dispose(&self) -> Result<(), CoreError>;
23}
24
25/// Combined trait for services that support both async initialization and disposal
26#[async_trait]
27pub trait LifecycleManaged: AsyncInitializable + Disposable {}
28
29// Blanket implementation for types that implement both traits
30impl<T> LifecycleManaged for T where T: AsyncInitializable + Disposable {}
31
32/// Service lifecycle state
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ServiceState {
35    /// Service is registered but not yet created
36    Registered,
37    /// Service instance is created but not initialized
38    Created,
39    /// Service is initialized and ready for use
40    Initialized,
41    /// Service is being disposed
42    Disposing,
43    /// Service has been disposed
44    Disposed,
45}
46
47/// Service lifecycle manager that tracks initialization and disposal
48pub struct ServiceLifecycleManager {
49    /// Services that need async initialization
50    initializable_services: Vec<Arc<dyn AsyncInitializable>>,
51    /// Services that need disposal (in reverse order of creation)
52    disposable_services: Vec<Arc<dyn Disposable>>,
53    /// Service type names for initializable services (parallel to initializable_services)
54    initializable_service_types: Vec<String>,
55    /// Current state of the lifecycle manager
56    state: ServiceState,
57    /// Optional handle for background disposal task
58    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    /// Create a new service lifecycle manager
77    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    /// Add a service that needs async initialization
88    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    /// Add a service that needs disposal
95    pub fn add_disposable<T: Disposable + 'static>(&mut self, service: Arc<T>) {
96        self.disposable_services.push(service);
97    }
98
99    /// Add a service that needs both initialization and disposal
100    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    /// Initialize all registered services
109    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        // Initialize services in registration order
119        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    /// Initialize services with timeout
140    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    /// Dispose all services in reverse order
158    pub async fn dispose_all(&mut self) -> Result<(), CoreError> {
159        if self.state == ServiceState::Disposed || self.state == ServiceState::Disposing {
160            return Ok(()); // Already disposed or disposing
161        }
162
163        self.state = ServiceState::Disposing;
164
165        // Dispose services in reverse order (LIFO)
166        for service in self.disposable_services.iter().rev() {
167            if let Err(e) = service.dispose().await {
168                // TODO: Use a proper logging facade like tracing::error! or collect errors.
169                eprintln!("Error disposing service: {:?}", e);
170            }
171        }
172
173        self.state = ServiceState::Disposed;
174        self.disposal_handle = None; // Clear any handle
175        Ok(())
176    }
177
178    /// Schedule disposal in the background (non-blocking)
179    /// This is useful when you can't await in the current context (like Drop)
180    pub fn schedule_disposal(&mut self) {
181        if self.is_disposed() || self.disposal_handle.is_some() {
182            return; // Already disposed or disposal scheduled
183        }
184
185        // Take ownership of the services to dispose
186        let services = std::mem::take(&mut self.disposable_services);
187        self.state = ServiceState::Disposing;
188
189        // Spawn a background task to handle disposal
190        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    /// Wait for any scheduled disposal to complete
202    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    /// Get the current lifecycle state
214    pub fn state(&self) -> ServiceState {
215        self.state
216    }
217
218    /// Check if all services are initialized
219    pub fn is_initialized(&self) -> bool {
220        self.state == ServiceState::Initialized
221    }
222
223    /// Check if services are disposed
224    pub fn is_disposed(&self) -> bool {
225        self.state == ServiceState::Disposed
226    }
227
228    /// Get the number of services requiring initialization
229    pub fn initializable_count(&self) -> usize {
230        self.initializable_services.len()
231    }
232
233    /// Get the number of services requiring disposal
234    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            // Check if we're in a tokio runtime context
249            if let Ok(_handle) = tokio::runtime::Handle::try_current() {
250                // We're in a tokio context, schedule disposal
251                self.schedule_disposal();
252            } else {
253                // No tokio runtime available, log a warning
254                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                // Simulate slow initialization
336                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        // Should timeout
347        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        // Schedule disposal in background
365        manager.schedule_disposal();
366
367        // Wait for it to complete
368        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            // Drop the manager without calling dispose_all()
384            // It should schedule disposal automatically
385        }
386
387        // Give background task time to run
388        tokio::time::sleep(std::time::Duration::from_millis(10)).await;
389
390        // Service should be disposed by Drop
391        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        // Should fail with proper service type name
414        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            // Check that it contains the actual service type name
421            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}