scuffle_bootstrap/
service.rs

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