1use std::any::{TypeId, type_name};
2use std::collections::HashSet;
3use std::marker::PhantomData;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use diode::{
8 AddServiceExt as _, App, AppBuilder, AppContext, Dependencies, Plugin, Service,
9 ServiceDependencyExt as _, StdError,
10};
11use tokio::task::JoinSet;
12
13pub use tokio_util::sync::CancellationToken;
15
16use crate::defer;
17
18#[derive(Default)]
19struct DaemonRegistry {
20 daemons: Vec<Arc<dyn DynDaemon>>,
21 types: HashSet<TypeId>,
22}
23
24impl DaemonRegistry {
25 pub fn add_daemon<T>(&mut self, daemon: Arc<T>)
26 where
27 T: Daemon + 'static,
28 {
29 if !self.types.insert(TypeId::of::<T>()) {
30 panic!("Daemon {} already added", type_name::<T>());
31 }
32 self.daemons.push(daemon);
33 }
34
35 pub fn has_daemon<T>(&self) -> bool
36 where
37 T: Daemon + 'static,
38 {
39 self.types.contains(&TypeId::of::<T>())
40 }
41
42 pub async fn run_daemons(
43 &self,
44 app: Arc<App>,
45 shutdown: CancellationToken,
46 ) -> Result<(), StdError> {
47 let span = tracing::info_span!("daemons");
48 let mut futures = JoinSet::new();
49 tracing::info!(parent: &span, "Daemons starting");
50 for daemon in self.daemons.iter() {
51 let shutdown = shutdown.child_token();
52 let app = app.clone();
53 let daemon = daemon.clone();
54 futures.spawn(async move { daemon.run(&app, shutdown).await });
55 }
56 tracing::info!(parent: &span, "Daemons running");
57 defer! {
58 tracing::info!(parent: &span, "Daemons stopped");
59 };
60 let first_result = futures.join_next().await;
61 shutdown.cancel();
62 if let Some(result) = first_result {
63 result.map_err(Box::new)??;
64 while let Some(result) = futures.join_next().await {
65 result.map_err(Box::new)??;
66 }
67 }
68 Ok(())
69 }
70}
71
72pub trait Daemon: Send + Sync {
86 fn run(
92 &self,
93 app: &App,
94 shutdown: CancellationToken,
95 ) -> impl Future<Output = Result<(), StdError>> + Send {
96 let _ = app;
97 async move {
98 shutdown.cancelled_owned().await;
99 Ok(())
100 }
101 }
102}
103
104#[async_trait]
105trait DynDaemon: Send + Sync {
106 async fn run(&self, app: &App, shutdown: CancellationToken) -> Result<(), StdError>;
107}
108
109#[async_trait]
110impl<T> DynDaemon for T
111where
112 T: Daemon,
113{
114 async fn run(&self, app: &App, shutdown: CancellationToken) -> Result<(), StdError> {
115 self.run(app, shutdown).await
116 }
117}
118
119pub trait RunDaemonsExt {
121 fn run_daemons(
132 self,
133 shutdown: CancellationToken,
134 ) -> impl Future<Output = Result<(), StdError>> + Send;
135}
136
137impl RunDaemonsExt for App {
138 async fn run_daemons(self, shutdown: CancellationToken) -> Result<(), StdError> {
139 Arc::new(self).run_daemons(shutdown).await
140 }
141}
142
143impl RunDaemonsExt for Arc<App> {
144 async fn run_daemons(self, shutdown: CancellationToken) -> Result<(), StdError> {
145 match self.get_component_ref::<DaemonRegistry>() {
146 Some(v) => v.run_daemons(self.clone(), shutdown).await,
147 None => Ok(()),
148 }
149 }
150}
151
152pub trait AddDaemonExt {
158 fn add_daemon<T>(&self, daemon: impl Into<Arc<T>>)
166 where
167 T: Daemon + 'static;
168
169 fn has_daemon<T>(&self) -> bool
171 where
172 T: Daemon + 'static;
173}
174
175impl AddDaemonExt for AppContext {
176 fn add_daemon<T>(&self, daemon: impl Into<Arc<T>>)
177 where
178 T: Daemon + 'static,
179 {
180 if !self.has_component::<DaemonRegistry>() {
181 self.add_component(DaemonRegistry::default());
182 }
183 self.get_component_mut::<DaemonRegistry>()
184 .unwrap()
185 .add_daemon(daemon.into());
186 }
187
188 fn has_daemon<T>(&self) -> bool
189 where
190 T: Daemon + 'static,
191 {
192 self.get_component_ref::<DaemonRegistry>()
193 .is_some_and(|registry| registry.has_daemon::<T>())
194 }
195}
196
197struct DaemonServiceProvider<T>(PhantomData<T>);
198
199impl<T> Plugin for DaemonServiceProvider<T>
200where
201 T: Service<Handle = Arc<T>> + Daemon + 'static,
202{
203 async fn build(&self, ctx: &AppContext) -> Result<(), StdError> {
204 let handle = ctx.get_component::<T::Handle>().unwrap();
205 ctx.add_daemon::<T>(handle);
206 Ok(())
207 }
208
209 fn dependencies(&self) -> Dependencies {
210 T::dependencies().service::<T>()
211 }
212}
213
214pub trait AddDaemonServiceExt {
220 fn add_daemon_service<T>(&mut self) -> &mut Self
231 where
232 T: Service<Handle = Arc<T>> + Daemon + 'static;
233
234 fn has_daemon_service<T>(&self) -> bool
236 where
237 T: Service<Handle = Arc<T>> + Daemon + 'static;
238}
239
240impl AddDaemonServiceExt for AppBuilder {
241 fn add_daemon_service<T>(&mut self) -> &mut Self
242 where
243 T: Service<Handle = Arc<T>> + Daemon + 'static,
244 {
245 if !self.has_service::<T>() {
246 self.add_service::<T>();
247 }
248 self.add_plugin(DaemonServiceProvider::<T>(PhantomData));
249 self
250 }
251
252 fn has_daemon_service<T>(&self) -> bool
253 where
254 T: Service<Handle = Arc<T>> + Daemon + 'static,
255 {
256 self.has_plugin::<DaemonServiceProvider<T>>()
257 }
258}