1use std::fmt;
5use std::panic;
6use std::time::Duration;
7
8use futures::future;
9use futures::future::BoxFuture;
10use futures::future::FutureExt;
11use tap::TapFallible;
12use tokio::signal;
13use tokio::task::JoinSet;
14use tokio::time::timeout;
15use tracing::error;
16use tracing::info;
17
18pub const GRACE: Duration = Duration::from_secs(30);
23
24#[must_use = "Dropping the service aborts all its tasks immediately"]
45#[derive(Default)]
46pub struct Service {
47 exits: Vec<BoxFuture<'static, ()>>,
49
50 fsts: JoinSet<anyhow::Result<()>>,
52
53 snds: JoinSet<anyhow::Result<()>>,
55}
56
57#[derive(thiserror::Error, Debug)]
58pub enum Error {
59 #[error("Service has been terminated gracefully")]
60 Terminated,
61
62 #[error("Service has been aborted due to ungraceful shutdown")]
63 Aborted,
64
65 #[error(transparent)]
66 Task(anyhow::Error),
67}
68
69impl Service {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn spawn(
81 mut self,
82 task: impl Future<Output = anyhow::Result<()>> + Send + 'static,
83 ) -> Self {
84 self.fsts.spawn(task.boxed());
86 self
87 }
88
89 pub fn spawn_aborting(
93 mut self,
94 task: impl Future<Output = anyhow::Result<()>> + Send + 'static,
95 ) -> Self {
96 let h = self.fsts.spawn(task.boxed());
97 self.with_shutdown_signal(async move { h.abort() })
98 }
99
100 pub fn with_shutdown_signal(mut self, exit: impl Future<Output = ()> + Send + 'static) -> Self {
108 self.exits.push(exit.boxed());
109 self
110 }
111
112 pub fn merge(mut self, mut other: Service) -> Self {
114 self.exits.extend(other.exits);
115
116 if !other.fsts.is_empty() {
117 self.fsts.spawn(async move { run(&mut other.fsts).await });
118 }
119
120 if !other.snds.is_empty() {
121 self.snds.spawn(async move { run(&mut other.snds).await });
122 }
123
124 self
125 }
126
127 pub fn attach(mut self, mut other: Service) -> Self {
131 self.exits.extend(other.exits);
132
133 if !other.fsts.is_empty() {
134 self.snds.spawn(async move { run(&mut other.fsts).await });
135 }
136
137 if !other.snds.is_empty() {
138 self.snds.spawn(async move { run(&mut other.snds).await });
139 }
140
141 self
142 }
143
144 pub async fn main(self) -> Result<(), Error> {
147 self.wait_for_shutdown(GRACE, terminate).await
148 }
149
150 async fn wait_for_shutdown<T: Future<Output = ()>>(
161 mut self,
162 grace: Duration,
163 mut terminate: impl FnMut() -> T,
164 ) -> Result<(), Error> {
165 let exec = tokio::select! {
166 res = self.join() => {
167 res.map_err(Error::Task)
168 }
169
170 _ = terminate() => {
171 info!("Termination received");
172 Err(Error::Terminated)
173 }
174 };
175
176 info!("Shutting down gracefully...");
177 tokio::select! {
178 res = timeout(grace, self.shutdown()) => {
179 match res {
180 Ok(Ok(())) => {},
181 Ok(Err(_)) => return Err(Error::Aborted),
182 Err(_) => {
183 error!("Grace period elapsed, aborting...");
184 return Err(Error::Aborted);
185 }
186 }
187 }
188
189 _ = terminate() => {
190 error!("Termination received during shutdown, aborting...");
191 return Err(Error::Aborted);
192 },
193 }
194
195 exec
196 }
197
198 pub async fn join(&mut self) -> anyhow::Result<()> {
204 tokio::select! {
205 res = run(&mut self.fsts) => {
206 res.tap_err(|e| error!("Primary task failure: {e:#}"))
207 },
208
209 res = run_secondary(&mut self.snds) => {
210 res.tap_err(|e| error!("Secondary task failure: {e:#}"))
211 }
212 }
213 }
214
215 pub async fn shutdown(mut self) -> Result<(), Error> {
220 for exit in self.exits {
221 exit.await;
222 }
223 if let Err(e) = future::try_join(run(&mut self.fsts), run(&mut self.snds)).await {
224 error!("Task failure during shutdown: {e:#}");
225 return Err(Error::Task(e));
226 }
227
228 Ok(())
229 }
230}
231
232unsafe impl Sync for Service {}
236
237impl fmt::Debug for Service {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 f.debug_struct("Service")
240 .field("exits", &self.exits.len())
241 .field("fsts", &self.fsts)
242 .field("snds", &self.snds)
243 .finish()
244 }
245}
246
247async fn run(tasks: &mut JoinSet<anyhow::Result<()>>) -> anyhow::Result<()> {
250 while let Some(res) = tasks.join_next().await {
251 match res {
252 Ok(Ok(())) => continue,
253 Ok(Err(e)) => return Err(e),
254
255 Err(e) => {
256 if e.is_panic() {
257 panic::resume_unwind(e.into_panic());
258 }
259 }
260 }
261 }
262
263 Ok(())
264}
265
266async fn run_secondary(tasks: &mut JoinSet<anyhow::Result<()>>) -> anyhow::Result<()> {
271 run(tasks).await?;
272 std::future::pending().await
273}
274
275pub async fn terminate() {
280 tokio::select! {
281 _ = signal::ctrl_c() => {},
282
283 _ = async {
284 #[cfg(unix)]
285 let _ = signal::unix::signal(signal::unix::SignalKind::terminate()).unwrap().recv().await;
286
287 #[cfg(not(unix))]
288 future::pending::<()>().await;
289 } => {}
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use std::sync::Arc;
296
297 use anyhow::bail;
298 use tokio::sync::Notify;
299 use tokio::sync::oneshot;
300
301 use super::*;
302
303 #[tokio::test]
304 async fn test_empty() {
305 Service::new()
307 .wait_for_shutdown(GRACE, std::future::pending)
308 .await
309 .unwrap();
310 }
311
312 #[tokio::test]
313 async fn test_empty_attach_merge() {
314 Service::new()
316 .attach(Service::new())
317 .merge(Service::new())
318 .wait_for_shutdown(GRACE, std::future::pending)
319 .await
320 .unwrap();
321 }
322
323 #[tokio::test]
324 async fn test_completion() {
325 let (atx, arx) = oneshot::channel::<()>();
326 let (btx, brx) = oneshot::channel::<()>();
327
328 let svc = Service::new().spawn(async move {
329 let _brx = brx;
330 Ok(arx.await?)
331 });
332
333 assert!(!btx.is_closed());
335
336 atx.send(()).unwrap();
339 svc.wait_for_shutdown(GRACE, std::future::pending)
340 .await
341 .unwrap();
342 assert!(btx.is_closed());
343 }
344
345 #[tokio::test]
346 async fn test_failure() {
347 let svc = Service::new().spawn(async move { bail!("boom") });
348 let res = svc.wait_for_shutdown(GRACE, std::future::pending).await;
349 assert!(matches!(res, Err(Error::Task(_))));
350 }
351
352 #[tokio::test]
353 #[should_panic]
354 async fn test_panic() {
355 let svc = Service::new().spawn(async move { panic!("boom") });
356 let _ = svc.wait_for_shutdown(GRACE, std::future::pending).await;
357 }
358
359 #[tokio::test]
360 async fn test_graceful_shutdown() {
361 let (atx, arx) = oneshot::channel::<()>();
362 let (btx, brx) = oneshot::channel::<()>();
363
364 let srx = Arc::new(Notify::new());
365 let stx = srx.clone();
366
367 let svc = Service::new()
368 .with_shutdown_signal(async move { atx.send(()).unwrap() })
369 .spawn(async move {
370 arx.await?;
371 btx.send(()).unwrap();
372 Ok(())
373 });
374
375 let handle =
377 tokio::spawn(svc.wait_for_shutdown(GRACE, move || srx.clone().notified_owned()));
378
379 stx.notify_one();
382 brx.await.unwrap();
383
384 let res = handle.await.unwrap();
386 assert!(matches!(res, Err(Error::Terminated)));
387 }
388
389 #[tokio::test]
390 async fn test_multiple_tasks() {
391 let (atx, arx) = oneshot::channel::<()>();
392 let (btx, brx) = oneshot::channel::<()>();
393 let (ctx, crx) = oneshot::channel::<()>();
394
395 let svc = Service::new()
398 .spawn(async move { Ok(arx.await?) })
399 .spawn(async move { Ok(brx.await?) })
400 .spawn(async move { Ok(crx.await?) });
401
402 let handle = tokio::spawn(svc.wait_for_shutdown(GRACE, std::future::pending));
403
404 atx.send(()).unwrap();
405 tokio::task::yield_now().await;
406
407 btx.send(()).unwrap();
408 tokio::task::yield_now().await;
409
410 ctx.send(()).unwrap();
411 handle.await.unwrap().unwrap();
412 }
413
414 #[tokio::test]
415 async fn test_multiple_task_failure() {
416 let (atx, arx) = oneshot::channel::<()>();
417
418 let svc = Service::new()
421 .spawn_aborting(async move { Ok(arx.await?) })
422 .spawn(async move { bail!("boom") });
423
424 let handle = tokio::spawn(svc.wait_for_shutdown(GRACE, std::future::pending));
425 let res = handle.await.unwrap();
426
427 assert!(matches!(res, Err(Error::Task(_))));
430 assert!(atx.is_closed());
431 }
432
433 #[tokio::test]
434 async fn test_secondary_stuck() {
435 let (atx, arx) = oneshot::channel::<()>();
436 let (btx, brx) = oneshot::channel::<()>();
437
438 let snd = Service::new().spawn_aborting(async move { Ok(brx.await?) });
440 let svc = Service::new()
441 .spawn(async move { Ok(arx.await?) })
442 .attach(snd);
443
444 let handle = tokio::spawn(svc.wait_for_shutdown(GRACE, std::future::pending));
445
446 atx.send(()).unwrap();
448 handle.await.unwrap().unwrap();
449 assert!(btx.is_closed());
450 }
451
452 #[tokio::test]
453 async fn test_secondary_complete() {
454 let (atx, arx) = oneshot::channel::<()>();
455 let (btx, brx) = oneshot::channel::<()>();
456 let (mut ctx, crx) = oneshot::channel::<()>();
457
458 let snd = Service::new().spawn(async move {
460 let _crx = crx;
461 brx.await?;
462 Ok(())
463 });
464
465 let svc = Service::new()
466 .spawn(async move { Ok(arx.await?) })
467 .attach(snd);
468
469 let handle = tokio::spawn(svc.wait_for_shutdown(GRACE, std::future::pending));
470
471 btx.send(()).unwrap();
473 ctx.closed().await;
474 tokio::task::yield_now().await;
475
476 atx.send(()).unwrap();
479 handle.await.unwrap().unwrap();
480 }
481
482 #[tokio::test]
483 async fn test_secondary_failure() {
484 let (atx, arx) = oneshot::channel::<()>();
485
486 let snd = Service::new().spawn(async move { bail!("boom") });
488 let svc = Service::new()
489 .spawn_aborting(async move { Ok(arx.await?) })
490 .attach(snd);
491
492 let res = svc.wait_for_shutdown(GRACE, std::future::pending).await;
495 assert!(matches!(res, Err(Error::Task(_))));
496 assert!(atx.is_closed());
497 }
498
499 #[tokio::test]
500 #[should_panic]
501 async fn test_secondary_panic() {
502 let (_atx, arx) = oneshot::channel::<()>();
503
504 let snd = Service::new().spawn(async move { panic!("boom") });
506 let svc = Service::new()
507 .spawn_aborting(async move { Ok(arx.await?) })
508 .attach(snd);
509
510 let _ = svc.wait_for_shutdown(GRACE, std::future::pending).await;
512 }
513
514 #[tokio::test]
515 async fn test_secondary_graceful_shutdown() {
516 let (atx, arx) = oneshot::channel::<()>();
517 let (btx, brx) = oneshot::channel::<()>();
518 let (ctx, crx) = oneshot::channel::<()>();
519
520 let srx = Arc::new(Notify::new());
521 let stx = srx.clone();
522
523 let snd = Service::new()
525 .with_shutdown_signal(async move { atx.send(()).unwrap() })
526 .spawn(async move {
527 let _crx = crx;
528 Ok(arx.await?)
529 });
530
531 let svc = Service::new()
533 .spawn_aborting(async move { Ok(brx.await?) })
534 .attach(snd);
535
536 let handle =
538 tokio::spawn(svc.wait_for_shutdown(GRACE, move || srx.clone().notified_owned()));
539
540 assert!(!btx.is_closed());
542 assert!(!ctx.is_closed());
543
544 stx.notify_one();
546 let res = handle.await.unwrap();
547 assert!(matches!(res, Err(Error::Terminated)));
548 assert!(btx.is_closed());
549 assert!(ctx.is_closed());
550 }
551
552 #[tokio::test]
553 async fn test_merge() {
554 let (atx, arx) = oneshot::channel::<()>();
555 let (btx, brx) = oneshot::channel::<()>();
556 let (ctx, crx) = oneshot::channel::<()>();
557 let (dtx, drx) = oneshot::channel::<()>();
558 let (etx, erx) = oneshot::channel::<()>();
559 let (ftx, frx) = oneshot::channel::<()>();
560
561 let srx = Arc::new(Notify::new());
562 let stx = srx.clone();
563
564 let a = Service::new()
567 .spawn(async move { Ok(arx.await?) })
568 .with_shutdown_signal(async move { ctx.send(()).unwrap() })
569 .spawn(async move {
570 crx.await?;
571 dtx.send(()).unwrap();
572 Ok(())
573 });
574
575 let b = Service::new()
576 .spawn(async move { Ok(brx.await?) })
577 .with_shutdown_signal(async move { etx.send(()).unwrap() })
578 .spawn(async move {
579 erx.await?;
580 ftx.send(()).unwrap();
581 Ok(())
582 });
583
584 let svc = Service::new().merge(a).merge(b);
586 let handle =
587 tokio::spawn(svc.wait_for_shutdown(GRACE, move || srx.clone().notified_owned()));
588
589 atx.send(()).unwrap();
591 tokio::task::yield_now().await;
592
593 btx.send(()).unwrap();
594 tokio::task::yield_now().await;
595
596 stx.notify_one();
599 drx.await.unwrap();
600 frx.await.unwrap();
601
602 let res = handle.await.unwrap();
604 assert!(matches!(res, Err(Error::Terminated)));
605 }
606
607 #[tokio::test]
608 async fn test_drop_abort() {
609 let (mut atx, arx) = oneshot::channel::<()>();
610 let (mut btx, brx) = oneshot::channel::<()>();
611
612 let svc = Service::new()
613 .spawn(async move { Ok(arx.await?) })
614 .spawn_aborting(async move { Ok(brx.await?) });
615
616 assert!(!atx.is_closed());
617 assert!(!btx.is_closed());
618
619 drop(svc);
622 atx.closed().await;
623 btx.closed().await;
624 }
625
626 #[tokio::test]
627 async fn test_shutdown() {
628 let (atx, arx) = oneshot::channel::<()>();
629 let (btx, brx) = oneshot::channel::<()>();
630
631 let svc = Service::new()
632 .with_shutdown_signal(async move { atx.send(()).unwrap() })
633 .spawn(async move { Ok(arx.await?) })
634 .spawn_aborting(async move { Ok(brx.await?) });
635
636 svc.shutdown().await.unwrap();
640 assert!(btx.is_closed());
641 }
642
643 #[tokio::test]
644 async fn test_error_cascade() {
645 let (atx, arx) = oneshot::channel::<()>();
646
647 let svc = Service::new()
649 .spawn(async move { bail!("boom") })
650 .with_shutdown_signal(async move { atx.send(()).unwrap() })
651 .spawn(async move {
652 arx.await?;
653 bail!("boom, again")
654 });
655
656 let res = svc.wait_for_shutdown(GRACE, std::future::pending).await;
658 assert!(matches!(res, Err(Error::Aborted)));
659 }
660
661 #[tokio::test]
662 async fn test_multiple_errors() {
663 let svc = Service::new()
666 .spawn(async move { bail!("boom") })
667 .spawn(async move { bail!("boom, again") });
668
669 let res = svc.wait_for_shutdown(GRACE, std::future::pending).await;
671 assert!(matches!(res, Err(Error::Aborted)));
672 }
673
674 #[tokio::test]
675 async fn test_termination_cascade() {
676 let svc = Service::new().spawn(std::future::pending());
678
679 let srx = Arc::new(Notify::new());
680 let stx = srx.clone();
681
682 let handle =
684 tokio::spawn(svc.wait_for_shutdown(GRACE, move || srx.clone().notified_owned()));
685
686 stx.notify_one();
688 tokio::task::yield_now().await;
689
690 stx.notify_one();
692 tokio::task::yield_now().await;
693
694 let res = handle.await.unwrap();
695 assert!(matches!(res, Err(Error::Aborted)));
696 }
697
698 #[tokio::test]
699 #[should_panic]
700 async fn test_panic_during_shutdown() {
701 let (atx, arx) = oneshot::channel::<()>();
702
703 let srx = Arc::new(Notify::new());
704 let stx = srx.clone();
705
706 let svc = Service::new()
707 .with_shutdown_signal(async move { atx.send(()).unwrap() })
708 .spawn(async move {
709 arx.await?;
710 panic!("boom")
711 });
712
713 let handle =
715 tokio::spawn(svc.wait_for_shutdown(GRACE, move || srx.clone().notified_owned()));
716
717 stx.notify_one();
719 let _ = handle.await.unwrap();
720 }
721
722 #[tokio::test(start_paused = true)]
723 async fn test_graceful_shutdown_timeout() {
724 let srx = Arc::new(Notify::new());
725 let stx = srx.clone();
726
727 let svc = Service::new().spawn(std::future::pending());
729
730 let handle =
731 tokio::spawn(svc.wait_for_shutdown(GRACE, move || srx.clone().notified_owned()));
732
733 stx.notify_one();
736 tokio::time::advance(GRACE * 2).await;
737
738 let res = handle.await.unwrap();
739 assert!(matches!(res, Err(Error::Aborted)));
740 }
741}