Slow down when urges are high.

Also fix a few issues with concurrency errors from the DB.
This commit is contained in:
Condorra 2023-09-14 22:52:24 +10:00
parent daa847b448
commit 2beaf0d2af
12 changed files with 245 additions and 125 deletions

View File

@ -21,6 +21,7 @@ use std::collections::BTreeSet;
use std::error::Error; use std::error::Error;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use tokio_postgres::error::{DbError, SqlState};
use tokio_postgres::types::ToSql; use tokio_postgres::types::ToSql;
use tokio_postgres::NoTls; use tokio_postgres::NoTls;
use tokio_postgres::{config::Config as PgConfig, row::Row}; use tokio_postgres::{config::Config as PgConfig, row::Row};
@ -1810,3 +1811,18 @@ impl DBTrans {
.ok_or("Transaction already closed".into()) .ok_or("Transaction already closed".into())
} }
} }
pub fn is_concurrency_error(e: &(dyn Error + 'static)) -> bool {
match e.source() {
None => {}
Some(e_src) => {
if is_concurrency_error(e_src) {
return true;
}
}
}
match e.downcast_ref::<DbError>() {
None => false,
Some(dbe) => dbe.code() == &SqlState::T_R_SERIALIZATION_FAILURE,
}
}

View File

@ -1,36 +1,37 @@
use tokio::{task, time}; use crate::DResult;
use tokio::net::{TcpSocket, TcpStream, lookup_host};
use log::{info, warn};
use tokio_util::codec;
use tokio_util::codec::length_delimited::LengthDelimitedCodec;
use tokio_serde::formats::Cbor;
use blastmud_interfaces::*; use blastmud_interfaces::*;
use futures::prelude::*; use futures::prelude::*;
use tokio::sync::{Mutex, mpsc, oneshot}; use log::{info, warn};
use std::collections::BTreeMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid;
use std::collections::BTreeMap;
use crate::DResult;
use std::time::Instant; use std::time::Instant;
use tokio::net::{lookup_host, TcpSocket, TcpStream};
use tokio::sync::{mpsc, oneshot, Mutex};
use tokio::{task, time};
use tokio_serde::formats::Cbor;
use tokio_util::codec;
use tokio_util::codec::length_delimited::LengthDelimitedCodec;
use uuid::Uuid;
#[derive(Debug)] #[derive(Debug)]
pub struct ListenerSend { pub struct ListenerSend {
pub message: MessageToListener, pub message: MessageToListener,
pub ack_notify: oneshot::Sender<()> pub ack_notify: oneshot::Sender<()>,
} }
pub type ListenerMap = Arc<Mutex<BTreeMap<Uuid, mpsc::Sender<ListenerSend>>>>; pub type ListenerMap = Arc<Mutex<BTreeMap<Uuid, mpsc::Sender<ListenerSend>>>>;
async fn handle_from_listener<FHandler, HandlerFut>( async fn handle_from_listener<FHandler, HandlerFut>(
conn: TcpStream, conn: TcpStream,
message_handler: FHandler, message_handler: FHandler,
listener_map: ListenerMap) listener_map: ListenerMap,
where ) where
FHandler: Fn(Uuid, MessageFromListener) -> HandlerFut + Send + 'static, FHandler: Fn(Uuid, MessageFromListener) -> HandlerFut + Send + 'static,
HandlerFut: Future<Output = DResult<()>> + Send + 'static { HandlerFut: Future<Output = DResult<()>> + Send + 'static,
{
let mut conn_framed = tokio_serde::Framed::new( let mut conn_framed = tokio_serde::Framed::new(
codec::Framed::new(conn, LengthDelimitedCodec::new()), codec::Framed::new(conn, LengthDelimitedCodec::new()),
Cbor::<MessageFromListener, MessageToListener>::default() Cbor::<MessageFromListener, MessageToListener>::default(),
); );
let listener_id = match conn_framed.try_next().await { let listener_id = match conn_framed.try_next().await {
@ -43,7 +44,7 @@ where
} }
}; };
uuid uuid
}, }
Ok(Some(msg)) => { Ok(Some(msg)) => {
warn!("Got non-ping first message from listener: {:?}", msg); warn!("Got non-ping first message from listener: {:?}", msg);
return; return;
@ -53,15 +54,24 @@ where
return; return;
} }
Err(e) => { Err(e) => {
warn!("Lost listener connection to error {} before first message", e); warn!(
"Lost listener connection to error {} before first message",
e
);
return; return;
} }
}; };
match conn_framed.send(MessageToListener::AcknowledgeMessage).await { match conn_framed
.send(MessageToListener::AcknowledgeMessage)
.await
{
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
warn!("Got error sending listener acknowledge for initial ping: {}", e); warn!(
"Got error sending listener acknowledge for initial ping: {}",
e
);
return; return;
} }
} }
@ -189,17 +199,20 @@ pub fn make_listener_map() -> ListenerMap {
pub async fn start_listener<FHandler, HandlerFut>( pub async fn start_listener<FHandler, HandlerFut>(
bind_to: String, bind_to: String,
listener_map: ListenerMap, listener_map: ListenerMap,
handle_message: FHandler handle_message: FHandler,
) -> DResult<()> ) -> DResult<()>
where where
FHandler: Fn(Uuid, MessageFromListener) -> HandlerFut + Send + Clone + 'static, FHandler: Fn(Uuid, MessageFromListener) -> HandlerFut + Send + Clone + 'static,
HandlerFut: Future<Output = DResult<()>> + Send + 'static HandlerFut: Future<Output = DResult<()>> + Send + 'static,
{ {
info!("Starting listener on {}", bind_to); info!("Starting listener on {}", bind_to);
let addr = lookup_host(bind_to).await?.next().expect("listener address didn't resolve"); let addr = lookup_host(bind_to)
.await?
.next()
.expect("listener address didn't resolve");
let socket = match addr { let socket = match addr {
SocketAddr::V4 { .. } => TcpSocket::new_v4()?, SocketAddr::V4 { .. } => TcpSocket::new_v4()?,
SocketAddr::V6 {..} => TcpSocket::new_v6()? SocketAddr::V6 { .. } => TcpSocket::new_v6()?,
}; };
socket.set_reuseaddr(true)?; socket.set_reuseaddr(true)?;
socket.set_reuseport(true)?; socket.set_reuseport(true)?;
@ -215,7 +228,11 @@ where
} }
Ok((socket, _)) => { Ok((socket, _)) => {
info!("Accepted new inbound connection from listener"); info!("Accepted new inbound connection from listener");
task::spawn(handle_from_listener(socket, handle_message.clone(), listener_map_for_task.clone())); task::spawn(handle_from_listener(
socket,
handle_message.clone(),
listener_map_for_task.clone(),
));
} }
} }
} }

