diode_base/
daemon.rs

1use std::any::TypeId;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use async_trait::async_trait;
6use diode::{App, AppBuilder, StdError};
7use tokio::task::JoinSet;
8
9pub use tokio_util::sync::CancellationToken;
10
11use crate::defer;
12
13#[derive(Default)]
14struct DaemonRegistry {
15    daemons: HashMap<TypeId, Arc<dyn DynDaemon>>,
16}
17
18impl DaemonRegistry {
19    pub fn add_daemon<T>(&mut self, daemon: Arc<T>)
20    where
21        T: Daemon + 'static,
22    {
23        let type_id = TypeId::of::<T>();
24        self.daemons.insert(type_id, daemon);
25    }
26
27    pub fn has_daemon<T>(&self) -> bool
28    where
29        T: Daemon + 'static,
30    {
31        let type_id = TypeId::of::<T>();
32        self.daemons.contains_key(&type_id)
33    }
34
35    pub async fn run_daemons(
36        &self,
37        app: Arc<App>,
38        shutdown: CancellationToken,
39    ) -> Result<(), StdError> {
40        let span = tracing::info_span!("daemons");
41        let mut futures = JoinSet::new();
42        tracing::info!(parent: &span, "Daemons starting");
43        for daemon in self.daemons.values() {
44            let shutdown = shutdown.child_token();
45            let app = app.clone();
46            let daemon = daemon.clone();
47            futures.spawn(async move { daemon.run(&app, shutdown).await });
48        }
49        tracing::info!(parent: &span, "Daemons running");
50        defer! {
51            tracing::info!(parent: &span, "Daemons stopped");
52        };
53        let first_result = futures.join_next().await;
54        shutdown.cancel();
55        if let Some(result) = first_result {
56            result.map_err(Box::new)??;
57            while let Some(result) = futures.join_next().await {
58                result.map_err(Box::new)??;
59            }
60        }
61        Ok(())
62    }
63}
64
65pub trait Daemon: Send + Sync {
66    fn run(
67        &self,
68        app: &App,
69        shutdown: CancellationToken,
70    ) -> impl Future<Output = Result<(), StdError>> + Send {
71        let _ = app;
72        async move {
73            shutdown.cancelled_owned().await;
74            Ok(())
75        }
76    }
77}
78
79#[async_trait]
80trait DynDaemon: Send + Sync {
81    async fn run(&self, app: &App, shutdown: CancellationToken) -> Result<(), StdError>;
82}
83
84#[async_trait]
85impl<T> DynDaemon for T
86where
87    T: Daemon,
88{
89    async fn run(&self, app: &App, shutdown: CancellationToken) -> Result<(), StdError> {
90        self.run(app, shutdown).await
91    }
92}
93
94pub trait RunDaemonsExt {
95    fn run_daemons(
96        self,
97        shutdown: CancellationToken,
98    ) -> impl Future<Output = Result<(), StdError>> + Send;
99}
100
101impl RunDaemonsExt for App {
102    async fn run_daemons(self, shutdown: CancellationToken) -> Result<(), StdError> {
103        Arc::new(self).run_daemons(shutdown).await
104    }
105}
106
107impl RunDaemonsExt for Arc<App> {
108    async fn run_daemons(self, shutdown: CancellationToken) -> Result<(), StdError> {
109        match self.get_component_ref::<DaemonRegistry>() {
110            Some(v) => v.run_daemons(self.clone(), shutdown).await,
111            None => Ok(()),
112        }
113    }
114}
115
116pub trait AddDaemonExt {
117    fn add_daemon<T>(&mut self, daemon: impl Into<Arc<T>>) -> &mut Self
118    where
119        T: Daemon + 'static;
120
121    fn has_daemon<T>(&self) -> bool
122    where
123        T: Daemon + 'static;
124}
125
126impl AddDaemonExt for AppBuilder {
127    fn add_daemon<T>(&mut self, daemon: impl Into<Arc<T>>) -> &mut Self
128    where
129        T: Daemon + 'static,
130    {
131        if !self.has_component::<DaemonRegistry>() {
132            self.add_component(DaemonRegistry::default());
133        }
134        self.get_component_mut::<DaemonRegistry>()
135            .unwrap()
136            .add_daemon(daemon.into());
137        self
138    }
139
140    fn has_daemon<T>(&self) -> bool
141    where
142        T: Daemon + 'static,
143    {
144        self.get_component_ref::<DaemonRegistry>()
145            .is_some_and(|v| v.has_daemon::<T>())
146    }
147}