scuffle_bootstrap/
service.rs1use 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
9pub trait Service<Global>: Send + Sync + 'static + Sized {
20 fn name(&self) -> Option<&'static str> {
22 None
23 }
24
25 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 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}