View File

@ -1,39 +1,42 @@
use serde::Deserialize;
use std::fs;
use std::error::Error;
use log::{info, error, LevelFilter};
use simple_logger::SimpleLogger;
use tokio::signal::unix::{signal, SignalKind};
use db::DBPool; use db::DBPool;
use log::{error, info, LevelFilter};
use serde::Deserialize;
use simple_logger::SimpleLogger;
use std::error::Error;
use std::fs;
use tokio::signal::unix::{signal, SignalKind};
mod av;
mod db; mod db;
mod language;
mod listener; mod listener;
mod message_handler; mod message_handler;
mod version_cutover;
mod av;
mod regular_tasks;
mod models; mod models;
mod static_content; mod regular_tasks;
mod language;
mod services; mod services;
mod static_content;
mod version_cutover;
pub type DResult<T> = Result<T, Box<dyn Error + Send + Sync>>; pub type DResult<T> = Result<T, Box<dyn Error + Send + Sync + 'static>>;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct Config { struct Config {
listener: String, listener: String,
pidfile: String, pidfile: String,
database_conn_string: String database_conn_string: String,
} }
fn read_latest_config() -> DResult<Config> { fn read_latest_config() -> DResult<Config> {
serde_yaml::from_str(&fs::read_to_string("gameserver.conf")?). serde_yaml::from_str(&fs::read_to_string("gameserver.conf")?)
map_err(|error| Box::new(error) as Box<dyn Error + Send + Sync>) .map_err(|error| Box::new(error) as Box<dyn Error + Send + Sync>)
} }
#[tokio::main(worker_threads = 2)] #[tokio::main(worker_threads = 2)]
async fn main() -> DResult<()> { async fn main() -> DResult<()> {
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap(); SimpleLogger::new()
.with_level(LevelFilter::Info)
.init()
.unwrap();
av::check().or_else(|e| -> Result<(), Box<dyn Error + Send + Sync>> { av::check().or_else(|e| -> Result<(), Box<dyn Error + Send + Sync>> {
error!("Couldn't verify age-verification.yml - this is not a complete game. Check README.md: {}", e); error!("Couldn't verify age-verification.yml - this is not a complete game. Check README.md: {}", e);
@ -50,11 +53,12 @@ async fn main() -> DResult<()> {
let listener_map = listener::make_listener_map(); let listener_map = listener::make_listener_map();
let mh_pool = pool.clone(); let mh_pool = pool.clone();
listener::start_listener(config.listener, listener_map.clone(), listener::start_listener(
move |listener_id, msg| { config.listener,
message_handler::handle(listener_id, msg, mh_pool.clone()) listener_map.clone(),
} move |listener_id, msg| message_handler::handle(listener_id, msg, mh_pool.clone()),
).await?; )
.await?;
static_content::refresh_static_content(&pool).await?; static_content::refresh_static_content(&pool).await?;

View File

@ -1,8 +1,8 @@
use blastmud_interfaces::*;
use crate::db; use crate::db;
use MessageFromListener::*;
use uuid::Uuid;
use crate::DResult; use crate::DResult;
use blastmud_interfaces::*;
use uuid::Uuid;
use MessageFromListener::*;
mod new_session; mod new_session;
pub mod user_commands; pub mod user_commands;
@ -10,7 +10,7 @@ pub mod user_commands;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ListenerSession { pub struct ListenerSession {
pub listener: Uuid, pub listener: Uuid,
pub session: Uuid pub session: Uuid,
} }
#[cfg(test)] #[cfg(test)]
@ -19,21 +19,22 @@ impl Default for ListenerSession {
use uuid::uuid; use uuid::uuid;
ListenerSession { ListenerSession {
listener: uuid!("6f9c9b61-9228-4427-abd7-c4aef127a862"), listener: uuid!("6f9c9b61-9228-4427-abd7-c4aef127a862"),
session: uuid!("668efb68-79d3-4004-9d6a-1e5757792e1a") session: uuid!("668efb68-79d3-4004-9d6a-1e5757792e1a"),
} }
} }
} }
pub async fn handle(listener: Uuid, msg: MessageFromListener, pool: db::DBPool) pub async fn handle(listener: Uuid, msg: MessageFromListener, pool: db::DBPool) -> DResult<()> {
-> DResult<()> {
match msg { match msg {
ListenerPing { .. } => { pool.record_listener_ping(listener).await?; } ListenerPing { .. } => {
pool.record_listener_ping(listener).await?;
}
SessionConnected { session, source } => { SessionConnected { session, source } => {
new_session::handle( new_session::handle(&ListenerSession { listener, session }, source, &pool).await?;
&ListenerSession { listener, session }, source, &pool).await?;
} }
SessionDisconnected { session } => { SessionDisconnected { session } => {
pool.end_session(ListenerSession { listener, session }).await?; pool.end_session(ListenerSession { listener, session })
.await?;
} }
SessionSentLine { session, msg } => { SessionSentLine { session, msg } => {
user_commands::handle(&ListenerSession { listener, session }, &msg, &pool).await?; user_commands::handle(&ListenerSession { listener, session }, &msg, &pool).await?;

View File

@ -1,7 +1,7 @@
use super::ListenerSession; use super::ListenerSession;
#[double] #[double]
use crate::db::DBTrans; use crate::db::DBTrans;
use crate::db::{DBPool, ItemSearchParams}; use crate::db::{is_concurrency_error, DBPool, ItemSearchParams};
use crate::models::user::UserFlag; use crate::models::user::UserFlag;
use crate::models::{item::Item, session::Session, user::User}; use crate::models::{item::Item, session::Session, user::User};
use crate::DResult; use crate::DResult;
@ -272,10 +272,13 @@ fn resolve_handler(ctx: &VerbContext, cmd: &str) -> Option<&'static UserVerbRef>
result result
} }
#[cfg(not(test))] pub async fn handle_in_trans(
pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DResult<()> { session: &ListenerSession,
msg: &str,
pool: &DBPool,
trans: DBTrans,
) -> DResult<()> {
let (cmd, params) = parsing::parse_command_name(msg); let (cmd, params) = parsing::parse_command_name(msg);
let trans = pool.start_transaction().await?;
let (mut session_dat, mut user_dat) = match trans.get_session_user_model(session).await? { let (mut session_dat, mut user_dat) = match trans.get_session_user_model(session).await? {
None => { None => {
// If the session has been cleaned up from the database, there is // If the session has been cleaned up from the database, there is
@ -320,6 +323,24 @@ pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DRes
Err(SystemError(e)) => Err(e)?, Err(SystemError(e)) => Err(e)?,
}, },
} }
Ok(())
}
#[cfg(not(test))]
pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DResult<()> {
loop {
let trans = pool.start_transaction().await?;
match handle_in_trans(session, msg, pool, trans).await {
Ok(_) => break,
Err(e) => {
if is_concurrency_error(e.as_ref()) {
continue;
} else {
return Err(e);
}
}
}
}
pool.bump_session_time(&session).await?; pool.bump_session_time(&session).await?;
Ok(()) Ok(())
} }

View File

@ -691,7 +691,65 @@ impl QueueCommandHandler for QueueHandler {
move_to_where(&use_location, direction, ctx).await?; move_to_where(&use_location, direction, ctx).await?;
stand_if_needed(&ctx.trans, &mut ctx.item).await?; stand_if_needed(&ctx.trans, &mut ctx.item).await?;
propagate_move_to_followers(&ctx.trans, &mut ctx.item, &direction, &source).await?; propagate_move_to_followers(&ctx.trans, &mut ctx.item, &direction, &source).await?;
Ok(time::Duration::from_secs(1))
let mut move_factor: u64 = 1;
let mut slow_factors: Vec<String> = vec![];
if let Some(urges) = ctx.item.urges.as_ref() {
if urges.hunger.value > 9500 {
slow_factors.push("you're starving".to_owned());
move_factor *= 8;
} else if urges.hunger.value > 8000 {
slow_factors.push("you're very hungry".to_owned());
move_factor *= 4;
} else if urges.hunger.value > 5000 {
slow_factors.push("you're hungry".to_owned());
move_factor *= 2;
}
if urges.thirst.value > 9500 {
slow_factors.push("your throat is parched with thirst".to_owned());
move_factor *= 8;
} else if urges.thirst.value > 8000 {
slow_factors.push("you're very thirsty".to_owned());
move_factor *= 4;
} else if urges.thirst.value > 5000 {
slow_factors.push("you're thirsty".to_owned());
move_factor *= 2;
}
if urges.stress.value > 9500 {
slow_factors.push("you're exhausted".to_owned());
move_factor *= 8;
} else if urges.stress.value > 8000 {
slow_factors.push("you're very stressed and tired".to_owned());
move_factor *= 4;
} else if urges.stress.value > 5000 {
slow_factors.push("you're stressed and tired".to_owned());
move_factor *= 2;
}
}
if slow_factors.len() > 0 {
if let Some((sess, _)) = ctx.get_session().await? {
ctx.trans
.queue_for_session(
&sess,
Some(&format!(
"You move slowly because {}.\n",
language::join_words(
&slow_factors
.iter()
.map(|f| f.as_str())
.collect::<Vec<&str>>()
)
)),
)
.await?
}
}
if move_factor > 16 {
move_factor = 16;
}
Ok(time::Duration::from_secs(move_factor))
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]

View File

@ -1,7 +1,7 @@
pub mod session;
pub mod user;
pub mod item;
pub mod task;
pub mod consent; pub mod consent;
pub mod corp; pub mod corp;
pub mod item;
pub mod journal; pub mod journal;
pub mod session;
pub mod task;
pub mod user;

View File

@ -1,6 +1,6 @@
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum ConsentType { pub enum ConsentType {
@ -8,7 +8,7 @@ pub enum ConsentType {
Medicine, Medicine,
Gifts, Gifts,
Visit, Visit,
Sex Sex,
} }
impl ConsentType { impl ConsentType {
@ -20,7 +20,7 @@ impl ConsentType {
"gifts" => Some(Gifts), "gifts" => Some(Gifts),
"visit" => Some(Visit), "visit" => Some(Visit),
"sex" => Some(Sex), "sex" => Some(Sex),
_ => None _ => None,
} }
} }
@ -57,7 +57,7 @@ impl Default for FightConsent {
status: ConsentStatus::PendingAdd, status: ConsentStatus::PendingAdd,
pending_change: None, pending_change: None,
allow_pick: false, allow_pick: false,
freely_revoke: false freely_revoke: false,
} }
} }
} }
@ -76,31 +76,30 @@ impl Default for Consent {
Self { Self {
fight_consent: None, fight_consent: None,
expires: None, expires: None,
only_in: vec!(), only_in: vec![],
allow_private: false, allow_private: false,
until_death: false until_death: false,
} }
} }
} }
impl Consent { impl Consent {
pub fn to_str(&self) -> String { pub fn to_str(&self) -> String {
let mut details = vec!(); let mut details = vec![];
if let Some(ref fc) = self.fight_consent { if let Some(ref fc) = self.fight_consent {
match fc.status { match fc.status {
ConsentStatus::PendingAdd => { ConsentStatus::PendingAdd => {
details.push("pending acceptance".to_owned()); details.push("pending acceptance".to_owned());
}, }
ConsentStatus::PendingDelete => { ConsentStatus::PendingDelete => {
details.push("pending agreement to delete".to_owned()); details.push("pending agreement to delete".to_owned());
}, }
_ => {} _ => {}
} }
match fc.pending_change.as_ref() { match fc.pending_change.as_ref() {
None => {}, None => {}
Some(new_self) => { Some(new_self) => {
details.push(format!("pending amendment to [{}]", details.push(format!("pending amendment to [{}]", &new_self.to_str()));
&new_self.to_str()));
} }
} }
if fc.allow_pick { if fc.allow_pick {
@ -122,10 +121,12 @@ impl Consent {
details.push(format!("in {}", in_place)) details.push(format!("in {}", in_place))
} }
if let Some(exp) = self.expires { if let Some(exp) = self.expires {
details.push(format!("valid for {}", details.push(format!(
"valid for {}",
humantime::format_duration(std::time::Duration::from_secs( humantime::format_duration(std::time::Duration::from_secs(
(exp - Utc::now()).num_seconds() as u64 (exp - Utc::now()).num_seconds() as u64
)))); ))
));
} }
return details.into_iter().join(", "); return details.into_iter().join(", ");
} }

View File

@ -1,5 +1,5 @@
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)] #[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)]
pub enum CorpPermission { pub enum CorpPermission {
@ -22,7 +22,7 @@ impl CorpPermission {
"war" => Some(War), "war" => Some(War),
"config" | "configure" => Some(Configure), "config" | "configure" => Some(Configure),
"finance" | "finances" => Some(Finance), "finance" | "finances" => Some(Finance),
_ => None _ => None,
} }
} }
pub fn display(&self) -> &'static str { pub fn display(&self) -> &'static str {
@ -39,7 +39,6 @@ impl CorpPermission {
} }
} }
#[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)] #[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)]
pub enum CorpCommType { pub enum CorpCommType {
Chat, Chat,
@ -60,7 +59,7 @@ impl CorpCommType {
"reward" => Some(Reward), "reward" => Some(Reward),
"death" => Some(Death), "death" => Some(Death),
"consent" => Some(Consent), "consent" => Some(Consent),
_ => None _ => None,
} }
} }
pub fn display(&self) -> &'static str { pub fn display(&self) -> &'static str {
@ -95,7 +94,7 @@ impl Default for Corp {
Self { Self {
name: "Unset".to_owned(), name: "Unset".to_owned(),
allow_combat_required: false, allow_combat_required: false,
member_permissions: vec!(), member_permissions: vec![],
founded: Utc::now(), founded: Utc::now(),
} }
} }
@ -119,18 +118,11 @@ impl Default for CorpMembership {
CorpMembership { CorpMembership {
invited_at: None, invited_at: None,
joined_at: None, joined_at: None,
permissions: vec!(), permissions: vec![],
allow_combat: false, allow_combat: false,
job_title: "Employee".to_owned(), job_title: "Employee".to_owned(),
priority: 100, priority: 100,
comms_on: vec!( comms_on: vec![Chat, Notice, Connect, Reward, Death, Consent],
Chat,
Notice,
Connect,
Reward,
Death,
Consent,
),
} }
} }
} }

