use std::{
collections::{HashMap, HashSet, hash_map::Entry},
hash::Hash,
};
use tracing::warn;
enum ChannelState<Channel> {
Active(Channel),
Retiring(Channel),
}
pub(super) struct ChannelCache<Node, Channel> {
channels: HashMap<Node, ChannelState<Channel>>,
}
impl<Node, Channel> Default for ChannelCache<Node, Channel> {
fn default() -> Self {
Self {
channels: HashMap::new(),
}
}
}
impl<Node: Eq + Hash + Clone, Channel> ChannelCache<Node, Channel> {
pub(super) fn get(&self, node: &Node) -> Option<&Channel> {
match self.channels.get(node)? {
ChannelState::Active(channel) => Some(channel),
ChannelState::Retiring(_) => None,
}
}
pub(super) fn active_iter(&self) -> impl Iterator<Item = (&Node, &Channel)> {
self.channels
.iter()
.filter_map(|(node, state)| match state {
ChannelState::Active(channel) => Some((node, channel)),
ChannelState::Retiring(_) => None,
})
}
pub(super) fn insert(&mut self, node: Node, channel: Channel) {
match self.channels.entry(node) {
Entry::Occupied(mut entry) => match entry.get_mut() {
state @ ChannelState::Active(_) => *state = ChannelState::Active(channel),
ChannelState::Retiring(_) => {
warn!("refusing to replace a retiring channel — dropping the fresh open");
drop(channel);
}
},
Entry::Vacant(entry) => {
entry.insert(ChannelState::Active(channel));
}
}
}
pub(super) fn retire_node(&mut self, node: &Node) -> bool {
let Some(state) = self.channels.get_mut(node) else {
return false;
};
if matches!(state, ChannelState::Retiring(_)) {
return false;
}
let Some(ChannelState::Active(channel) | ChannelState::Retiring(channel)) =
self.channels.remove(node)
else {
return false;
};
self.channels
.insert(node.clone(), ChannelState::Retiring(channel));
true
}
pub(super) fn prepare_open(
&mut self,
node: &Node,
is_quiescent: impl FnOnce(&Channel) -> bool,
) -> bool {
let Some(ChannelState::Retiring(channel)) = self.channels.get(node) else {
return true;
};
if is_quiescent(channel) {
self.channels.remove(node);
}
false
}
pub(super) fn retire_absent(&mut self, seen: &HashSet<Node>) -> usize {
let absent = self
.active_iter()
.map(|(node, _)| node)
.filter(|node| !seen.contains(*node))
.cloned()
.collect::<Vec<_>>();
absent
.into_iter()
.filter(|node| self.retire_node(node))
.count()
}
pub(super) fn reap_absent(
&mut self,
seen: &HashSet<Node>,
is_quiescent: impl Fn(&Channel) -> bool,
) {
self.channels.retain(|node, state| match state {
ChannelState::Active(_) => true,
ChannelState::Retiring(channel) => seen.contains(node) || !is_quiescent(channel),
});
}
#[cfg(test)]
pub(super) fn is_retiring(&self, node: &Node) -> bool {
matches!(self.channels.get(node), Some(ChannelState::Retiring(_)))
}
}