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    /// Note: Adding the
37    /// [`scuffle_signal::SignalSvc`](../../scuffle_signal/struct.SignalSvc.html)
38    /// service to the list of services when calling [`main`](crate::main) will
39    /// cancel the context as soon as a shutdown signal is received.
40    ///
41    /// # See Also
42    ///
43    /// - [`Context`](scuffle_context::Context)
44    /// - [`scuffle_signal::SignalSvc`](../../scuffle_signal/struct.SignalSvc.html)
45    fn run(
46        self,
47        global: Arc<Global>,
48        ctx: scuffle_context::Context,
49    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send + 'static {
50        let _ = global;
51        async move {
52            ctx.done().await;
53            Ok(())
54        }
55    }
56}
57
58impl<G, F, Fut> Service<G> for F
59where
60    F: FnOnce(Arc<G>, scuffle_context::Context) -> Fut + Send + Sync + 'static,
61    Fut: std::future::Future<Output = anyhow::Result<()>> + Send + 'static,
62{
63    fn run(
64        self,
65        global: Arc<G>,
66        ctx: scuffle_context::Context,
67    ) -> impl std::future::Future<Output = anyhow::Result<()>> + Send + 'static {
68        self(global, ctx)
69    }
70}
71
72pin_project_lite::pin_project! {
73    /// A future that can be named yielding the name and the result of the inner future.
74    #[must_use = "futures do nothing unless polled"]
75    pub struct NamedFuture<T> {
76        name: &'static str,
77        #[pin]
78        fut: T,
79    }
80}
81
82impl<T> NamedFuture<T> {
83    /// Create a new named future from the given name and future.
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!(
129            handler
130                .shutdown()
131                .with_timeout(tokio::time::Duration::from_millis(200))
132                .await
133                .is_ok()
134        );
135    }
136
137    #[tokio::test]
138    async fn future_service() {
139        let (ctx, handler) = scuffle_context::Context::new();
140        let global = Arc::new(());
141
142        let fut_fn = |_global: Arc<()>, _ctx: scuffle_context::Context| async { anyhow::Result::<()>::Ok(()) };
143        assert!(fut_fn.run(global, ctx).await.is_ok());
144
145        handler.cancel();
146        assert!(
147            handler
148                .shutdown()
149                .with_timeout(tokio::time::Duration::from_millis(200))
150                .await
151                .is_ok()
152        );
153    }
154
155    #[tokio::test]
156    async fn named_future() {
157        let named_fut = NamedFuture::new("test", async { 42 });
158        assert_eq!(named_fut.await, ("test", 42));
159    }
160}