View File

@ -1,15 +1,14 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet; use std::collections::BTreeSet;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum JournalType { pub enum JournalType {
SlayedMeanDog, SlayedMeanDog,
Died Died,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum JournalInProgress { pub enum JournalInProgress {}
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(default)] #[serde(default)]
@ -22,7 +21,7 @@ impl Default for JournalState {
fn default() -> Self { fn default() -> Self {
Self { Self {
completed_journals: BTreeSet::new(), completed_journals: BTreeSet::new(),
in_progress_journals: vec!(), in_progress_journals: vec![],
} }
} }
} }

View File

@ -3,7 +3,7 @@ use crate::db::DBTrans;
#[cfg(not(test))] #[cfg(not(test))]
use crate::models::task::{TaskParse, TaskRecurrence}; use crate::models::task::{TaskParse, TaskRecurrence};
use crate::{ use crate::{
db, db::{self, is_concurrency_error},
listener::{ListenerMap, ListenerSend}, listener::{ListenerMap, ListenerSend},
message_handler::user_commands::{delete, drop, hire, open, rent}, message_handler::user_commands::{delete, drop, hire, open, rent},
models::task::Task, models::task::Task,
@ -168,6 +168,9 @@ async fn process_tasks_once(pool: db::DBPool) -> DResult<()> {
}; };
match handler.do_task(&mut ctx).await { match handler.do_task(&mut ctx).await {
Err(e) => { Err(e) => {
if is_concurrency_error(e.as_ref()) {
continue;
}
task.meta.consecutive_failure_count += 1; task.meta.consecutive_failure_count += 1;
warn!("Error handling event of type {} code {} (consecutive count: {}): {:?}", warn!("Error handling event of type {} code {} (consecutive count: {}): {:?}",
&task.details.name(), &task.meta.task_code, &task.details.name(), &task.meta.task_code,
@ -265,7 +268,7 @@ async fn process_tasks_once(pool: db::DBPool) -> DResult<()> {
#[cfg(test)] #[cfg(test)]
async fn process_tasks_once(_pool: db::DBPool) -> DResult<()> { async fn process_tasks_once(_pool: db::DBPool) -> DResult<()> {
task_handler_registry(); task_handler_registry();
unimplemented!(); Ok(())
} }
fn start_task_runner(pool: db::DBPool) { fn start_task_runner(pool: db::DBPool) {

View File

@ -1,28 +1,35 @@
use crate::DResult;
use log::info;
use nix::{
sys::signal::{kill, Signal},
unistd::{getpid, Pid},
};
use std::error::Error;
use std::fs::{read_to_string, write}; use std::fs::{read_to_string, write};
use std::path::Path; use std::path::Path;
use std::error::Error;
use log::info;
use nix::{sys::signal::{kill, Signal}, unistd::{Pid, getpid}};
use crate::DResult;
pub fn replace_old_gameserver(pidfile: &str) -> DResult<()> { pub fn replace_old_gameserver(pidfile: &str) -> DResult<()> {
match read_to_string(pidfile) { match read_to_string(pidfile) {
Err(e) => Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound { if e.kind() == std::io::ErrorKind::NotFound {
info!("pidfile not found, assuming not already running"); info!("pidfile not found, assuming not already running");
Ok(()) Ok(())
} else { } else {
info!("Error reading pidfile (other than NotFound): {}", e); info!("Error reading pidfile (other than NotFound): {}", e);
Err(Box::new(e) as Box::<dyn Error + Send + Sync>) Err(Box::new(e) as Box<dyn Error + Send + Sync>)
}
} }
Ok(f) => { Ok(f) => {
let pid: Pid = Pid::from_raw(f.parse().map_err(|e| Box::new(e) as Box::<dyn Error + Send + Sync>)?); let pid: Pid = Pid::from_raw(
f.parse()
.map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)?,
);
if pid == getpid() { if pid == getpid() {
info!("Pid in pidfile is me - ignoring"); info!("Pid in pidfile is me - ignoring");
return Ok(()); return Ok(());
} }
match read_to_string(format!("/proc/{}/cmdline", pid)) { match read_to_string(format!("/proc/{}/cmdline", pid)) {
Ok(content) => Ok(content) => {
if content.contains("blastmud_game") { if content.contains("blastmud_game") {
info!("pid in pidfile references blastmud_game; starting cutover"); info!("pid in pidfile references blastmud_game; starting cutover");
kill(pid, Signal::SIGUSR1) kill(pid, Signal::SIGUSR1)
@ -31,6 +38,7 @@ pub fn replace_old_gameserver(pidfile: &str) -> DResult<()> {
info!("Pid in pidfile is for process not including blastmud_game - ignoring pidfile"); info!("Pid in pidfile is for process not including blastmud_game - ignoring pidfile");
Ok(()) Ok(())
} }
}
Err(_) => { Err(_) => {
info!("Pid in pidfile is gone - ignoring pidfile"); info!("Pid in pidfile is gone - ignoring pidfile");
Ok(()) Ok(())
@ -40,5 +48,5 @@ pub fn replace_old_gameserver(pidfile: &str) -> DResult<()> {
}?; }?;
info!("Writing new pidfile"); info!("Writing new pidfile");
write(Path::new(pidfile), format!("{}", std::process::id())) write(Path::new(pidfile), format!("{}", std::process::id()))
.map_err(|e| Box::new(e) as Box::<dyn Error + Send + Sync>) .map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)
} }