siderite_core/lifespan/
managed.rs1use super::Lifespan;
4use crate::{ApiError, ServerError};
5use std::time::Duration;
6use tokio::sync::oneshot;
7use tokio::task::JoinHandle;
8use tokio::time::Instant;
9
10type Outcome = Result<(), ServerError>;
11type Ready = oneshot::Receiver<Outcome>;
12type Worker = JoinHandle<Outcome>;
13
14#[derive(Debug)]
21pub struct ManagedLifespan {
22 ready: Option<Ready>,
23 stop: Option<oneshot::Sender<Instant>>,
24 worker: Option<Worker>,
25 started: bool,
26 budget: Duration,
27}
28
29impl ManagedLifespan {
30 pub(super) fn new(mut lifespan: Lifespan) -> Result<Self, ServerError> {
31 let runtime = tokio::runtime::Handle::try_current().map_err(|_| {
32 let message = "supervision needs a Tokio runtime";
33 ServerError::Configuration(message.into())
34 })?;
35 let budget = lifespan.shutdown_budget;
36 let (ready_tx, ready) = oneshot::channel();
37 let (stop, mut stop_rx) = oneshot::channel();
38 let worker = runtime.spawn(async move {
39 let startup = tokio::select! {
40 result = lifespan.start_hooks() => result,
41 deadline = &mut stop_rx => {
42 let deadline = deadline.unwrap_or_else(|_| {
43 Instant::now() + budget
44 });
45 let _ = ready_tx.send(Err(ServerError::Configuration(
46 "startup was cancelled".into(),
47 )));
48 return lifespan.shutdown_at(deadline).await;
49 }
50 };
51 match startup {
52 Ok(()) => {
53 if ready_tx.send(Ok(())).is_err() {
54 let at = Instant::now() + budget;
55 return lifespan.shutdown_at(at).await;
56 }
57 let received = stop_rx.await;
58 let at = received.unwrap_or_else(|_| Instant::now() + budget);
59 lifespan.shutdown_at(at).await
60 }
61 Err(error) => {
62 let at = Instant::now() + budget;
64 let _ = lifespan.shutdown_at(at).await;
65 let _ = ready_tx.send(Err(error));
66 Ok(())
67 }
68 }
69 });
70 Ok(Self {
71 ready: Some(ready),
72 stop: Some(stop),
73 worker: Some(worker),
74 started: false,
75 budget,
76 })
77 }
78
79 pub async fn ready(&mut self) -> Result<(), ServerError> {
91 if self.started {
92 return Ok(());
93 }
94 let receiver = self.ready.take().ok_or_else(|| {
95 let message = "readiness consumed";
96 ServerError::Configuration(message.into())
97 })?;
98 receiver.await.map_err(|_| supervisor_error())??;
99 self.started = true;
100 Ok(())
101 }
102
103 pub async fn shutdown(self) -> Result<(), ServerError> {
115 let deadline = Instant::now() + self.budget;
116 self.shutdown_at(deadline).await
117 }
118
119 pub(crate) async fn shutdown_at(mut self, at: Instant) -> Outcome {
120 if let Some(stop) = self.stop.take() {
121 let _ = stop.send(at);
122 }
123 let worker = self.worker.take().ok_or_else(supervisor_error)?;
124 worker.await.map_err(|_| supervisor_error())?
125 }
126}
127
128fn supervisor_error() -> ServerError {
129 ServerError::Lifespan(ApiError::internal("lifespan supervisor failed"))
130}