Files
MetaCrate/crates/libremetaverse/src/simulator_data_pool.rs
Chili Palmer c9a1170a27
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Complete first release candidate audit (#107)
2026-08-12 14:44:28 +00:00

164 lines
4.9 KiB
Rust

//! Process-wide simulator cache ownership and inactivity reaping.
use crate::{Parcel, Primitive, Settings, Simulator, SimulatorDataPool};
use libremetaverse_types::Error;
use std::collections::HashMap;
use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak};
use std::thread::JoinHandle;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
const REAPER_INTERVAL: Duration = Duration::from_secs(180);
const ACTIVE_TIMESTAMP_SECONDS: u64 = 253_402_300_799;
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
struct PoolRecord {
active_clients: i32,
inactive_since: Option<(Instant, SystemTime)>,
prim_cache: HashMap<u32, Primitive>,
parcels: HashMap<i32, Parcel>,
parcel_map: Vec<i32>,
downloading_parcel_map: bool,
}
impl PoolRecord {
fn new() -> Self {
Self {
active_clients: 0,
inactive_since: None,
prim_cache: HashMap::new(),
parcels: HashMap::new(),
parcel_map: vec![0; 64 * 64],
downloading_parcel_map: false,
}
}
fn snapshot(&self, handle: u64) -> SimulatorDataPool {
SimulatorDataPool {
active_clients: self.active_clients,
downloading_parcel_map: self.downloading_parcel_map,
handle,
inactive_since: self.inactive_since.map_or_else(
|| UNIX_EPOCH + Duration::from_secs(ACTIVE_TIMESTAMP_SECONDS),
|(_, timestamp)| timestamp,
),
parcel_map: self.parcel_map.clone(),
parcels: std::sync::RwLock::new(self.parcels.clone()),
prim_cache: self.prim_cache.clone(),
}
}
}
struct Runtime {
records: Mutex<HashMap<u64, PoolRecord>>,
worker: Mutex<Option<JoinHandle<()>>>,
wake: Condvar,
stopped: Mutex<bool>,
}
fn runtime() -> &'static Arc<Runtime> {
static RUNTIME: OnceLock<Arc<Runtime>> = OnceLock::new();
RUNTIME.get_or_init(|| {
Arc::new(Runtime {
records: Mutex::new(HashMap::new()),
worker: Mutex::new(None),
wake: Condvar::new(),
stopped: Mutex::new(false),
})
})
}
fn reap(runtime: &Runtime) {
let timeout = Duration::from_millis(
u64::try_from(Settings::simulator_pool_timeout().max(1)).unwrap_or(1),
);
mutex(&runtime.records).retain(|_, record| {
record
.inactive_since
.is_none_or(|(instant, _)| instant.elapsed() < timeout)
});
}
fn ensure_reaper() {
let runtime = Arc::clone(runtime());
let mut worker = mutex(&runtime.worker);
if worker.is_some() {
return;
}
*mutex(&runtime.stopped) = false;
let weak: Weak<Runtime> = Arc::downgrade(&runtime);
*worker = Some(std::thread::spawn(move || {
let Some(runtime) = weak.upgrade() else {
return;
};
let mut stopped = mutex(&runtime.stopped);
loop {
let (guard, _) = runtime
.wake
.wait_timeout(stopped, REAPER_INTERVAL)
.unwrap_or_else(std::sync::PoisonError::into_inner);
stopped = guard;
if *stopped {
return;
}
drop(stopped);
reap(&runtime);
stopped = mutex(&runtime.stopped);
}
}));
}
pub(crate) fn snapshots() -> HashMap<u64, SimulatorDataPool> {
reap(runtime());
mutex(&runtime().records)
.iter()
.map(|(handle, record)| (*handle, record.snapshot(*handle)))
.collect()
}
pub(crate) fn get(handle: u64) -> Result<SimulatorDataPool, Error> {
let mut records = mutex(&runtime().records);
let record = records.entry(handle).or_insert_with(PoolRecord::new);
Ok(record.snapshot(handle))
}
pub(crate) fn add(simulator: Simulator) -> Result<(), Error> {
ensure_reaper();
let mut records = mutex(&runtime().records);
let record = records
.entry(simulator.handle)
.or_insert_with(PoolRecord::new);
record.active_clients = record.active_clients.saturating_add(1).max(1);
record.inactive_since = None;
Ok(())
}
pub(crate) fn release(simulator: Simulator) -> Result<(), Error> {
let mut records = mutex(&runtime().records);
let record = records
.entry(simulator.handle)
.or_insert_with(PoolRecord::new);
record.active_clients = record.active_clients.saturating_sub(1);
if record.active_clients <= 0 {
record.active_clients = 0;
record.inactive_since = Some((Instant::now(), SystemTime::now()));
}
Ok(())
}
pub(crate) fn shutdown() -> Result<(), Error> {
let runtime = runtime();
*mutex(&runtime.stopped) = true;
runtime.wake.notify_all();
if let Some(worker) = mutex(&runtime.worker).take()
&& worker.thread().id() != std::thread::current().id()
{
worker.join().map_err(|_| Error::InvalidOperation)?;
}
Ok(())
}