Add simple SIGINT handling

This commit is contained in:
timvisee
2021-11-07 21:14:14 +01:00
parent 9995f9fa4a
commit 934c0ee6d0
7 changed files with 124 additions and 71 deletions

1
Cargo.lock generated
View File

@@ -174,6 +174,7 @@ version = "0.1.0"
dependencies = [
"bytes",
"futures",
"libc",
"minecraft-protocol",
"rand 0.8.4",
"tokio",

View File

@@ -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"] }

View File

@@ -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...";

View File

@@ -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(())
}

View File

@@ -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.

View File

@@ -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
View 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!();
}
}