sz_rust_core/runtime/
spawn.rs1use std::future::Future;
20use std::time::Duration;
21
22use tokio_util::sync::CancellationToken;
23
24pub fn spawn_with_token<F, T>(token: CancellationToken, future: F) -> tokio::task::JoinHandle<T>
50where
51 F: Future<Output = T> + Send + 'static,
52 T: Send + 'static,
53{
54 tokio::spawn(async move {
55 let _ = token; future.await
57 })
58}
59
60pub fn spawn_after<F, T>(delay: Duration, future: F) -> tokio::task::JoinHandle<T>
64where
65 F: Future<Output = T> + Send + 'static,
66 T: Send + 'static,
67{
68 tokio::spawn(async move {
69 tokio::time::sleep(delay).await;
70 future.await
71 })
72}
73
74pub fn spawn_tick<F>(
82 interval_ms: u64,
83 token: CancellationToken,
84 mut future: F,
85) -> tokio::task::JoinHandle<()>
86where
87 F: FnMut() + Send + 'static,
88{
89 tokio::spawn(async move {
90 let mut ticker = tokio::time::interval(Duration::from_millis(interval_ms));
91 loop {
92 tokio::select! {
93 _ = token.cancelled() => break,
94 _ = ticker.tick() => future(),
95 }
96 }
97 })
98}
99
100pub fn spawn_with_timeout<F, T>(
104 timeout: Duration,
105 future: F,
106) -> tokio::task::JoinHandle<Result<T, TimeoutError>>
107where
108 F: Future<Output = T> + Send + 'static,
109 T: Send + 'static,
110{
111 tokio::spawn(async move {
112 match tokio::time::timeout(timeout, future).await {
113 Ok(result) => Ok(result),
114 Err(_) => Err(TimeoutError),
115 }
116 })
117}
118
119pub async fn with_timeout<F, T>(future: F) -> Result<T, TimeoutError>
131where
132 F: Future<Output = T>,
133{
134 tokio::time::timeout(DEFAULT_IO_TIMEOUT, future)
135 .await
136 .map_err(|_| TimeoutError)
137}
138
139const DEFAULT_IO_TIMEOUT: Duration = Duration::from_secs(5);
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
144#[error("task timed out")]
145pub struct TimeoutError;
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use std::sync::atomic::{AtomicUsize, Ordering};
151 use std::sync::Arc;
152 use std::time::Instant;
153
154 #[tokio::test]
155 async fn test_spawn_with_token_basic() {
156 let token = CancellationToken::new();
157 let handle = spawn_with_token(token, async { 42 });
158 assert_eq!(handle.await.unwrap(), 42);
159 }
160
161 #[tokio::test]
162 async fn test_spawn_with_token_cancellation() {
163 let token = CancellationToken::new();
164 let token_clone = token.clone();
165 let token_for_closure = token.clone();
166 let handle = spawn_with_token(token_clone, async move {
167 token_for_closure.cancelled().await;
168 99
169 });
170 token.cancel();
171 assert_eq!(handle.await.unwrap(), 99);
172 }
173
174 #[tokio::test]
175 async fn test_spawn_after_basic() {
176 let start = Instant::now();
177 let handle = spawn_after(Duration::from_millis(50), async { 7 });
178 let result = handle.await.unwrap();
179 assert_eq!(result, 7);
180 assert!(start.elapsed() >= Duration::from_millis(40));
181 }
182
183 #[tokio::test]
184 async fn test_spawn_after_zero_delay() {
185 let handle = spawn_after(Duration::from_millis(0), async { 1 });
186 assert_eq!(handle.await.unwrap(), 1);
187 }
188
189 #[tokio::test]
190 async fn test_spawn_tick_fires_multiple_times() {
191 let counter = Arc::new(AtomicUsize::new(0));
192 let token = CancellationToken::new();
193 let counter_clone = counter.clone();
194 let handle = spawn_tick(10, token.clone(), move || {
195 counter_clone.fetch_add(1, Ordering::SeqCst);
196 });
197
198 tokio::time::sleep(Duration::from_millis(100)).await;
200 token.cancel();
201 let _ = handle.await;
202
203 assert!(counter.load(Ordering::SeqCst) >= 1);
205 }
206
207 #[tokio::test]
208 async fn test_spawn_tick_stops_on_cancel() {
209 let counter = Arc::new(AtomicUsize::new(0));
210 let token = CancellationToken::new();
211 let counter_clone = counter.clone();
212 let handle = spawn_tick(10, token.clone(), move || {
213 counter_clone.fetch_add(1, Ordering::SeqCst);
214 });
215
216 tokio::time::sleep(Duration::from_millis(30)).await;
217 token.cancel();
218 let _ = handle.await;
219 let count_after_cancel = counter.load(Ordering::SeqCst);
220
221 tokio::time::sleep(Duration::from_millis(50)).await;
223 assert_eq!(counter.load(Ordering::SeqCst), count_after_cancel);
224 }
225
226 #[tokio::test]
227 async fn test_spawn_with_timeout_success() {
228 let handle = spawn_with_timeout(Duration::from_millis(100), async { 42 });
229 assert_eq!(handle.await.unwrap().unwrap(), 42);
230 }
231
232 #[tokio::test]
233 async fn test_spawn_with_timeout_failure() {
234 let handle = spawn_with_timeout(Duration::from_millis(10), async {
235 tokio::time::sleep(Duration::from_millis(100)).await;
236 42
237 });
238 let result = handle.await.unwrap();
239 assert!(result.is_err());
240 assert_eq!(result.unwrap_err(), TimeoutError);
241 }
242
243 #[tokio::test]
244 async fn test_spawn_with_timeout_zero_timeout() {
245 let handle = spawn_with_timeout(Duration::from_millis(0), async { 1 });
247 let result = handle.await.unwrap();
248 let _ = result;
250 }
251
252 #[tokio::test]
253 async fn test_timeout_error_display() {
254 let err = TimeoutError;
255 assert_eq!(format!("{}", err), "task timed out");
256 }
257
258 #[tokio::test]
259 async fn test_spawn_multiple_concurrent_tasks() {
260 let token1 = CancellationToken::new();
261 let token2 = CancellationToken::new();
262 let h1 = spawn_with_token(token1, async { 1 });
263 let h2 = spawn_with_token(token2, async { 2 });
264 assert_eq!(h1.await.unwrap(), 1);
265 assert_eq!(h2.await.unwrap(), 2);
266 }
267
268 #[tokio::test]
269 async fn test_spawn_tick_immediate_first_fire() {
270 let counter = Arc::new(AtomicUsize::new(0));
272 let token = CancellationToken::new();
273 let counter_clone = counter.clone();
274 let handle = spawn_tick(1, token.clone(), move || {
275 counter_clone.fetch_add(1, Ordering::SeqCst);
276 });
277
278 let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
280 while counter.load(Ordering::SeqCst) < 1 {
281 tokio::time::sleep(Duration::from_millis(5)).await;
282 if tokio::time::Instant::now() > deadline {
283 token.cancel();
284 let _ = handle.await;
285 panic!("spawn_tick 首次 tick 未在 5s 内触发");
286 }
287 }
288 token.cancel();
289 let _ = handle.await;
290 assert!(
291 counter.load(Ordering::SeqCst) >= 1,
292 "spawn_tick 首次 tick 应在创建后立即触发,实际计数为 {}",
293 counter.load(Ordering::SeqCst)
294 );
295 }
296}