sz_rust_orm_facade/
pool_warmer.rs1use std::future::Future;
19use std::pin::Pin;
20use std::sync::Arc;
21use std::time::Duration;
22
23#[derive(Debug, thiserror::Error)]
25pub enum WarmupError {
26 #[error("connection failed: {0}")]
28 ConnectionFailed(String),
29 #[error("warmup timeout after {0:?}")]
31 Timeout(Duration),
32 #[error("partial warmup failure: {succeeded}/{total} succeeded")]
34 PartialFailure { succeeded: u32, total: u32 },
35}
36
37pub type ConnectFn =
39 Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<(), WarmupError>> + Send>> + Send + Sync>;
40
41pub struct PoolWarmer {
46 warmup_count: u32,
48 connect_fn: ConnectFn,
50 connect_timeout: Duration,
52}
53
54impl PoolWarmer {
55 pub fn new<F, Fut>(warmup_count: u32, connect_fn: F) -> Self
60 where
61 F: Fn() -> Fut + Send + Sync + 'static,
62 Fut: Future<Output = Result<(), WarmupError>> + Send + 'static,
63 {
64 Self {
65 warmup_count,
66 connect_fn: Arc::new(move || Box::pin(connect_fn())),
67 connect_timeout: Duration::from_secs(10),
68 }
69 }
70
71 pub fn with_timeout(mut self, timeout: Duration) -> Self {
73 self.connect_timeout = timeout;
74 self
75 }
76
77 pub fn warmup_count(&self) -> u32 {
79 self.warmup_count
80 }
81
82 pub async fn warm(&self) -> Result<(), WarmupError> {
87 if self.warmup_count == 0 {
88 return Ok(());
89 }
90
91 let mut handles = Vec::with_capacity(self.warmup_count as usize);
92 for _ in 0..self.warmup_count {
93 let connect_fn = self.connect_fn.clone();
94 let timeout = self.connect_timeout;
95 handles.push(tokio::spawn(async move {
96 tokio::time::timeout(timeout, connect_fn()).await
97 }));
98 }
99
100 let mut succeeded = 0u32;
101 let mut last_error = String::new();
102 for handle in handles {
103 match handle.await {
104 Ok(Ok(Ok(()))) => succeeded += 1,
105 Ok(Ok(Err(e))) => last_error = e.to_string(),
106 Ok(Err(_)) => last_error = "timeout".to_string(),
107 Err(e) => last_error = e.to_string(),
108 }
109 }
110
111 if succeeded == self.warmup_count {
112 Ok(())
113 } else if succeeded > 0 {
114 Err(WarmupError::PartialFailure {
115 succeeded,
116 total: self.warmup_count,
117 })
118 } else {
119 Err(WarmupError::ConnectionFailed(last_error))
120 }
121 }
122}
123
124impl std::fmt::Debug for PoolWarmer {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(
127 f,
128 "PoolWarmer {{ warmup_count: {}, timeout: {:?} }}",
129 self.warmup_count, self.connect_timeout
130 )
131 }
132}
133
134#[cfg(test)]
139mod tests {
140 use super::*;
141 use std::sync::atomic::{AtomicU32, Ordering};
142
143 #[tokio::test]
144 async fn test_pool_warmer_success() {
145 let counter = Arc::new(AtomicU32::new(0));
146 let counter_clone = counter.clone();
147 let warmer = PoolWarmer::new(5, move || {
148 let c = counter_clone.clone();
149 async move {
150 c.fetch_add(1, Ordering::Relaxed);
151 Ok(())
152 }
153 });
154 let result = warmer.warm().await;
155 assert!(result.is_ok());
156 assert_eq!(counter.load(Ordering::Relaxed), 5);
157 }
158
159 #[tokio::test]
160 async fn test_pool_warmer_zero_count() {
161 let warmer = PoolWarmer::new(0, || async { Ok(()) });
162 let result = warmer.warm().await;
163 assert!(result.is_ok());
164 }
165
166 #[tokio::test]
167 async fn test_pool_warmer_partial_failure() {
168 let counter = Arc::new(AtomicU32::new(0));
169 let counter_clone = counter.clone();
170 let warmer = PoolWarmer::new(4, move || {
171 let c = counter_clone.clone();
172 async move {
173 let n = c.fetch_add(1, Ordering::Relaxed);
174 if n < 2 {
175 Ok(())
176 } else {
177 Err(WarmupError::ConnectionFailed("mock fail".to_string()))
178 }
179 }
180 });
181 let result = warmer.warm().await;
182 assert!(matches!(
183 result,
184 Err(WarmupError::PartialFailure {
185 succeeded: 2,
186 total: 4
187 })
188 ));
189 }
190
191 #[tokio::test]
192 async fn test_pool_warmer_all_fail() {
193 let warmer = PoolWarmer::new(3, || async {
194 Err(WarmupError::ConnectionFailed("mock fail".to_string()))
195 });
196 let result = warmer.warm().await;
197 assert!(matches!(result, Err(WarmupError::ConnectionFailed(_))));
198 }
199
200 #[tokio::test]
201 async fn test_pool_warmer_timeout() {
202 let warmer = PoolWarmer::new(1, || async {
203 tokio::time::sleep(Duration::from_secs(60)).await;
204 Ok(())
205 })
206 .with_timeout(Duration::from_millis(50));
207 let result = warmer.warm().await;
208 assert!(result.is_err());
209 }
210
211 #[test]
212 fn test_pool_warmer_warmup_count() {
213 let warmer = PoolWarmer::new(10, || async { Ok(()) });
214 assert_eq!(warmer.warmup_count(), 10);
215 }
216
217 #[test]
218 fn test_pool_warmer_debug() {
219 let warmer = PoolWarmer::new(5, || async { Ok(()) });
220 let s = format!("{warmer:?}");
221 assert!(s.contains("warmup_count: 5"));
222 }
223}