mirror of
https://github.com/timvisee/lazymc.git
synced 2025-07-26 09:42:03 -07:00
Add simple SIGINT handling
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -174,6 +174,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"libc",
|
||||
"minecraft-protocol",
|
||||
"rand 0.8.4",
|
||||
"tokio",
|
||||
|
@@ -21,6 +21,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bytes = "1.1"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
libc = "0.2"
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" }
|
||||
rand = "0.8"
|
||||
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process"] }
|
||||
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] }
|
||||
|
@@ -8,7 +8,7 @@ pub(crate) const ADDRESS_PUBLIC: &str = "127.0.0.1:9090";
|
||||
pub(crate) 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(crate) 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...";
|
||||
|
50
src/main.rs
50
src/main.rs
@@ -1,7 +1,8 @@
|
||||
pub mod config;
|
||||
pub mod monitor;
|
||||
pub mod protocol;
|
||||
pub mod types;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod monitor;
|
||||
pub(crate) mod protocol;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod types;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -19,11 +20,10 @@ use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::tcp::ReadHalf;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::process::Command;
|
||||
|
||||
use config::*;
|
||||
use monitor::ServerState;
|
||||
use protocol::{Client, ClientState, RawPacket};
|
||||
use server::ServerState;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ()> {
|
||||
@@ -32,7 +32,7 @@ async fn main() -> Result<(), ()> {
|
||||
ADDRESS_PUBLIC, ADDRESS_PROXY,
|
||||
);
|
||||
|
||||
let server_state = monitor::ServerState::default().shared();
|
||||
let server_state = Arc::new(ServerState::default());
|
||||
|
||||
// Listen for new connections
|
||||
// TODO: do not drop error here
|
||||
@@ -42,6 +42,17 @@ async fn main() -> Result<(), ()> {
|
||||
let addr = ADDRESS_PROXY.parse().expect("invalid server IP");
|
||||
tokio::spawn(monitor::monitor_server(addr, server_state.clone()));
|
||||
|
||||
let sub = server_state.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
if !sub.kill_server() {
|
||||
// TODO: gracefully kill itself instead
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy all incomming connections
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let client = Client::default();
|
||||
@@ -164,7 +175,7 @@ async fn status_server(
|
||||
// Start server if not starting yet
|
||||
if !server.starting() {
|
||||
server.set_starting(true);
|
||||
tokio::spawn(start_server(server).map(|_| ()));
|
||||
tokio::spawn(server::start(server).map(|_| ()));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -223,10 +234,10 @@ async fn status_server(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Show unhandled packet warning
|
||||
eprintln!("Received unhandled packet:");
|
||||
eprintln!("- State: {:?}", client.state());
|
||||
eprintln!("- Packet ID: {}", packet.id);
|
||||
// // Show unhandled packet warning
|
||||
// eprintln!("Received unhandled packet:");
|
||||
// eprintln!("- State: {:?}", client.state());
|
||||
// eprintln!("- Packet ID: {}", packet.id);
|
||||
}
|
||||
|
||||
// Gracefully close connection
|
||||
@@ -259,18 +270,3 @@ async fn proxy(mut inbound: TcpStream, addr_target: String) -> Result<(), ()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start Minecraft server.
|
||||
async fn start_server(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut cmd = Command::new(SERVER_CMD);
|
||||
|
||||
let status = cmd.status().await?;
|
||||
|
||||
println!("Server exited, status: {}", status);
|
||||
|
||||
// Reset online and starting state
|
||||
state.set_online(false);
|
||||
state.set_starting(false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// TODO: remove all unwraps/expects here!
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -15,6 +14,7 @@ use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::protocol::{self, ClientState, RawPacket};
|
||||
use crate::server::ServerState;
|
||||
|
||||
/// Minecraft protocol version used when polling server status.
|
||||
const PROTOCOL_VERSION: i32 = 754;
|
||||
@@ -25,43 +25,6 @@ const MONITOR_PING_INTERVAL: u64 = 2;
|
||||
/// Ping timeout in seconds.
|
||||
const PING_TIMEOUT: u64 = 8;
|
||||
|
||||
/// Shared server state.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ServerState {
|
||||
/// Whether the server is online.
|
||||
online: AtomicBool,
|
||||
|
||||
/// Whether the server is starting.
|
||||
starting: AtomicBool,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
/// Transform into shared instance.
|
||||
pub fn shared(self) -> Arc<Self> {
|
||||
Arc::new(self)
|
||||
}
|
||||
|
||||
/// Whether the server is online.
|
||||
pub fn online(&self) -> bool {
|
||||
self.online.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set whether the server is online.
|
||||
pub fn set_online(&self, online: bool) {
|
||||
self.online.store(online, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Whether the server is starting.
|
||||
pub fn starting(&self) -> bool {
|
||||
self.starting.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set whether the server is starting.
|
||||
pub fn set_starting(&self, starting: bool) {
|
||||
self.starting.store(starting, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll server state.
|
||||
///
|
||||
/// Returns `true` if a ping succeeded.
|
||||
|
@@ -46,10 +46,10 @@ impl ClientState {
|
||||
/// From state ID.
|
||||
pub fn from_id(id: i32) -> Option<Self> {
|
||||
match id {
|
||||
// 0 => Self::Handshake,
|
||||
0 => Some(Self::Handshake),
|
||||
1 => Some(Self::Status),
|
||||
2 => Some(Self::Login),
|
||||
// 2 => Self::Play,
|
||||
3 => Some(Self::Play),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -57,10 +57,10 @@ impl ClientState {
|
||||
/// Get state ID.
|
||||
pub fn to_id(self) -> i32 {
|
||||
match self {
|
||||
Self::Handshake => unimplemented!(),
|
||||
Self::Handshake => 0,
|
||||
Self::Status => 1,
|
||||
Self::Login => 2,
|
||||
Self::Play => unimplemented!(),
|
||||
Self::Play => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
92
src/server.rs
Normal file
92
src/server.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::config::SERVER_CMD;
|
||||
|
||||
/// Shared server state.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ServerState {
|
||||
/// Whether the server is online.
|
||||
online: AtomicBool,
|
||||
|
||||
/// Whether the server is starting.
|
||||
starting: AtomicBool,
|
||||
|
||||
/// Server PID.
|
||||
pid: Mutex<Option<u32>>,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
/// Whether the server is online.
|
||||
pub fn online(&self) -> bool {
|
||||
self.online.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set whether the server is online.
|
||||
pub fn set_online(&self, online: bool) {
|
||||
self.online.store(online, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Whether the server is starting.
|
||||
pub fn starting(&self) -> bool {
|
||||
self.starting.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set whether the server is starting.
|
||||
pub fn set_starting(&self, starting: bool) {
|
||||
self.starting.store(starting, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Kill any running server.
|
||||
pub fn kill_server(&self) -> bool {
|
||||
if let Some(pid) = *self.pid.lock().unwrap() {
|
||||
eprintln!("[lazymc] Sending kill signal to server");
|
||||
kill_gracefully(pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Set server PID.
|
||||
pub fn set_pid(&self, pid: Option<u32>) {
|
||||
*self.pid.lock().unwrap() = pid;
|
||||
}
|
||||
}
|
||||
|
||||
/// Start Minecraft server.
|
||||
pub async fn start(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut cmd = Command::new(SERVER_CMD);
|
||||
|
||||
eprintln!("[lazymc] Starting server...");
|
||||
let mut child = cmd.spawn()?;
|
||||
|
||||
state.set_pid(Some(child.id().expect("unknown server PID")));
|
||||
|
||||
let status = child.wait().await?;
|
||||
eprintln!("[lazymc] Server stopped (status: {})\n", status);
|
||||
|
||||
// Reset online and starting state
|
||||
// TODO: also set this when returning early due to error
|
||||
state.set_pid(None);
|
||||
state.set_online(false);
|
||||
state.set_starting(false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gracefully kill process.
|
||||
fn kill_gracefully(pid: u32) {
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
libc::kill(pid as i32, libc::SIGINT);
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// TODO: implement this for Windows
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user