mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 12:50:23 -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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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.";
|
"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(|_| ())?;
|
writer.write_all(&response).await.map_err(|_| ())?;
|
||||||
|
|
||||||
// Start server if not starting yet
|
// Start server if not starting yet
|
||||||
|
// TODO: move this into server state?
|
||||||
if !server.starting() {
|
if !server.starting() {
|
||||||
server.set_starting(true);
|
server.set_starting(true);
|
||||||
|
server.update_last_active_time();
|
||||||
tokio::spawn(server::start(server).map(|_| ()));
|
tokio::spawn(server::start(server).map(|_| ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,13 +25,15 @@ const STATUS_TIMEOUT: u64 = 8;
|
|||||||
/// Monitor server.
|
/// Monitor server.
|
||||||
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
||||||
loop {
|
loop {
|
||||||
|
// Poll server state and update internal status
|
||||||
trace!("Fetching status for {} ... ", addr);
|
trace!("Fetching status for {} ... ", addr);
|
||||||
let status = poll_server(addr).await;
|
let status = poll_server(addr).await;
|
||||||
|
state.update_status(status);
|
||||||
|
|
||||||
// Update server state
|
// Sleep server when it's bedtime
|
||||||
state.set_online(status.is_some());
|
if state.should_sleep() {
|
||||||
if let Some(status) = status {
|
info!("Server has been idle, initiating sleep...");
|
||||||
state.set_status(status);
|
state.kill_server();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use interval instead, for a more reliable polling interval?
|
// TODO: use interval instead, for a more reliable polling interval?
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use minecraft_protocol::data::server_status::ServerStatus;
|
use minecraft_protocol::data::server_status::ServerStatus;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::config::SERVER_CMD;
|
use crate::config::{SERVER_CMD, SLEEP_DELAY};
|
||||||
|
|
||||||
/// Shared server state.
|
/// Shared server state.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
@ -13,8 +14,12 @@ pub struct ServerState {
|
|||||||
online: AtomicBool,
|
online: AtomicBool,
|
||||||
|
|
||||||
/// Whether the server is starting.
|
/// Whether the server is starting.
|
||||||
|
// TODO: use enum for starting/started/stopping states
|
||||||
starting: AtomicBool,
|
starting: AtomicBool,
|
||||||
|
|
||||||
|
/// Whether the server is stopping.
|
||||||
|
stopping: AtomicBool,
|
||||||
|
|
||||||
/// Server PID.
|
/// Server PID.
|
||||||
pid: Mutex<Option<u32>>,
|
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.
|
/// Once set, this will remain set, and isn't cleared when the server goes offline.
|
||||||
// TODO: make this private?
|
// TODO: make this private?
|
||||||
pub status: Mutex<Option<ServerStatus>>,
|
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 {
|
impl ServerState {
|
||||||
@ -49,11 +59,18 @@ impl ServerState {
|
|||||||
/// Kill any running server.
|
/// Kill any running server.
|
||||||
pub fn kill_server(&self) -> bool {
|
pub fn kill_server(&self) -> bool {
|
||||||
if let Some(pid) = *self.pid.lock().unwrap() {
|
if let Some(pid) = *self.pid.lock().unwrap() {
|
||||||
warn!("Sending kill signal to server");
|
debug!("Sending kill signal to server");
|
||||||
kill_gracefully(pid);
|
kill_gracefully(pid);
|
||||||
|
|
||||||
|
// TODO: should we set this?
|
||||||
|
self.set_online(false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: set stopping state elsewhere
|
||||||
|
self.stopping.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +88,62 @@ impl ServerState {
|
|||||||
pub fn set_status(&self, status: ServerStatus) {
|
pub fn set_status(&self, status: ServerStatus) {
|
||||||
self.status.lock().unwrap().replace(status);
|
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.
|
/// 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_pid(None);
|
||||||
state.set_online(false);
|
state.set_online(false);
|
||||||
state.set_starting(false);
|
state.set_starting(false);
|
||||||
|
state.stopping.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -98,9 +172,15 @@ pub async fn start(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Er
|
|||||||
fn kill_gracefully(pid: u32) {
|
fn kill_gracefully(pid: u32) {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
unsafe {
|
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))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
// TODO: implement this for Windows
|
// TODO: implement this for Windows
|
||||||
|
Loading…
x
Reference in New Issue
Block a user