use super::{
shard::{ShardRef, LOCAL_ONE},
Inner,
};
use std::{
panic::Location,
sync::{Arc, Weak},
time::Duration,
};
#[cfg(not(miri))]
use std::time::Instant;
#[derive(Debug)]
pub(crate) struct GuardInfo {
location: &'static Location<'static>,
#[cfg(not(miri))]
created_at: Instant,
}
impl GuardInfo {
pub(crate) fn new(location: &'static Location<'static>) -> Arc<Self> {
Arc::new(Self {
location,
#[cfg(not(miri))]
created_at: Instant::now(),
})
}
pub(crate) fn location(&self) -> &'static Location<'static> {
self.location
}
#[allow(clippy::unnecessary_wraps)] pub(crate) fn age(&self) -> Option<Duration> {
#[cfg(not(miri))]
{
Some(self.created_at.elapsed())
}
#[cfg(miri)]
{
None
}
}
}
#[derive(Debug, Default)]
pub(crate) struct GuardRegistry {
guards: Vec<Weak<GuardInfo>>,
sweep_threshold: usize,
}
impl GuardRegistry {
const MIN_SWEEP_THRESHOLD: usize = 16;
pub(crate) fn register(&mut self, guard: Weak<GuardInfo>) {
if self.guards.len() >= self.sweep_threshold.max(Self::MIN_SWEEP_THRESHOLD) {
self.sweep();
}
self.guards.push(guard);
}
pub(crate) fn sweep(&mut self) {
self.guards.retain(|guard| guard.strong_count() > 0);
self.sweep_threshold = self.guards.len() * 2;
}
pub(crate) fn live(&self) -> impl Iterator<Item = Arc<GuardInfo>> + '_ {
self.guards.iter().filter_map(Weak::upgrade)
}
}
#[derive(Debug)]
pub struct Guard {
shard: ShardRef,
pin: Option<Arc<Inner>>,
location: &'static Location<'static>,
info: Option<Arc<GuardInfo>>,
}
impl Guard {
#[track_caller]
pub(crate) fn new(inner: &Arc<Inner>) -> Self {
Self::at_location(inner, Location::caller())
}
fn at_location(inner: &Arc<Inner>, location: &'static Location<'static>) -> Self {
let shard = inner.shard();
let info = shard.provenance(location);
shard.arrive(LOCAL_ONE, inner);
Self {
shard: shard.clone(),
pin: (!inner.is_root()).then(|| Arc::clone(inner)),
location,
info,
}
}
#[track_caller]
pub(crate) fn for_shard(shard: &ShardRef) -> Option<Self> {
if shard.is_root() && shard.arrive_if_arrived(LOCAL_ONE) {
let location = Location::caller();
return Some(Self {
shard: shard.clone(),
pin: None,
location,
info: shard.provenance(location),
});
}
let inner = shard.node()?;
Some(Self::at_location(&inner, Location::caller()))
}
}
impl Drop for Guard {
fn drop(&mut self) {
if self.shard.is_stopped() && log::log_enabled!(log::Level::Debug) {
if let Some(age) = self.info.as_ref().and_then(|info| info.age()) {
log::debug!(
"guard created at {} dropped during shutdown after {age:.1?}",
self.location
);
} else {
log::debug!("guard created at {} dropped during shutdown", self.location);
}
}
if self.shard.depart(LOCAL_ONE) {
if let Some(node) = &self.pin {
self.shard.flush(node);
} else if let Some(node) = self.shard.node() {
self.shard.flush(&node);
}
}
}
}
impl Clone for Guard {
fn clone(&self) -> Self {
let info = self.shard.provenance(self.location);
self.shard.arrive_nonempty(LOCAL_ONE);
Self {
shard: self.shard.clone(),
pin: self.pin.clone(),
location: self.location,
info,
}
}
}
impl Eq for Guard {}
impl PartialEq for Guard {
fn eq(&self, other: &Self) -> bool {
self.shard.same_node(&other.shard)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_memory_stays_bounded_without_reads() {
let mut registry = GuardRegistry::default();
for _ in 0..1000 {
let info = GuardInfo::new(Location::caller());
registry.register(Arc::downgrade(&info));
drop(info);
}
assert!(registry.guards.len() <= GuardRegistry::MIN_SWEEP_THRESHOLD);
}
#[test]
fn registry_tracks_live_guards() {
let mut registry = GuardRegistry::default();
let infos: Vec<_> = (0..100)
.map(|_| {
let info = GuardInfo::new(Location::caller());
registry.register(Arc::downgrade(&info));
info
})
.collect();
assert_eq!(registry.live().count(), 100);
drop(infos);
assert_eq!(registry.live().count(), 0);
}
}