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}