scuffle_bootstrap/
service.rs

1use std::pin::Pin;
2use std::sync::Arc;
3use std::task::{ready, Context, Poll};
4
5#[cfg(any(test, doctest))]
6#[doc(hidden)]
7pub use scuffle_signal::SignalSvc;
8
9/// A service that can be run.
10///
11/// This trait is used to define a service that can be run in parallel to other
12/// services.
13///
14/// # See Also
15///
16/// - [`Global`](crate::Global)
17/// - [`GlobalWithoutConfig`](crate::GlobalWithoutConfig)
18/// - [`main`](crate::main)
19pub trait Service<Global>: Send + Sync + 'static + Sized {
20    /// Returns the name of the service, if any.
21    fn name(&self) -> Option<&'static str> {
22        None
23    }
24
25    /// Initialize the service and return `Ok(true)` if the service should be
26    /// run.
27    fn enabled(&self, global: &Arc<Global>) -> impl std::future::Future<Output = anyhow::Result<bool>> + Send {
28        let _ = global;
29        std::future::ready(Ok(true))
30    }
31
32    /// Run the service.
33    /// This function should return a future that is pending as long as the
34    /// service is running. When the service finishes without any errors,
35    /// the future should resolve to `Ok(())`. As a best practice, the
36    /// service should stop as soon as the provided context is done.
37    ///
38    /// Note: Adding the
39    /// [`scuffle_signal::SignalSvc`](../../scuffle_signal/struct.SignalSvc.html)
40    /// service to the list of services when calling [`main`](crate::main) will
41    /// cancel the context as soon as a shutdown signal is received.
42    ///
43    /// # See Also
44    ///
45    /// - [`Context`](scuffle_context::Context)
46    /// - [`scuffle_signal::SignalSvc`](../../scuffle_signal/struct.SignalSvc.html)
47    fn run(
48        self,
49        global: Arc<Global>,
50        ctx: scuffle_context::Context,
51    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send + 'static {
52        let _ = global;
53        async move {
54            ctx.done().await;
55            Ok(())
56        }
57    }
58}
59
60impl<G, F, Fut> Service<G> for F
61where
62    F: FnOnce(Arc<G>, scuffle_context::Context) -> Fut + Send + Sync + 'static,
63    Fut: std::future::Future<Output = anyhow::Result<()>> + Send + 'static,
64{
65    fn run(
66        self,
67        global: Arc<G>,
68        ctx: scuffle_context::Context,
69    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send + 'static {
70        self(global, ctx)
71    }
72}
73
74pin_project_lite::pin_project! {
75    #[must_use = "futures do nothing unless polled"]
76    pub struct NamedFuture<T> {
77        name: &'static str,
78        #[pin]
79        fut: T,
80    }
81}
82
83impl<T> NamedFuture<T> {
84    pub fn new(name: &'static str, fut: T) -> Self {
85        Self { name, fut }
86    }
87}
88
89impl<T> std::future::Future for NamedFuture<T>
90where
91    T: std::future::Future,
92{
93    type Output = (&'static str, T::Output);
94
95    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
96        let this = self.project();
97        let res = ready!(this.fut.poll(cx));
98        Poll::Ready((this.name, res))
99    }
100}
101
102#[cfg(test)]
103#[cfg_attr(all(test, coverage_nightly), coverage(off))]
104mod tests {
105    use std::sync::Arc;
106
107    use scuffle_future_ext::FutureExt;
108
109    use super::{NamedFuture, Service};
110
111    struct DefaultService;
112
113    impl Service<()> for DefaultService {}
114
115    #[tokio::test]
116    async fn defaukt_service() {
117        let svc = DefaultService;
118        let global = Arc::new(());
119        let (ctx, handler) = scuffle_context::Context::new();
120
121        assert_eq!(svc.name(), None);
122        assert!(svc.enabled(&global).await.unwrap());
123
124        handler.cancel();
125
126        assert!(matches!(svc.run(global, ctx).await, Ok(())));
127
128        assert!(handler
129            .shutdown()
130            .with_timeout(tokio::time::Duration::from_millis(200))
131            .await
132            .is_ok());
133    }
134
135    #[tokio::test]
136    async fn future_service() {
137        let (ctx, handler) = scuffle_context::Context::new();
138        let global = Arc::new(());
139
140        let fut_fn = |_global: Arc<()>, _ctx: scuffle_context::Context| async { anyhow::Result::<()>::Ok(()) };
141        assert!(fut_fn.run(global, ctx).await.is_ok());
142
143        handler.cancel();
144        assert!(handler
145            .shutdown()
146            .with_timeout(tokio::time::Duration::from_millis(200))
147            .await
148            .is_ok());
149    }
150
151    #[tokio::test]
152    async fn named_future() {
153        let named_fut = NamedFuture::new("test", async { 42 });
154        assert_eq!(named_fut.await, ("test", 42));
155    }
156}