use std::sync::Arc;
use dashmap::DashSet;
use crate::SequenceHash;
#[derive(Debug, Default)]
pub struct PendingTracker {
pending: DashSet<SequenceHash>,
}
impl PendingTracker {
pub fn new() -> Self {
Self {
pending: DashSet::new(),
}
}
pub fn is_pending(&self, hash: &SequenceHash) -> bool {
self.pending.contains(hash)
}
pub fn len(&self) -> usize {
self.pending.len()
}
pub fn is_empty(&self) -> bool {
self.pending.is_empty()
}
pub fn guard(self: &Arc<Self>, hash: SequenceHash) -> PendingGuard {
self.pending.insert(hash);
PendingGuard {
hash,
tracker: Arc::clone(self),
}
}
}
pub(crate) trait PendingCheck {
fn is_hash_pending(&self, hash: &SequenceHash) -> bool;
}
impl PendingCheck for Option<Arc<PendingTracker>> {
fn is_hash_pending(&self, hash: &SequenceHash) -> bool {
self.as_ref().is_some_and(|t| t.is_pending(hash))
}
}
pub struct PendingGuard {
hash: SequenceHash,
tracker: Arc<PendingTracker>,
}
impl PendingGuard {
pub fn sequence_hash(&self) -> SequenceHash {
self.hash
}
}
impl Drop for PendingGuard {
fn drop(&mut self) {
self.tracker.pending.remove(&self.hash);
}
}
impl std::fmt::Debug for PendingGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PendingGuard")
.field("sequence_hash", &self.hash)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_hash(id: u64) -> SequenceHash {
SequenceHash::new(id, Some(0), id)
}
#[test]
fn test_pending_tracker_new() {
let tracker = PendingTracker::new();
assert!(tracker.is_empty());
assert_eq!(tracker.len(), 0);
}
#[test]
fn test_pending_guard_inserts_and_removes() {
let tracker = Arc::new(PendingTracker::new());
let hash = test_hash(12345);
assert!(!tracker.is_pending(&hash));
{
let _guard = tracker.guard(hash);
assert!(tracker.is_pending(&hash));
assert_eq!(tracker.len(), 1);
}
assert!(!tracker.is_pending(&hash));
assert!(tracker.is_empty());
}
#[test]
fn test_multiple_guards_different_hashes() {
let tracker = Arc::new(PendingTracker::new());
let hash1 = test_hash(111);
let hash2 = test_hash(222);
let hash3 = test_hash(333);
let guard1 = tracker.guard(hash1);
let guard2 = tracker.guard(hash2);
assert!(tracker.is_pending(&hash1));
assert!(tracker.is_pending(&hash2));
assert!(!tracker.is_pending(&hash3));
assert_eq!(tracker.len(), 2);
drop(guard1);
assert!(!tracker.is_pending(&hash1));
assert!(tracker.is_pending(&hash2));
assert_eq!(tracker.len(), 1);
drop(guard2);
assert!(tracker.is_empty());
}
#[test]
fn test_guard_sequence_hash_accessor() {
let tracker = Arc::new(PendingTracker::new());
let hash = test_hash(42);
let guard = tracker.guard(hash);
assert_eq!(guard.sequence_hash(), hash);
}
#[test]
fn test_tracker_debug() {
let tracker = PendingTracker::new();
let debug_str = format!("{:?}", tracker);
assert!(debug_str.contains("PendingTracker"));
}
#[test]
fn test_guard_debug() {
let tracker = Arc::new(PendingTracker::new());
let hash = test_hash(999);
let guard = tracker.guard(hash);
let debug_str = format!("{:?}", guard);
assert!(debug_str.contains("PendingGuard"));
assert!(debug_str.contains("sequence_hash"));
}
#[test]
fn test_concurrent_access_to_same_hash() {
let tracker = Arc::new(PendingTracker::new());
let hash = test_hash(555);
let guard1 = tracker.guard(hash);
assert!(tracker.is_pending(&hash));
assert_eq!(tracker.len(), 1);
let guard2 = tracker.guard(hash);
assert!(tracker.is_pending(&hash));
assert_eq!(tracker.len(), 1);
drop(guard1);
assert!(!tracker.is_pending(&hash));
drop(guard2);
assert!(tracker.is_empty());
}
}