use anyhow::Result;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use crate::exit_state::prettify_exit_states;
use crate::shutdown_token::create_shutdown_token;
use crate::signal_handling::wait_for_signal;
use crate::{ShutdownToken, SubsystemHandle};
use super::subsystem::SubsystemData;
pub struct Toplevel {
subsys_data: Arc<SubsystemData>,
subsys_handle: SubsystemHandle,
}
impl Toplevel {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let global_shutdown_token = create_shutdown_token();
let local_shutdown_token = global_shutdown_token.clone();
let subsys_data = Arc::new(SubsystemData::new(
"",
global_shutdown_token,
local_shutdown_token,
));
let subsys_handle = SubsystemHandle::new(subsys_data.clone());
Self {
subsys_data,
subsys_handle,
}
}
pub fn start<
Err: Into<crate::Error>,
Fut: 'static + Future<Output = core::result::Result<(), Err>> + Send,
S: 'static + FnOnce(SubsystemHandle) -> Fut + Send,
>(
self,
name: &'static str,
subsystem: S,
) -> Self {
SubsystemHandle::new(self.subsys_data.clone()).start(name, subsystem);
self
}
pub fn catch_signals(self) -> Self {
let shutdown_token = self.subsys_handle.global_shutdown_token().clone();
tokio::spawn(async move {
wait_for_signal().await;
shutdown_token.shutdown();
});
self
}
async fn attempt_clean_shutdown(&self) -> Result<(), crate::Error> {
let result = self.subsys_data.perform_shutdown().await;
let exit_codes = match &result {
Ok(codes) => {
log::debug!("Shutdown successful. Subsystem states:");
codes
}
Err(codes) => {
log::debug!("Some subsystems failed. Subsystem states:");
codes
}
};
for formatted_exit_code in prettify_exit_states(exit_codes) {
log::debug!(" {}", formatted_exit_code);
}
match result {
Ok(_) => Ok(()),
Err(_) => Err(anyhow::anyhow!("Subsytem errors occurred.").into()),
}
}
pub async fn handle_shutdown_requests(self, shutdown_timeout: Duration) -> Result<(), crate::Error> {
self.subsys_handle.on_shutdown_requested().await;
match tokio::time::timeout(shutdown_timeout, self.attempt_clean_shutdown()).await {
Ok(val) => val,
Err(_) => {
log::error!("Shutdown timed out. Attempting to cleanup stale subsystems ...");
self.subsys_data.cancel_all_subsystems().await;
tokio::time::timeout(shutdown_timeout, self.attempt_clean_shutdown()).await?
}
}
}
#[doc(hidden)]
pub fn get_shutdown_token(&self) -> &ShutdownToken {
self.subsys_handle.global_shutdown_token()
}
}