mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
179 lines
7.2 KiB
Rust
179 lines
7.2 KiB
Rust
use crate::settings::Settings;
|
|
use common::comp::chat::ChatType;
|
|
use common_net::msg::ServerGeneral;
|
|
use server::Server;
|
|
use std::{
|
|
ops::Add,
|
|
sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc,
|
|
},
|
|
time::{Duration, Instant},
|
|
};
|
|
use tracing::{error, info};
|
|
|
|
/// Coordinates the shutdown procedure for the server, which can be initiated by
|
|
/// either the TUI console interface or by sending the server the SIGUSR1 (or
|
|
/// others) signal which indicates the server is restarting due to an update.
|
|
pub(crate) struct ShutdownCoordinator {
|
|
/// The instant that the last shutdown message was sent, used for
|
|
/// calculating when to send the next shutdown message
|
|
last_shutdown_msg: Instant,
|
|
/// The interval that shutdown warning messages are sent at
|
|
msg_interval: Duration,
|
|
/// The instant that shudown was initiated at
|
|
shutdown_initiated_at: Option<Instant>,
|
|
/// The period to wait before shutting down after shutdown is initiated
|
|
shutdown_grace_period: Duration,
|
|
/// The message to use for the shutdown warning message that is sent to all
|
|
/// connected players
|
|
shutdown_message: String,
|
|
/// Provided by `signal_hook` to allow observation of a shutdown signal
|
|
shutdown_signal: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl ShutdownCoordinator {
|
|
pub fn new(shutdown_signal: Arc<AtomicBool>) -> Self {
|
|
Self {
|
|
last_shutdown_msg: Instant::now(),
|
|
msg_interval: Duration::from_secs(30),
|
|
shutdown_initiated_at: None,
|
|
shutdown_grace_period: Duration::from_secs(0),
|
|
shutdown_message: String::new(),
|
|
shutdown_signal,
|
|
}
|
|
}
|
|
|
|
/// Initiates a graceful shutdown of the server using the specified grace
|
|
/// period and message. When the grace period expires, the server
|
|
/// process exits.
|
|
pub fn initiate_shutdown(
|
|
&mut self,
|
|
server: &mut Server,
|
|
grace_period: Duration,
|
|
message: String,
|
|
) {
|
|
if self.shutdown_initiated_at.is_none() {
|
|
self.shutdown_grace_period = grace_period;
|
|
self.shutdown_initiated_at = Some(Instant::now());
|
|
self.shutdown_message = message;
|
|
|
|
// Send an initial shutdown warning message to all connected clients
|
|
self.send_shutdown_msg(server);
|
|
} else {
|
|
error!("Shutdown already in progress")
|
|
}
|
|
}
|
|
|
|
/// Aborts an in-progress shutdown and sends a message to all connected
|
|
/// clients.
|
|
pub fn abort_shutdown(&mut self, server: &mut Server) {
|
|
if self.shutdown_initiated_at.is_some() {
|
|
self.shutdown_initiated_at = None;
|
|
ShutdownCoordinator::send_msg(server, "The shutdown has been aborted".to_owned());
|
|
} else {
|
|
error!("There is no shutdown in progress");
|
|
}
|
|
}
|
|
|
|
/// Called once per tick to process any pending actions related to server
|
|
/// shutdown. If the grace period for an initiated shutdown has expired,
|
|
/// returns `true` which triggers the loop in `main.rs` to break and
|
|
/// exit the server process.
|
|
pub fn check(&mut self, server: &mut Server, settings: &Settings) -> bool {
|
|
// Check whether shutdown has been set
|
|
self.check_shutdown_signal(server, settings);
|
|
|
|
// If a shutdown is in progress, check whether it's time to send another warning
|
|
// message or shut down if the grace period has expired.
|
|
if let Some(shutdown_initiated_at) = self.shutdown_initiated_at {
|
|
if Instant::now() > shutdown_initiated_at.add(self.shutdown_grace_period) {
|
|
info!("Shutting down");
|
|
return true;
|
|
}
|
|
|
|
// In the last 10 seconds start sending messages every 1 second
|
|
if let Some(time_until_shutdown) = self.time_until_shutdown() {
|
|
if time_until_shutdown <= Duration::from_secs(10) {
|
|
self.msg_interval = Duration::from_secs(1);
|
|
}
|
|
}
|
|
|
|
// Send another shutdown warning message to all connected clients if
|
|
// msg_interval has expired
|
|
if self.last_shutdown_msg + self.msg_interval <= Instant::now() {
|
|
self.send_shutdown_msg(server);
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Checks whether a shutdown (SIGUSR1 by default) signal has been set,
|
|
/// which is used to trigger a graceful shutdown for an update. [Watchtower](https://containrrr.dev/watchtower/) is configured on the main
|
|
/// Veloren server to send SIGUSR1 instead of SIGTERM which allows us to
|
|
/// react specifically to shutdowns that are for an update.
|
|
/// NOTE: SIGUSR1 is not supported on Windows
|
|
fn check_shutdown_signal(&mut self, server: &mut Server, settings: &Settings) {
|
|
if self.shutdown_signal.load(Ordering::Relaxed) && self.shutdown_initiated_at.is_none() {
|
|
info!("Received shutdown signal, initiating graceful shutdown");
|
|
let grace_period =
|
|
Duration::from_secs(u64::from(settings.update_shutdown_grace_period_secs));
|
|
let shutdown_message = settings.update_shutdown_message.to_owned();
|
|
self.initiate_shutdown(server, grace_period, shutdown_message);
|
|
|
|
// Reset the SIGUSR1 signal indicator in case shutdown is aborted and we need to
|
|
// trigger shutdown again
|
|
self.shutdown_signal.store(false, Ordering::Relaxed);
|
|
}
|
|
}
|
|
|
|
/// Constructs a formatted shutdown message and sends it to all connected
|
|
/// clients
|
|
fn send_shutdown_msg(&mut self, server: &mut Server) {
|
|
if let Some(time_until_shutdown) = self.time_until_shutdown() {
|
|
let msg = format!(
|
|
"{} in {}",
|
|
self.shutdown_message,
|
|
ShutdownCoordinator::duration_to_text(time_until_shutdown)
|
|
);
|
|
ShutdownCoordinator::send_msg(server, msg);
|
|
self.last_shutdown_msg = Instant::now();
|
|
}
|
|
}
|
|
|
|
/// Calculates the remaining time before the shutdown grace period expires
|
|
fn time_until_shutdown(&self) -> Option<Duration> {
|
|
let shutdown_initiated_at = self.shutdown_initiated_at?;
|
|
let shutdown_time = shutdown_initiated_at + self.shutdown_grace_period;
|
|
|
|
// If we're somehow trying to calculate the time until shutdown after the
|
|
// shutdown time Instant::checked_duration_since will return None as
|
|
// negative durations are not supported.
|
|
shutdown_time.checked_duration_since(Instant::now())
|
|
}
|
|
|
|
/// Logs and sends a message to all connected clients
|
|
fn send_msg(server: &mut Server, msg: String) {
|
|
info!("{}", &msg);
|
|
server.notify_players(ServerGeneral::server_msg(ChatType::CommandError, msg));
|
|
}
|
|
|
|
/// Converts a `Duration` into text in the format XsXm for example 1 minute
|
|
/// 50 seconds would be converted to "1m50s", 2 minutes 0 seconds to
|
|
/// "2m" and 0 minutes 23 seconds to "23s".
|
|
fn duration_to_text(duration: Duration) -> String {
|
|
let secs = duration.as_secs_f32().round() as i32 % 60;
|
|
let mins = duration.as_secs_f32().round() as i32 / 60;
|
|
|
|
let mut text = String::new();
|
|
if mins > 0 {
|
|
text.push_str(format!("{}m", mins).as_str())
|
|
}
|
|
if secs > 0 {
|
|
text.push_str(format!("{}s", secs).as_str())
|
|
}
|
|
text
|
|
}
|
|
}
|