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