mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 04:40:22 -07:00
Use more efficient sync types for server state
This commit is contained in:
parent
fb8c40e5bb
commit
01b4a9ab6e
@ -1,4 +1,5 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
@ -10,6 +11,9 @@ use crate::config::Config;
|
|||||||
/// Server state.
|
/// Server state.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
|
/// Server is stopped.
|
||||||
|
Stopped,
|
||||||
|
|
||||||
/// Server is starting.
|
/// Server is starting.
|
||||||
Starting,
|
Starting,
|
||||||
|
|
||||||
@ -18,16 +22,38 @@ pub enum State {
|
|||||||
|
|
||||||
/// Server is stopping.
|
/// Server is stopping.
|
||||||
Stopping,
|
Stopping,
|
||||||
|
}
|
||||||
|
|
||||||
/// Server is stopped.
|
impl State {
|
||||||
Stopped,
|
/// From u8, panics if invalid.
|
||||||
|
pub fn from_u8(state: u8) -> Self {
|
||||||
|
match state {
|
||||||
|
0 => Self::Stopped,
|
||||||
|
1 => Self::Starting,
|
||||||
|
2 => Self::Started,
|
||||||
|
3 => Self::Stopping,
|
||||||
|
_ => panic!("invalid State u8"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To u8.
|
||||||
|
pub fn to_u8(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Stopped => 0,
|
||||||
|
Self::Starting => 1,
|
||||||
|
Self::Started => 2,
|
||||||
|
Self::Stopping => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared server state.
|
/// Shared server state.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
/// Server state.
|
/// Server state.
|
||||||
state: Mutex<State>,
|
///
|
||||||
|
/// Matches `State`, utilzes AtomicU8 for better performance.
|
||||||
|
state: AtomicU8,
|
||||||
|
|
||||||
/// Server process PID.
|
/// Server process PID.
|
||||||
///
|
///
|
||||||
@ -37,22 +63,22 @@ pub struct Server {
|
|||||||
/// Last known server status.
|
/// Last known server status.
|
||||||
///
|
///
|
||||||
/// Will remain set once known, not cleared if server goes offline.
|
/// Will remain set once known, not cleared if server goes offline.
|
||||||
status: Mutex<Option<ServerStatus>>,
|
status: RwLock<Option<ServerStatus>>,
|
||||||
|
|
||||||
/// Last active time.
|
/// Last active time.
|
||||||
///
|
///
|
||||||
/// The last time there was activity on the server. Also set at the moment the server comes
|
/// The last time there was activity on the server. Also set at the moment the server comes
|
||||||
/// online.
|
/// online.
|
||||||
last_active: Mutex<Option<Instant>>,
|
last_active: RwLock<Option<Instant>>,
|
||||||
|
|
||||||
/// Force server to stay online until.
|
/// Force server to stay online until.
|
||||||
keep_online_until: Mutex<Option<Instant>>,
|
keep_online_until: RwLock<Option<Instant>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
/// Get current state.
|
/// Get current state.
|
||||||
pub fn state(&self) -> State {
|
pub fn state(&self) -> State {
|
||||||
*self.state.lock().unwrap()
|
State::from_u8(self.state.load(Ordering::Relaxed))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a new state.
|
/// Set a new state.
|
||||||
@ -69,29 +95,41 @@ impl Server {
|
|||||||
/// This updates various other internal things depending on how the state changes.
|
/// This updates various other internal things depending on how the state changes.
|
||||||
///
|
///
|
||||||
/// Returns false if current state didn't match `from` or if nothing changed.
|
/// Returns false if current state didn't match `from` or if nothing changed.
|
||||||
fn update_state_from(&self, from: Option<State>, state: State, config: &Config) -> bool {
|
fn update_state_from(&self, from: Option<State>, new: State, config: &Config) -> bool {
|
||||||
// Get current state, must differ from current, must match from
|
// Atomically swap state to new, return if from doesn't match
|
||||||
let mut cur = self.state.lock().unwrap();
|
let old = State::from_u8(match from {
|
||||||
if *cur == state || (from.is_some() && from != Some(*cur)) {
|
Some(from) => match self.state.compare_exchange(
|
||||||
|
from.to_u8(),
|
||||||
|
new.to_u8(),
|
||||||
|
Ordering::Relaxed,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
) {
|
||||||
|
Ok(old) => old,
|
||||||
|
Err(_) => return false,
|
||||||
|
},
|
||||||
|
None => self.state.swap(new.to_u8(), Ordering::Relaxed),
|
||||||
|
});
|
||||||
|
|
||||||
|
// State must be changed
|
||||||
|
if old == new {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Change server state from {:?} to {:?}", *cur, state);
|
trace!("Change server state from {:?} to {:?}", old, new);
|
||||||
|
|
||||||
// Online/offline messages
|
// Online/offline messages
|
||||||
match state {
|
match new {
|
||||||
State::Started => info!(target: "lazymc::monitor", "Server is now online"),
|
State::Started => info!(target: "lazymc::monitor", "Server is now online"),
|
||||||
State::Stopped => info!(target: "lazymc::monitor", "Server is now sleeping"),
|
State::Stopped => info!(target: "lazymc::monitor", "Server is now sleeping"),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Starting -> Started, update active time and keep it online for configured time
|
// If Starting -> Started, update active time and keep it online for configured time
|
||||||
if *cur == State::Starting && state == State::Started {
|
if old == State::Starting && new == State::Started {
|
||||||
self.update_last_active();
|
self.update_last_active();
|
||||||
self.keep_online_for(Some(config.time.min_online_time));
|
self.keep_online_for(Some(config.time.min_online_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
*cur = state;
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,10 +138,8 @@ impl Server {
|
|||||||
/// This updates various other internal things depending on the current state and the given
|
/// This updates various other internal things depending on the current state and the given
|
||||||
/// status.
|
/// status.
|
||||||
pub fn update_status(&self, config: &Config, status: Option<ServerStatus>) {
|
pub fn update_status(&self, config: &Config, status: Option<ServerStatus>) {
|
||||||
let state = *self.state.lock().unwrap();
|
|
||||||
|
|
||||||
// Update state based on curren
|
// Update state based on curren
|
||||||
match (state, &status) {
|
match (self.state(), &status) {
|
||||||
(State::Stopped | State::Starting, Some(_)) => {
|
(State::Stopped | State::Starting, Some(_)) => {
|
||||||
self.update_state(State::Started, config);
|
self.update_state(State::Started, config);
|
||||||
}
|
}
|
||||||
@ -120,7 +156,7 @@ impl Server {
|
|||||||
self.update_last_active();
|
self.update_last_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.status.lock().unwrap().replace(status);
|
self.status.write().unwrap().replace(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,14 +211,14 @@ impl Server {
|
|||||||
/// Always returns false if it is currently not online.
|
/// Always returns false if it is currently not online.
|
||||||
pub fn should_sleep(&self, config: &Config) -> bool {
|
pub fn should_sleep(&self, config: &Config) -> bool {
|
||||||
// Server must be online
|
// Server must be online
|
||||||
if *self.state.lock().unwrap() != State::Started {
|
if self.state() != State::Started {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never sleep if players are online
|
// Never sleep if players are online
|
||||||
let players_online = self
|
let players_online = self
|
||||||
.status
|
.status
|
||||||
.lock()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|status| status.players.online > 0)
|
.map(|status| status.players.online > 0)
|
||||||
@ -195,7 +231,7 @@ impl Server {
|
|||||||
// Don't sleep when keep online until isn't expired
|
// Don't sleep when keep online until isn't expired
|
||||||
let keep_online = self
|
let keep_online = self
|
||||||
.keep_online_until
|
.keep_online_until
|
||||||
.lock()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|i| i >= Instant::now())
|
.map(|i| i >= Instant::now())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
@ -205,27 +241,26 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Last active time must have passed sleep threshold
|
// Last active time must have passed sleep threshold
|
||||||
if let Some(last_idle) = self.last_active.lock().unwrap().as_ref() {
|
if let Some(last_idle) = self.last_active.read().unwrap().as_ref() {
|
||||||
return last_idle.elapsed() >= Duration::from_secs(config.time.sleep_after as u64);
|
return last_idle.elapsed() >= Duration::from_secs(config.time.sleep_after as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clone last known server status.
|
/// Read last known server status.
|
||||||
// TODO: return mutex guard here
|
pub fn status(&self) -> RwLockReadGuard<Option<ServerStatus>> {
|
||||||
pub fn clone_status(&self) -> Option<ServerStatus> {
|
self.status.read().unwrap()
|
||||||
self.status.lock().unwrap().clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the last active time.
|
/// Update the last active time.
|
||||||
fn update_last_active(&self) {
|
fn update_last_active(&self) {
|
||||||
self.last_active.lock().unwrap().replace(Instant::now());
|
self.last_active.write().unwrap().replace(Instant::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force the server to be online for the given number of seconds.
|
/// Force the server to be online for the given number of seconds.
|
||||||
fn keep_online_for(&self, duration: Option<u32>) {
|
fn keep_online_for(&self, duration: Option<u32>) {
|
||||||
*self.keep_online_until.lock().unwrap() = duration
|
*self.keep_online_until.write().unwrap() = duration
|
||||||
.filter(|d| *d > 0)
|
.filter(|d| *d > 0)
|
||||||
.map(|d| Instant::now() + Duration::from_secs(d as u64));
|
.map(|d| Instant::now() + Duration::from_secs(d as u64));
|
||||||
}
|
}
|
||||||
@ -234,7 +269,7 @@ impl Server {
|
|||||||
impl Default for Server {
|
impl Default for Server {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: Mutex::new(State::Stopped),
|
state: AtomicU8::new(State::Stopped.to_u8()),
|
||||||
pid: Default::default(),
|
pid: Default::default(),
|
||||||
status: Default::default(),
|
status: Default::default(),
|
||||||
last_active: Default::default(),
|
last_active: Default::default(),
|
||||||
|
@ -77,8 +77,8 @@ pub async fn serve(
|
|||||||
// Hijack server status packet
|
// Hijack server status packet
|
||||||
if client_state == ClientState::Status && packet.id == proto::STATUS_PACKET_ID_STATUS {
|
if client_state == ClientState::Status && packet.id == proto::STATUS_PACKET_ID_STATUS {
|
||||||
// Select version and player max from last known server status
|
// Select version and player max from last known server status
|
||||||
let (version, max) = match server.clone_status() {
|
let (version, max) = match server.status().as_ref() {
|
||||||
Some(status) => (status.version, status.players.max),
|
Some(status) => (status.version.clone(), status.players.max),
|
||||||
None => (
|
None => (
|
||||||
ServerVersion {
|
ServerVersion {
|
||||||
name: config.public.version.clone(),
|
name: config.public.version.clone(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user