Skip to main content

trz_gateway_common/
handle.rs

1//! Run the server in the background.
2
3use nameth::NamedEnumValues as _;
4use nameth::nameth;
5use tokio::sync::oneshot;
6use tracing::debug;
7use tracing::info;
8use tracing::warn;
9
10/// A handle to a server running in the background.
11///
12/// Dropping the handle or explicitly calling [ServerHandle::stop] stops the server.
13#[must_use]
14pub struct ServerHandle<R> {
15    name: String,
16    shutdown_tx: Option<oneshot::Sender<String>>,
17    terminated_rx: Option<oneshot::Receiver<R>>,
18}
19
20impl<R> ServerHandle<R> {
21    /// Creates a new [ServerHandle].
22    ///
23    /// This method should be called by the server on startup, it also returns
24    /// signals that the server can communicate termination.
25    pub fn new(name: impl Into<String>) -> (impl Future<Output = ()>, oneshot::Sender<R>, Self) {
26        let (shutdown_tx, shutdown_rx) = oneshot::channel();
27        let shutdown_rx = async move {
28            match shutdown_rx.await {
29                Ok(message) => info!("Server shutdown: {message}"),
30                Err(oneshot::error::RecvError { .. }) => debug!("Server handle dropped!"),
31            }
32        };
33        let (terminated_tx, terminated_rx) = oneshot::channel();
34        let handle = Self {
35            name: name.into(),
36            shutdown_tx: Some(shutdown_tx),
37            terminated_rx: Some(terminated_rx),
38        };
39        (shutdown_rx, terminated_tx, handle)
40    }
41
42    /// Stops the server and returns the result of stopping the server.
43    pub async fn stop(mut self, reason: impl std::fmt::Display) -> Result<R, ServerStopError> {
44        self.shutdown_tx
45            .take()
46            .expect("shutdown_tx")
47            .send(format!("{reason}"))
48            .map_err(|_| ServerStopError::NotRunning)?;
49        self.stopped().await
50    }
51
52    pub async fn stopped(mut self) -> Result<R, ServerStopError> {
53        self.terminated_rx
54            .take()
55            .expect("terminated_rx")
56            .await
57            .map_err(|_| ServerStopError::ShutdownError)
58    }
59}
60
61impl<R> Drop for ServerHandle<R> {
62    fn drop(&mut self) {
63        if self.terminated_rx.is_some() && !std::thread::panicking() {
64            warn!("The server '{}' was not shutdown", self.name);
65        }
66    }
67}
68
69#[nameth]
70#[derive(thiserror::Error, Debug)]
71pub enum ServerStopError {
72    #[error("[{n}] The server was not running", n = self.name())]
73    NotRunning,
74
75    #[error("[{n}] The server did not fully shutdown", n = self.name())]
76    ShutdownError,
77}