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
164 lines
4.9 KiB
Rust
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(())
|
|
}
|