Implement worker schedule access control and runtime tracking: Added a new user right worker_schedule_read to manage access to the getWorkerSchedules WebSocket endpoint. Enhanced the WebSocket server to handle requests for worker schedules, including detailed responses. Introduced runtime tracking for workers, allowing for monitoring of their current state and activity. Updated relevant SQL and Rust structures to support these features.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m36s

This commit is contained in:
Torsten Schulz (local)
2026-05-08 09:04:21 +02:00
parent b7d7705bbe
commit 42f71d831f
7 changed files with 479 additions and 5 deletions

View File

@@ -2,9 +2,12 @@ use crate::db::{ConnectionPool, DbError};
use crate::worker::sql::{QUERY_UPDATE_MONEY, QUERY_GET_MONEY};
use crate::message_broker::MessageBroker;
use std::sync::atomic::{AtomicBool, Ordering};
use std::collections::HashMap;
use std::sync::OnceLock;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
pub trait Worker: Send {
fn start_worker_thread(&mut self);
@@ -12,6 +15,66 @@ pub trait Worker: Send {
fn enable_watchdog(&mut self);
}
#[derive(Debug, Clone)]
pub struct WorkerRuntimeSnapshot {
pub worker: String,
pub current_step: String,
pub last_step_change_ts: u64,
pub running_worker: bool,
pub running_watchdog: bool,
}
#[derive(Debug, Clone)]
struct WorkerRuntimeEntry {
current_step: String,
last_step_change_ts: u64,
running_worker: bool,
running_watchdog: bool,
}
static WORKER_RUNTIME_REGISTRY: OnceLock<Mutex<HashMap<String, WorkerRuntimeEntry>>> = OnceLock::new();
fn runtime_registry() -> &'static Mutex<HashMap<String, WorkerRuntimeEntry>> {
WORKER_RUNTIME_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn now_unix_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
fn upsert_runtime_entry(name: &str, f: impl FnOnce(&mut WorkerRuntimeEntry)) {
if let Ok(mut reg) = runtime_registry().lock() {
let entry = reg.entry(name.to_string()).or_insert_with(|| WorkerRuntimeEntry {
current_step: format!("{name}: idle"),
last_step_change_ts: now_unix_secs(),
running_worker: false,
running_watchdog: false,
});
f(entry);
}
}
pub fn get_worker_runtime_snapshot() -> Vec<WorkerRuntimeSnapshot> {
let Ok(reg) = runtime_registry().lock() else {
return Vec::new();
};
let mut out: Vec<WorkerRuntimeSnapshot> = reg
.iter()
.map(|(worker, e)| WorkerRuntimeSnapshot {
worker: worker.clone(),
current_step: e.current_step.clone(),
last_step_change_ts: e.last_step_change_ts,
running_worker: e.running_worker,
running_watchdog: e.running_watchdog,
})
.collect();
out.sort_by(|a, b| a.worker.cmp(&b.worker));
out
}
pub(crate) struct WorkerState {
pub(crate) running_worker: AtomicBool,
pub(crate) running_watchdog: AtomicBool,
@@ -44,6 +107,10 @@ pub struct BaseWorker {
impl BaseWorker {
pub fn new(name: &str, pool: ConnectionPool, broker: MessageBroker) -> Self {
upsert_runtime_entry(name, |entry| {
entry.current_step = format!("{name}: idle");
entry.last_step_change_ts = now_unix_secs();
});
Self {
name: name.to_string(),
pool,
@@ -55,9 +122,15 @@ impl BaseWorker {
}
pub fn set_current_step<S: Into<String>>(&self, step: S) {
let new_step = step.into();
if let Ok(mut guard) = self.state.current_step.lock() {
*guard = step.into();
*guard = new_step.clone();
}
let worker_name = self.name.clone();
upsert_runtime_entry(&worker_name, |entry| {
entry.current_step = new_step;
entry.last_step_change_ts = now_unix_secs();
});
}
pub(crate) fn start_worker_with_loop<F>(&mut self, loop_fn: F)
@@ -68,6 +141,11 @@ impl BaseWorker {
eprintln!("[{}] Worker thread already running, skipping start.", self.name);
return;
}
let worker_name = self.name.clone();
upsert_runtime_entry(&worker_name, |entry| {
entry.running_worker = true;
entry.last_step_change_ts = now_unix_secs();
});
let state = Arc::clone(&self.state);
@@ -80,6 +158,11 @@ impl BaseWorker {
// Erst den Worker stoppen, dann auch den Watchdog beenden, damit keine
// Hintergrund-Threads weiterlaufen.
self.state.running_worker.store(false, Ordering::Relaxed);
let worker_name = self.name.clone();
upsert_runtime_entry(&worker_name, |entry| {
entry.running_worker = false;
entry.last_step_change_ts = now_unix_secs();
});
self.stop_watchdog();
if let Some(handle) = self.worker_thread.take() {
let _ = handle.join();
@@ -95,6 +178,11 @@ impl BaseWorker {
eprintln!("[{}] Watchdog already enabled, skipping.", self.name);
return;
}
let worker_name = self.name.clone();
upsert_runtime_entry(&worker_name, |entry| {
entry.running_watchdog = true;
entry.last_step_change_ts = now_unix_secs();
});
let state = Arc::clone(&self.state);
@@ -124,6 +212,11 @@ impl BaseWorker {
pub(crate) fn stop_watchdog(&mut self) {
self.state.running_watchdog.store(false, Ordering::Relaxed);
let worker_name = self.name.clone();
upsert_runtime_entry(&worker_name, |entry| {
entry.running_watchdog = false;
entry.last_step_change_ts = now_unix_secs();
});
if let Some(handle) = self.watchdog_thread.take() {
let _ = handle.join();
}

View File

@@ -22,6 +22,7 @@ mod notify;
mod sql;
pub use base::Worker;
pub use base::{get_worker_runtime_snapshot, WorkerRuntimeSnapshot};
pub use crate::db::ConnectionPool;
pub use character_creation::CharacterCreationWorker;
pub use director::DirectorWorker;