Use more efficient sync types for server state

This commit is contained in:
timvisee 2021-11-10 22:37:28 +01:00
parent fb8c40e5bb
commit 01b4a9ab6e
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
2 changed files with 68 additions and 33 deletions

View File

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

View File

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