use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use portable_atomic::AtomicU64;
use wacore_binary::CompactString;
const TOPOLOGY_LOG_CAPACITY: usize = 256;
struct TopologyLog {
entries: VecDeque<(u64, CompactString)>,
floor: u64,
}
pub(crate) struct DeviceTopology {
generation: AtomicU64,
log: std::sync::Mutex<TopologyLog>,
registry_mutation: async_lock::Mutex<()>,
}
pub(crate) struct DeviceRegistryMutationGuard<'a> {
_guard: async_lock::MutexGuard<'a, ()>,
}
impl DeviceTopology {
pub(crate) fn new() -> Arc<Self> {
Arc::new(Self {
generation: AtomicU64::new(0),
log: std::sync::Mutex::new(TopologyLog {
entries: VecDeque::with_capacity(TOPOLOGY_LOG_CAPACITY),
floor: 0,
}),
registry_mutation: async_lock::Mutex::new(()),
})
}
pub(crate) fn current(&self) -> u64 {
self.generation.load(Ordering::Acquire)
}
pub(crate) async fn lock_registry(&self) -> DeviceRegistryMutationGuard<'_> {
DeviceRegistryMutationGuard {
_guard: self.registry_mutation.lock().await,
}
}
pub(crate) fn record<'a>(&self, users: impl IntoIterator<Item = &'a str>) {
self.record_change(users);
}
fn record_change<'a>(&self, users: impl IntoIterator<Item = &'a str>) {
let mut log = self.log.lock().unwrap_or_else(|p| p.into_inner());
let generation = self.generation.load(Ordering::Acquire) + 1;
for user in users {
if log.entries.len() == TOPOLOGY_LOG_CAPACITY
&& let Some((evicted_gen, _)) = log.entries.pop_front()
{
log.floor = evicted_gen;
}
log.entries
.push_back((generation, CompactString::from(user)));
}
self.generation.store(generation, Ordering::Release);
}
pub(crate) fn record_registry<'a>(
&self,
_guard: &DeviceRegistryMutationGuard<'_>,
users: impl IntoIterator<Item = &'a str>,
) {
self.record_change(users);
}
pub(crate) fn record_global(&self) {
let mut log = self.log.lock().unwrap_or_else(|p| p.into_inner());
let generation = self.generation.load(Ordering::Acquire) + 1;
log.entries.clear();
log.floor = generation;
self.generation.store(generation, Ordering::Release);
}
pub(crate) fn unchanged_for(&self, since: u64, is_member: impl Fn(&str) -> bool) -> bool {
let log = self.log.lock().unwrap_or_else(|p| p.into_inner());
if log.floor > since {
return false;
}
log.entries
.iter()
.filter(|(generation, _)| *generation > since)
.all(|(_, user)| !is_member(user))
}
}
pub(crate) struct DeviceRegistryCache {
cache: crate::cache_store::TypedCache<String, Arc<wacore::store::traits::DeviceListRecord>>,
topology: Arc<DeviceTopology>,
}
impl DeviceRegistryCache {
pub(crate) fn new(
cache: crate::cache_store::TypedCache<String, Arc<wacore::store::traits::DeviceListRecord>>,
topology: Arc<DeviceTopology>,
) -> Self {
Self { cache, topology }
}
pub(crate) async fn get(
&self,
key: &str,
) -> Option<Arc<wacore::store::traits::DeviceListRecord>> {
self.cache.get(key).await
}
pub(crate) async fn insert<'a>(
&self,
guard: &DeviceRegistryMutationGuard<'_>,
key: String,
record: Arc<wacore::store::traits::DeviceListRecord>,
touched: impl IntoIterator<Item = &'a str>,
) {
self.cache.insert(key, record).await;
self.topology.record_registry(guard, touched);
}
pub(crate) async fn invalidate(&self, guard: &DeviceRegistryMutationGuard<'_>, key: &str) {
self.cache.invalidate(key).await;
self.topology.record_registry(guard, [key]);
}
pub(crate) async fn promote(
&self,
key: String,
record: Arc<wacore::store::traits::DeviceListRecord>,
) {
self.cache.insert(key, record).await;
}
pub(crate) async fn memory_stats(&self) -> wacore::stats::CollectionStats {
use wacore::stats::HeapSize;
self.cache
.memory_stats(|k, v| k.capacity() + v.heap_bytes())
.await
}
#[cfg(test)]
pub(crate) async fn run_pending_tasks(&self) {
self.cache.run_pending_tasks().await;
}
#[cfg(test)]
pub(crate) async fn raw_insert_for_tests(
&self,
key: String,
record: Arc<wacore::store::traits::DeviceListRecord>,
) {
self.cache.insert(key, record).await;
}
#[cfg(test)]
pub(crate) async fn raw_invalidate_for_tests(&self, key: &str) {
self.cache.invalidate(key).await;
}
}