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
119#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
121#[error("task timed out")]
122pub struct TimeoutError;
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::sync::atomic::{AtomicUsize, Ordering};
128 use std::sync::Arc;
129 use std::time::Instant;
130
131 #[tokio::test]
132 async fn test_spawn_with_token_basic() {
133 let token = CancellationToken::new();
134 let handle = spawn_with_token(token, async { 42 });
135 assert_eq!(handle.await.unwrap(), 42);
136 }
137
138 #[tokio::test]
139 async fn test_spawn_with_token_cancellation() {
140 let token = CancellationToken::new();
141 let token_clone = token.clone();
142 let token_for_closure = token.clone();
143 let handle = spawn_with_token(token_clone, async move {
144 token_for_closure.cancelled().await;
145 99
146 });
147 token.cancel();
148 assert_eq!(handle.await.unwrap(), 99);
149 }
150
151 #[tokio::test]
152 async fn test_spawn_after_basic() {
153 let start = Instant::now();
154 let handle = spawn_after(Duration::from_millis(50), async { 7 });
155 let result = handle.await.unwrap();
156 assert_eq!(result, 7);
157 assert!(start.elapsed() >= Duration::from_millis(40));
158 }
159
160 #[tokio::test]
161 async fn test_spawn_after_zero_delay() {
162 let handle = spawn_after(Duration::from_millis(0), async { 1 });
163 assert_eq!(handle.await.unwrap(), 1);
164 }
165
166 #[tokio::test]
167 async fn test_spawn_tick_fires_multiple_times() {
168 let counter = Arc::new(AtomicUsize::new(0));
169 let token = CancellationToken::new();
170 let counter_clone = counter.clone();
171 let handle = spawn_tick(10, token.clone(), move || {
172 counter_clone.fetch_add(1, Ordering::SeqCst);
173 });
174
175 tokio::time::sleep(Duration::from_millis(100)).await;
177 token.cancel();
178 let _ = handle.await;
179
180 assert!(counter.load(Ordering::SeqCst) >= 1);
182 }
183
184 #[tokio::test]
185 async fn test_spawn_tick_stops_on_cancel() {
186 let counter = Arc::new(AtomicUsize::new(0));
187 let token = CancellationToken::new();
188 let counter_clone = counter.clone();
189 let handle = spawn_tick(10, token.clone(), move || {
190 counter_clone.fetch_add(1, Ordering::SeqCst);
191 });
192
193 tokio::time::sleep(Duration::from_millis(30)).await;
194 token.cancel();
195 let _ = handle.await;
196 let count_after_cancel = counter.load(Ordering::SeqCst);
197
198 tokio::time::sleep(Duration::from_millis(50)).await;
200 assert_eq!(counter.load(Ordering::SeqCst), count_after_cancel);
201 }
202
203 #[tokio::test]
204 async fn test_spawn_with_timeout_success() {
205 let handle = spawn_with_timeout(Duration::from_millis(100), async { 42 });
206 assert_eq!(handle.await.unwrap().unwrap(), 42);
207 }
208
209 #[tokio::test]
210 async fn test_spawn_with_timeout_failure() {
211 let handle = spawn_with_timeout(Duration::from_millis(10), async {
212 tokio::time::sleep(Duration::from_millis(100)).await;
213 42
214 });
215 let result = handle.await.unwrap();
216 assert!(result.is_err());
217 assert_eq!(result.unwrap_err(), TimeoutError);
218 }
219
220 #[tokio::test]
221 async fn test_spawn_with_timeout_zero_timeout() {
222 let handle = spawn_with_timeout(Duration::from_millis(0), async { 1 });
224 let result = handle.await.unwrap();
225 let _ = result;
227 }
228
229 #[tokio::test]
230 async fn test_timeout_error_display() {
231 let err = TimeoutError;
232 assert_eq!(format!("{}", err), "task timed out");
233 }
234
235 #[tokio::test]
236 async fn test_spawn_multiple_concurrent_tasks() {
237 let token1 = CancellationToken::new();
238 let token2 = CancellationToken::new();
239 let h1 = spawn_with_token(token1, async { 1 });
240 let h2 = spawn_with_token(token2, async { 2 });
241 assert_eq!(h1.await.unwrap(), 1);
242 assert_eq!(h2.await.unwrap(), 2);
243 }
244
245 #[tokio::test]
246 async fn test_spawn_tick_immediate_first_fire() {
247 let counter = Arc::new(AtomicUsize::new(0));
249 let token = CancellationToken::new();
250 let counter_clone = counter.clone();
251 let handle = spawn_tick(1, token.clone(), move || {
252 counter_clone.fetch_add(1, Ordering::SeqCst);
253 });
254
255 let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
257 while counter.load(Ordering::SeqCst) < 1 {
258 tokio::time::sleep(Duration::from_millis(5)).await;
259 if tokio::time::Instant::now() > deadline {
260 token.cancel();
261 let _ = handle.await;
262 panic!("spawn_tick 首次 tick 未在 5s 内触发");
263 }
264 }
265 token.cancel();
266 let _ = handle.await;
267 assert!(
268 counter.load(Ordering::SeqCst) >= 1,
269 "spawn_tick 首次 tick 应在创建后立即触发,实际计数为 {}",
270 counter.load(Ordering::SeqCst)
271 );
272 }
273}