hypercall/shared/background_task.rs
1//! Trait for standardized background task spawning.
2//!
3//! This module provides the `BackgroundTask` trait that formalizes how
4//! background services are spawned and integrated with shutdown handling.
5//!
6//! # Example
7//! ```ignore
8//! use crate::shared::background_task::BackgroundTask;
9//! use crate::shared::task_group::TaskGroup;
10//! use crate::shared::shutdown::Shutdown;
11//!
12//! struct MyService {
13//! // ... service state
14//! }
15//!
16//! impl BackgroundTask for MyService {
17//! const NAME: &'static str = "MyService";
18//!
19//! fn spawn(self, group: &mut TaskGroup, shutdown: Shutdown) {
20//! let mut shutdown_rx = shutdown.subscribe();
21//! group.spawn(Self::NAME, async move {
22//! loop {
23//! tokio::select! {
24//! _ = shutdown_rx.recv() => break,
25//! // ... do work
26//! }
27//! }
28//! Ok(())
29//! });
30//! }
31//! }
32//! ```
33
34use super::shutdown::Shutdown;
35use super::task_group::TaskGroup;
36
37/// Trait for background services that can be spawned into a TaskGroup.
38///
39/// Implement this trait to standardize how services are spawned and
40/// how they integrate with the shutdown mechanism.
41pub trait BackgroundTask: Send + 'static {
42 /// A static name for this task, used for logging and error reporting.
43 const NAME: &'static str;
44
45 /// Spawn this task into the given TaskGroup.
46 ///
47 /// The implementation should:
48 /// 1. Subscribe to the shutdown signal if needed
49 /// 2. Call `group.spawn(Self::NAME, ...)` with a future that:
50 /// - Respects shutdown signals via `tokio::select!`
51 /// - Returns `Ok(())` on successful completion
52 /// - Returns `Err(...)` on failure
53 fn spawn(self, group: &mut TaskGroup, shutdown: Shutdown);
54}
55
56/// Extension trait for spawning BackgroundTask implementations.
57pub trait TaskGroupExt {
58 /// Spawn a BackgroundTask into this group.
59 fn spawn_task<T: BackgroundTask>(&mut self, task: T, shutdown: Shutdown);
60}
61
62impl TaskGroupExt for TaskGroup {
63 fn spawn_task<T: BackgroundTask>(&mut self, task: T, shutdown: Shutdown) {
64 task.spawn(self, shutdown);
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::sync::atomic::{AtomicBool, Ordering};
72 use std::sync::Arc;
73 use std::time::Duration;
74
75 struct TestService {
76 started: Arc<AtomicBool>,
77 }
78
79 impl BackgroundTask for TestService {
80 const NAME: &'static str = "TestService";
81
82 fn spawn(self, group: &mut TaskGroup, shutdown: Shutdown) {
83 let mut shutdown_rx = shutdown.subscribe();
84 let started = self.started;
85
86 group.spawn(Self::NAME, async move {
87 started.store(true, Ordering::SeqCst);
88 let _ = shutdown_rx.recv().await;
89 Ok(())
90 });
91 }
92 }
93
94 #[tokio::test]
95 async fn test_background_task_trait() {
96 let shutdown = Shutdown::new();
97 let mut group = TaskGroup::new();
98
99 let started = Arc::new(AtomicBool::new(false));
100 let service = TestService {
101 started: started.clone(),
102 };
103
104 service.spawn(&mut group, shutdown.clone());
105
106 // Give task time to start
107 tokio::time::sleep(Duration::from_millis(10)).await;
108 assert!(started.load(Ordering::SeqCst));
109
110 // Shutdown
111 let result = group
112 .shutdown_and_join(&shutdown, Duration::from_secs(1))
113 .await;
114 assert!(result.is_ok());
115 }
116
117 #[tokio::test]
118 async fn test_task_group_ext() {
119 let shutdown = Shutdown::new();
120 let mut group = TaskGroup::new();
121
122 let started = Arc::new(AtomicBool::new(false));
123 let service = TestService {
124 started: started.clone(),
125 };
126
127 // Use the extension trait
128 group.spawn_task(service, shutdown.clone());
129
130 tokio::time::sleep(Duration::from_millis(10)).await;
131 assert!(started.load(Ordering::SeqCst));
132
133 let result = group
134 .shutdown_and_join(&shutdown, Duration::from_secs(1))
135 .await;
136 assert!(result.is_ok());
137 }
138}