Skip to main content

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}