ferrous_di/
prewarm.rs

1//! Pre-warm and readiness functionality for deterministic agent startup.
2//!
3//! This module provides infrastructure to pre-initialize services during
4//! application startup, eliminating cold-start penalties during agent execution.
5
6use std::any::TypeId;
7use std::collections::HashSet;
8use crate::{Key, ServiceProvider};
9
10/// Trait for services that can perform readiness checks.
11///
12/// Implement this trait on services that need to perform initialization
13/// or health checks during the pre-warm phase. This is particularly useful
14/// for services that need to:
15/// - Establish database connections
16/// - Load machine learning models
17/// - Authenticate with external APIs  
18/// - Pre-populate caches
19/// - Validate configuration
20///
21/// # Examples
22///
23/// ```
24/// use ferrous_di::ReadyCheck;
25/// use async_trait::async_trait;
26///
27/// struct DatabaseService {
28///     connection_string: String,
29/// }
30///
31/// #[async_trait]
32/// impl ReadyCheck for DatabaseService {
33///     async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
34///         // Perform connection test
35///         println!("Testing database connection: {}", self.connection_string);
36///         // In real implementation: test actual connection
37///         Ok(())
38///     }
39/// }
40/// ```
41#[async_trait::async_trait]
42pub trait ReadyCheck: Send + Sync {
43    /// Performs readiness check for this service.
44    ///
45    /// This method is called during the pre-warm phase to verify that
46    /// the service is ready for use. It should perform any necessary
47    /// initialization and return an error if the service is not ready.
48    ///
49    /// # Returns
50    ///
51    /// * `Ok(())` if the service is ready
52    /// * `Err(error)` if initialization failed or the service is not ready
53    async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
54}
55
56/// Collection of service types to pre-warm during startup.
57#[derive(Default)]
58pub(crate) struct PrewarmSet {
59    /// Set of type IDs to pre-warm
60    types: HashSet<TypeId>,
61    /// Set of trait names to pre-warm
62    traits: HashSet<&'static str>,
63}
64
65impl PrewarmSet {
66    /// Creates a new empty prewarm set.
67    pub(crate) fn new() -> Self {
68        Self {
69            types: HashSet::new(),
70            traits: HashSet::new(),
71        }
72    }
73
74    /// Adds a concrete type to the prewarm set.
75    pub(crate) fn add_type<T: 'static + Send + Sync>(&mut self) {
76        self.types.insert(TypeId::of::<T>());
77    }
78
79    /// Adds a trait to the prewarm set.
80    pub(crate) fn add_trait<T: ?Sized + 'static + Send + Sync>(&mut self) {
81        self.traits.insert(std::any::type_name::<T>());
82    }
83
84    /// Returns true if any services are marked for prewarming.
85    #[allow(dead_code)]
86    pub(crate) fn has_services(&self) -> bool {
87        !self.types.is_empty() || !self.traits.is_empty()
88    }
89
90    /// Gets all service keys that should be prewarmed.
91    #[allow(dead_code)]
92    pub(crate) fn get_keys(&self) -> Vec<Key> {
93        let mut keys = Vec::new();
94
95        // Add concrete type keys
96        for _type_id in &self.types {
97            // We need to reconstruct the type name from TypeId
98            // This is a limitation - we'll need to store both TypeId and name
99            // For now, we'll just document this limitation
100        }
101
102        // Add trait keys
103        for trait_name in &self.traits {
104            keys.push(Key::Trait(trait_name));
105        }
106
107        keys
108    }
109}
110
111/// Readiness check result for a single service.
112pub struct ReadinessResult {
113    /// The service key that was checked
114    pub key: Key,
115    /// Whether the readiness check passed
116    pub success: bool,
117    /// Error message if the check failed
118    pub error: Option<String>,
119    /// Time taken for the readiness check
120    pub duration: std::time::Duration,
121}
122
123impl ReadinessResult {
124    /// Creates a successful readiness result.
125    pub fn success(key: Key, duration: std::time::Duration) -> Self {
126        Self {
127            key,
128            success: true,
129            error: None,
130            duration,
131        }
132    }
133
134    /// Creates a failed readiness result.
135    pub fn failure(key: Key, error: String, duration: std::time::Duration) -> Self {
136        Self {
137            key,
138            success: false,
139            error: Some(error),
140            duration,
141        }
142    }
143}
144
145/// Overall readiness check results.
146pub struct ReadinessReport {
147    /// Individual service results
148    pub services: Vec<ReadinessResult>,
149    /// Total time taken for all checks
150    pub total_duration: std::time::Duration,
151}
152
153impl ReadinessReport {
154    /// Returns true if all readiness checks passed.
155    pub fn all_ready(&self) -> bool {
156        self.services.iter().all(|r| r.success)
157    }
158
159    /// Returns the number of services that passed readiness checks.
160    pub fn ready_count(&self) -> usize {
161        self.services.iter().filter(|r| r.success).count()
162    }
163
164    /// Returns the number of services that failed readiness checks.
165    pub fn failed_count(&self) -> usize {
166        self.services.iter().filter(|r| !r.success).count()
167    }
168
169    /// Gets all failed services.
170    pub fn failures(&self) -> Vec<&ReadinessResult> {
171        self.services.iter().filter(|r| !r.success).collect()
172    }
173}
174
175impl ServiceProvider {
176    /// Performs readiness checks on all prewarmed services.
177    ///
178    /// This method resolves all services marked for prewarming and runs
179    /// readiness checks on those that implement `ReadyCheck`. This is
180    /// typically called during application startup to ensure all critical
181    /// services are ready before accepting requests.
182    ///
183    /// # Performance
184    ///
185    /// Readiness checks are run in parallel with a configurable concurrency
186    /// limit to balance startup time with resource usage.
187    ///
188    /// # Examples
189    ///
190    /// ```no_run
191    /// use ferrous_di::{ServiceCollection, ReadyCheck};
192    /// use async_trait::async_trait;
193    /// use std::sync::Arc;
194    ///
195    /// struct DatabaseService;
196    /// #[async_trait]
197    /// impl ReadyCheck for DatabaseService {
198    ///     async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
199    ///         // Test database connection
200    ///         Ok(())
201    ///     }
202    /// }
203    ///
204    /// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
205/// let mut services = ServiceCollection::new();
206/// services.add_singleton(DatabaseService);
207/// services.prewarm::<DatabaseService>();
208///
209/// let provider = services.build();
210/// let report = provider.ready().await?;
211///
212/// if report.all_ready() {
213///     println!("All services ready! Starting application...");
214/// } else {
215///     eprintln!("Some services failed readiness checks:");
216///     for failure in report.failures() {
217///         eprintln!("  {}: {}", 
218///             failure.key.display_name(), 
219///             failure.error.as_deref().unwrap_or("Unknown error"));
220///     }
221///     std::process::exit(1);
222/// }
223/// # Ok(())
224/// # }
225    /// ```
226    pub async fn ready(&self) -> Result<ReadinessReport, Box<dyn std::error::Error + Send + Sync>> {
227        // TODO: This is a placeholder implementation
228        // We need to integrate with the ServiceCollection's prewarm set
229        // and actually resolve and check the services
230        
231        let start = std::time::Instant::now();
232        let services = Vec::new(); // Empty for now
233        let total_duration = start.elapsed();
234
235        Ok(ReadinessReport {
236            services,
237            total_duration,
238        })
239    }
240}