mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 04:40:22 -07:00
Add basic server idle checking, sleep server if idle for specified time
This commit is contained in:
parent
049fce78b7
commit
fed541c893
20
res/start-server
Normal file
20
res/start-server
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Server file
|
||||
FILE=server.jar
|
||||
|
||||
# Switch to script directory
|
||||
DIR="$(dirname "$(realpath "$0")")"
|
||||
cd $DIR
|
||||
|
||||
# Catch SIGTERM to gracefully stop server
|
||||
trap 'kill -TERM $PID' TERM INT
|
||||
|
||||
# Start server
|
||||
java -Xms1G -Xmx1G -jar $FILE --nogui &
|
||||
|
||||
# Clean up stopped server
|
||||
PID=$!
|
||||
wait $PID
|
||||
trap - TERM INT
|
||||
wait $PID
|
@ -1,18 +1,21 @@
|
||||
/// Command to start server.
|
||||
pub(crate) const SERVER_CMD: &str = "/home/timvisee/git/lazymc/mcserver/start";
|
||||
pub const SERVER_CMD: &str = "/home/timvisee/git/lazymc/mcserver/start";
|
||||
|
||||
/// Public address for users to connect to.
|
||||
pub(crate) const ADDRESS_PUBLIC: &str = "127.0.0.1:9090";
|
||||
pub const ADDRESS_PUBLIC: &str = "127.0.0.1:9090";
|
||||
|
||||
/// Minecraft server address to proxy to.
|
||||
pub(crate) const ADDRESS_PROXY: &str = "127.0.0.1:9091";
|
||||
pub const ADDRESS_PROXY: &str = "127.0.0.1:9091";
|
||||
|
||||
/// Server description shown when server is starting.
|
||||
pub(crate) const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping\n§2☻ Join to start it up";
|
||||
pub const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping\n§2☻ Join to start it up";
|
||||
|
||||
/// Server description shown when server is starting.
|
||||
pub(crate) const LABEL_SERVER_STARTING: &str = "§2☻ Server is starting...\n§7⌛ Please wait...";
|
||||
pub const LABEL_SERVER_STARTING: &str = "§2☻ Server is starting...\n§7⌛ Please wait...";
|
||||
|
||||
/// Kick message shown when user tries to connect to starting server.
|
||||
pub(crate) const LABEL_SERVER_STARTING_MESSAGE: &str =
|
||||
pub const LABEL_SERVER_STARTING_MESSAGE: &str =
|
||||
"Server is starting... §c♥§r\n\nThis may take some time.\n\nPlease try to reconnect in a minute.";
|
||||
|
||||
/// Idle server sleeping delay in seconds.
|
||||
pub const SLEEP_DELAY: u64 = 10;
|
||||
|
@ -133,8 +133,10 @@ async fn serve_status(
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
// Start server if not starting yet
|
||||
// TODO: move this into server state?
|
||||
if !server.starting() {
|
||||
server.set_starting(true);
|
||||
server.update_last_active_time();
|
||||
tokio::spawn(server::start(server).map(|_| ()));
|
||||
}
|
||||
|
||||
|
@ -25,13 +25,15 @@ const STATUS_TIMEOUT: u64 = 8;
|
||||
/// Monitor server.
|
||||
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
||||
loop {
|
||||
// Poll server state and update internal status
|
||||
trace!("Fetching status for {} ... ", addr);
|
||||
let status = poll_server(addr).await;
|
||||
state.update_status(status);
|
||||
|
||||
// Update server state
|
||||
state.set_online(status.is_some());
|
||||
if let Some(status) = status {
|
||||
state.set_status(status);
|
||||
// Sleep server when it's bedtime
|
||||
if state.should_sleep() {
|
||||
info!("Server has been idle, initiating sleep...");
|
||||
state.kill_server();
|
||||
}
|
||||
|
||||
// TODO: use interval instead, for a more reliable polling interval?
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use minecraft_protocol::data::server_status::ServerStatus;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::config::SERVER_CMD;
|
||||
use crate::config::{SERVER_CMD, SLEEP_DELAY};
|
||||
|
||||
/// Shared server state.
|
||||
#[derive(Default, Debug)]
|
||||
@ -13,8 +14,12 @@ pub struct ServerState {
|
||||
online: AtomicBool,
|
||||
|
||||
/// Whether the server is starting.
|
||||
// TODO: use enum for starting/started/stopping states
|
||||
starting: AtomicBool,
|
||||
|
||||
/// Whether the server is stopping.
|
||||
stopping: AtomicBool,
|
||||
|
||||
/// Server PID.
|
||||
pid: Mutex<Option<u32>>,
|
||||
|
||||
@ -23,6 +28,11 @@ pub struct ServerState {
|
||||
/// Once set, this will remain set, and isn't cleared when the server goes offline.
|
||||
// TODO: make this private?
|
||||
pub status: Mutex<Option<ServerStatus>>,
|
||||
|
||||
/// Last active time.
|
||||
///
|
||||
/// The last known time when the server was active with online players.
|
||||
last_active: Mutex<Option<Instant>>,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
@ -49,11 +59,18 @@ impl ServerState {
|
||||
/// Kill any running server.
|
||||
pub fn kill_server(&self) -> bool {
|
||||
if let Some(pid) = *self.pid.lock().unwrap() {
|
||||
warn!("Sending kill signal to server");
|
||||
debug!("Sending kill signal to server");
|
||||
kill_gracefully(pid);
|
||||
|
||||
// TODO: should we set this?
|
||||
self.set_online(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: set stopping state elsewhere
|
||||
self.stopping.store(true, Ordering::Relaxed);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@ -71,6 +88,62 @@ impl ServerState {
|
||||
pub fn set_status(&self, status: ServerStatus) {
|
||||
self.status.lock().unwrap().replace(status);
|
||||
}
|
||||
|
||||
/// Update the server status, online state and last active time.
|
||||
// TODO: clean this up
|
||||
pub fn update_status(&self, status: Option<ServerStatus>) {
|
||||
let stopping = self.stopping.load(Ordering::Relaxed);
|
||||
let was_online = self.online();
|
||||
let online = status.is_some() && !stopping;
|
||||
self.set_online(online);
|
||||
|
||||
// If server just came online, update last active time
|
||||
if !was_online && online {
|
||||
// TODO: move this somewhere else
|
||||
info!("Server is now online");
|
||||
self.update_last_active_time();
|
||||
}
|
||||
|
||||
// // If server just went offline, reset stopping state
|
||||
// // TODO: do this elsewhere
|
||||
// if stopping && was_online && !online {
|
||||
// self.stopping.store(false, Ordering::Relaxed);
|
||||
// }
|
||||
|
||||
if let Some(status) = status {
|
||||
// Update last active time if there are online players
|
||||
if status.players.online > 0 {
|
||||
self.update_last_active_time();
|
||||
}
|
||||
|
||||
// Update last known players
|
||||
self.set_status(status);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the last active time.
|
||||
pub fn update_last_active_time(&self) {
|
||||
self.last_active.lock().unwrap().replace(Instant::now());
|
||||
}
|
||||
|
||||
/// Check whether the server should now sleep.
|
||||
pub fn should_sleep(&self) -> bool {
|
||||
// TODO: when initating server start, set last active time!
|
||||
// TODO: do not initiate sleep when starting?
|
||||
// TODO: do not initiate sleep when already initiated (with timeout)
|
||||
|
||||
// Server must be online, and must not be starting
|
||||
if !self.online() || !self.starting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Last active time must have passed sleep threshold
|
||||
if let Some(last_idle) = self.last_active.lock().unwrap().as_ref() {
|
||||
return last_idle.elapsed() >= Duration::from_secs(SLEEP_DELAY);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Start Minecraft server.
|
||||
@ -90,6 +163,7 @@ pub async fn start(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Er
|
||||
state.set_pid(None);
|
||||
state.set_online(false);
|
||||
state.set_starting(false);
|
||||
state.stopping.store(false, Ordering::Relaxed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -98,9 +172,15 @@ pub async fn start(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Er
|
||||
fn kill_gracefully(pid: u32) {
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
libc::kill(pid as i32, libc::SIGINT);
|
||||
debug!("Sending SIGTERM signal to {} to kill server", pid);
|
||||
let result = libc::kill(pid as i32, libc::SIGTERM);
|
||||
trace!("SIGTERM result: {}", result);
|
||||
|
||||
// TODO: send sigterm to childs as well?
|
||||
// TODO: handle error if != 0
|
||||
}
|
||||
|
||||
// TODO: implement for Windows
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// TODO: implement this for Windows
|
||||
|
Loading…
x
Reference in New Issue
Block a user