use dashmap::mapref::entry::Entry;
use dashmap::DashMap;
use std::sync::Arc;
use tokio::sync::Notify;
#[derive(Default)]
pub(crate) struct PieceNotifier {
notifiers: DashMap<String, Arc<Notify>>,
}
pub(crate) enum Claim {
Owner,
InFlight(Arc<Notify>),
}
impl PieceNotifier {
pub(crate) fn claim(&self, piece_id: &str) -> Claim {
match self.notifiers.entry(piece_id.to_string()) {
Entry::Occupied(entry) => Claim::InFlight(entry.get().clone()),
Entry::Vacant(entry) => {
entry.insert(Arc::new(Notify::new()));
Claim::Owner
}
}
}
pub(crate) fn get(&self, piece_id: &str) -> Option<Arc<Notify>> {
self.notifiers
.get(piece_id)
.map(|notifier| notifier.value().clone())
}
pub(crate) fn remove_and_notify(&self, piece_id: &str) {
if let Some((_, notifier)) = self.notifiers.remove(piece_id) {
notifier.notify_waiters();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn test_piece_notifier_wakes_enabled_waiters() {
let piece_notifier = PieceNotifier::default();
let piece_id = "d3add1f66b0d0b8083f14479d6e181ec9e2b34cf07d4a1a2ee2fcf51d3a3f14a-0";
assert!(piece_notifier.get(piece_id).is_none());
assert!(matches!(piece_notifier.claim(piece_id), Claim::Owner));
let notifier = piece_notifier.get(piece_id).unwrap();
match piece_notifier.claim(piece_id) {
Claim::InFlight(in_flight) => assert!(Arc::ptr_eq(¬ifier, &in_flight)),
Claim::Owner => panic!("expected the second claim to lose"),
}
let notified = notifier.notified();
tokio::pin!(notified);
notified.as_mut().enable();
piece_notifier.remove_and_notify(piece_id);
tokio::time::timeout(Duration::from_secs(1), notified)
.await
.unwrap();
assert!(piece_notifier.get(piece_id).is_none());
piece_notifier.remove_and_notify(piece_id);
assert!(matches!(piece_notifier.claim(piece_id), Claim::Owner));
}
}