1use aster_forge_runtime::{
10 AsterRuntimeBuilder, AsterRuntimeComponent, RuntimeComponentBundle,
11 RuntimeComponentBundleRegistration, RuntimeComponentKind, RuntimeComponentRegistry,
12 runtime_component,
13};
14use tokio_util::sync::CancellationToken;
15
16use crate::{BACKGROUND_TASKS_COMPONENT, BackgroundTasks, RuntimeTaskDefinition};
17
18pub const BACKGROUND_TASKS_SHUTDOWN_PHASE: &str = "background_tasks";
20
21pub struct BackgroundTaskRuntimeComponent {
23 background_tasks: BackgroundTasks,
24}
25
26pub struct BackgroundTaskRuntimeDefinitionsComponent<Kind: 'static, PresentationCode: 'static> {
28 background_tasks: BackgroundTasks,
29 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
30}
31
32pub struct BackgroundTaskRuntimeComponentFromShutdown<F> {
34 spawn: F,
35}
36
37pub struct BackgroundTaskRuntimeDefinitionsComponentFromShutdown<
39 Kind: 'static,
40 PresentationCode: 'static,
41 F,
42> {
43 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
44 spawn: F,
45}
46
47impl BackgroundTaskRuntimeComponent {
48 pub const fn new(background_tasks: BackgroundTasks) -> Self {
50 Self { background_tasks }
51 }
52}
53
54impl<F> BackgroundTaskRuntimeComponentFromShutdown<F> {
55 pub const fn new(spawn: F) -> Self {
57 Self { spawn }
58 }
59}
60
61impl<Kind: 'static, PresentationCode: 'static>
62 BackgroundTaskRuntimeDefinitionsComponent<Kind, PresentationCode>
63{
64 pub const fn new(
66 background_tasks: BackgroundTasks,
67 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
68 ) -> Self {
69 Self {
70 background_tasks,
71 definitions,
72 }
73 }
74}
75
76impl<Kind: 'static, PresentationCode: 'static, F>
77 BackgroundTaskRuntimeDefinitionsComponentFromShutdown<Kind, PresentationCode, F>
78{
79 pub const fn new(
81 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
82 spawn: F,
83 ) -> Self {
84 Self { definitions, spawn }
85 }
86}
87
88impl RuntimeComponentBundle for BackgroundTaskRuntimeComponent {
89 fn register(self, registry: &mut RuntimeComponentRegistry) {
90 register_background_tasks_shutdown(registry, self.background_tasks);
91 }
92}
93
94impl<Kind: 'static, PresentationCode: 'static> RuntimeComponentBundle
95 for BackgroundTaskRuntimeDefinitionsComponent<Kind, PresentationCode>
96{
97 fn register(self, registry: &mut RuntimeComponentRegistry) {
98 register_background_task_definitions(registry, self.definitions);
99 register_background_tasks_shutdown(registry, self.background_tasks);
100 }
101}
102
103impl<S, F> AsterRuntimeComponent<S> for BackgroundTaskRuntimeComponentFromShutdown<F>
104where
105 F: FnOnce(CancellationToken) -> BackgroundTasks,
106{
107 type Output = AsterRuntimeBuilder<S>;
108
109 fn apply(self, builder: AsterRuntimeBuilder<S>) -> Self::Output {
110 let background_tasks = (self.spawn)(builder.shutdown_token().clone());
111 background_task_component(background_tasks).apply(builder)
112 }
113}
114
115impl<S, Kind, PresentationCode, F> AsterRuntimeComponent<S>
116 for BackgroundTaskRuntimeDefinitionsComponentFromShutdown<Kind, PresentationCode, F>
117where
118 Kind: Sync + 'static,
119 PresentationCode: Sync + 'static,
120 F: FnOnce(CancellationToken) -> BackgroundTasks,
121{
122 type Output = AsterRuntimeBuilder<S>;
123
124 fn apply(self, builder: AsterRuntimeBuilder<S>) -> Self::Output {
125 let background_tasks = (self.spawn)(builder.shutdown_token().clone());
126 background_task_component_with_definitions(background_tasks, self.definitions)
127 .apply(builder)
128 }
129}
130
131pub fn background_task_component(
133 background_tasks: BackgroundTasks,
134) -> RuntimeComponentBundleRegistration<BackgroundTaskRuntimeComponent> {
135 runtime_component(BackgroundTaskRuntimeComponent::new(background_tasks))
136}
137
138pub fn background_task_component_from_shutdown<F>(
144 spawn: F,
145) -> BackgroundTaskRuntimeComponentFromShutdown<F>
146where
147 F: FnOnce(CancellationToken) -> BackgroundTasks,
148{
149 BackgroundTaskRuntimeComponentFromShutdown::new(spawn)
150}
151
152pub fn background_task_component_with_definitions<Kind, PresentationCode>(
159 background_tasks: BackgroundTasks,
160 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
161) -> RuntimeComponentBundleRegistration<
162 BackgroundTaskRuntimeDefinitionsComponent<Kind, PresentationCode>,
163>
164where
165 Kind: Sync + 'static,
166 PresentationCode: Sync + 'static,
167{
168 runtime_component(BackgroundTaskRuntimeDefinitionsComponent::new(
169 background_tasks,
170 definitions,
171 ))
172}
173
174pub fn background_task_component_with_definitions_from_shutdown<Kind, PresentationCode, F>(
181 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
182 spawn: F,
183) -> BackgroundTaskRuntimeDefinitionsComponentFromShutdown<Kind, PresentationCode, F>
184where
185 Kind: Sync + 'static,
186 PresentationCode: Sync + 'static,
187 F: FnOnce(CancellationToken) -> BackgroundTasks,
188{
189 BackgroundTaskRuntimeDefinitionsComponentFromShutdown::new(definitions, spawn)
190}
191
192fn register_background_tasks_shutdown(
194 registry: &mut RuntimeComponentRegistry,
195 background_tasks: BackgroundTasks,
196) {
197 registry.component_shutdown_once(
198 BACKGROUND_TASKS_COMPONENT,
199 RuntimeComponentKind::Tasks,
200 BACKGROUND_TASKS_SHUTDOWN_PHASE,
201 None,
202 background_tasks,
203 |background_tasks| async move {
204 background_tasks.shutdown().await;
205 Ok(())
206 },
207 );
208}
209
210fn register_background_task_definitions<Kind, PresentationCode>(
212 registry: &mut RuntimeComponentRegistry,
213 definitions: &'static [RuntimeTaskDefinition<Kind, PresentationCode>],
214) {
215 for task in definitions {
216 registry.component_task(
217 BACKGROUND_TASKS_COMPONENT,
218 RuntimeComponentKind::Tasks,
219 task.wire_value,
220 task.display_name,
221 );
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use aster_forge_runtime::{RuntimeComponentBundle, RuntimeComponentKind};
228
229 use super::{
230 BACKGROUND_TASKS_COMPONENT, BACKGROUND_TASKS_SHUTDOWN_PHASE,
231 background_task_component_from_shutdown, background_task_component_with_definitions,
232 };
233 use crate::{BackgroundTasks, RuntimeTaskDefinition};
234
235 const TEST_DEFINITIONS: &[RuntimeTaskDefinition<TestRuntimeTask, TestPresentationCode>] = &[
236 RuntimeTaskDefinition {
237 kind: TestRuntimeTask::Cleanup,
238 wire_value: "cleanup",
239 display_name: "Cleanup",
240 presentation_code: TestPresentationCode::Cleanup,
241 },
242 RuntimeTaskDefinition {
243 kind: TestRuntimeTask::Dispatch,
244 wire_value: "dispatch",
245 display_name: "Dispatch",
246 presentation_code: TestPresentationCode::Dispatch,
247 },
248 ];
249
250 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
251 enum TestRuntimeTask {
252 Cleanup,
253 Dispatch,
254 }
255
256 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
257 enum TestPresentationCode {
258 Cleanup,
259 Dispatch,
260 }
261
262 #[test]
263 fn background_task_component_registers_definitions_and_shutdown() {
264 let registry = aster_forge_runtime::RuntimeComponentRegistry::configured(|registry| {
265 background_task_component_with_definitions(BackgroundTasks::new(), TEST_DEFINITIONS)
266 .register(registry);
267 });
268
269 let descriptor = registry
270 .descriptor(BACKGROUND_TASKS_COMPONENT)
271 .expect("background task component should be registered");
272 assert_eq!(descriptor.kind, RuntimeComponentKind::Tasks);
273 assert_eq!(
274 descriptor
275 .shutdown
276 .first()
277 .expect("background task shutdown should be registered")
278 .phase_name,
279 BACKGROUND_TASKS_SHUTDOWN_PHASE
280 );
281 assert_eq!(
282 descriptor
283 .tasks
284 .iter()
285 .map(|task| (task.task_name, task.display_name))
286 .collect::<Vec<_>>(),
287 vec![("cleanup", "Cleanup"), ("dispatch", "Dispatch")]
288 );
289 }
290
291 #[tokio::test]
292 async fn background_task_component_from_shutdown_uses_runtime_shutdown_token() {
293 let observed = std::sync::Arc::new(std::sync::Mutex::new(false));
294 let observed_spawn = observed.clone();
295 let runtime = aster_forge_runtime::AsterRuntime::builder()
296 .component(aster_forge_runtime::RuntimeServiceComponent::new(
297 "test_service",
298 RuntimeComponentKind::Core,
299 async {},
300 Default::default(),
301 || async {},
302 ))
303 .component(background_task_component_from_shutdown(move |shutdown| {
304 let mut tasks = BackgroundTasks::with_shutdown_token(shutdown.clone());
305 let observed_task = observed_spawn.clone();
306 tasks.push(async move {
307 shutdown.cancelled().await;
308 match observed_task.lock() {
309 Ok(mut value) => *value = true,
310 Err(poisoned) => *poisoned.into_inner() = true,
311 }
312 });
313 tasks
314 }))
315 .build()
316 .expect("runtime should build with spawned background task component");
317
318 runtime
319 .run()
320 .await
321 .expect("runtime should shut down cleanly");
322 assert!(
323 *observed
324 .lock()
325 .expect("observed mutex should not be poisoned")
326 );
327 }
328}