use std::collections::hash_map::Entry as MapEntry;
use std::collections::HashMap;
use crate::freeze::ClearDone;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DeathCmd {
Request,
Clear,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kernel {
Registered,
PendingClear { rerequest: bool },
}
struct Slot {
kernel: Kernel,
linked: Vec<usize>,
}
#[derive(Default)]
pub(crate) struct DeathRegistry {
slots: HashMap<u32, Slot>,
}
impl DeathRegistry {
pub(crate) fn link(&mut self, handle: u32, owner: usize) -> Option<DeathCmd> {
match self.slots.entry(handle) {
MapEntry::Vacant(vacant) => {
vacant.insert(Slot {
kernel: Kernel::Registered,
linked: vec![owner],
});
Some(DeathCmd::Request)
}
MapEntry::Occupied(occupied) => {
let slot = occupied.into_mut();
if let Kernel::PendingClear { rerequest } = &mut slot.kernel {
*rerequest = true;
}
if !slot.linked.contains(&owner) {
slot.linked.push(owner);
}
None
}
}
}
pub(crate) fn abort_request(&mut self, handle: u32) {
self.slots.remove(&handle);
}
pub(crate) fn unlink(&mut self, handle: u32, owner: usize) -> Option<DeathCmd> {
let slot = self.slots.get_mut(&handle)?;
slot.linked.retain(|linked| *linked != owner);
if !slot.linked.is_empty() {
return None;
}
match &mut slot.kernel {
Kernel::Registered => {
slot.kernel = Kernel::PendingClear { rerequest: false };
Some(DeathCmd::Clear)
}
Kernel::PendingClear { rerequest } => {
*rerequest = false;
None
}
}
}
pub(crate) fn clear_done(&mut self, handle: u32) -> ClearDone {
let Some(slot) = self.slots.get_mut(&handle) else {
return ClearDone::Unexpected;
};
match slot.kernel {
Kernel::Registered => ClearDone::Unexpected,
Kernel::PendingClear { rerequest } if rerequest && !slot.linked.is_empty() => {
slot.kernel = Kernel::Registered;
ClearDone::Rerequest
}
Kernel::PendingClear { .. } => {
self.slots.remove(&handle);
ClearDone::Removed
}
}
}
#[cfg(test)]
fn kernel(&self, handle: u32) -> Option<Kernel> {
self.slots.get(&handle).map(|slot| slot.kernel)
}
}
#[cfg(test)]
mod tests {
use super::*;
const H: u32 = 9;
const OLD: usize = 0x1000;
const NEW: usize = 0x2000;
#[test]
fn one_request_and_one_clear_per_registration() {
let mut reg = DeathRegistry::default();
assert_eq!(reg.link(H, OLD), Some(DeathCmd::Request));
assert_eq!(reg.link(H, OLD), None, "an owner links once");
assert_eq!(reg.unlink(H, OLD), Some(DeathCmd::Clear));
assert_eq!(reg.unlink(H, OLD), None, "nothing left to clear");
assert_eq!(reg.clear_done(H), ClearDone::Removed);
assert_eq!(reg.kernel(H), None);
}
#[test]
fn an_old_proxys_drop_keeps_a_newer_link() {
let mut reg = DeathRegistry::default();
assert_eq!(reg.link(H, OLD), Some(DeathCmd::Request));
assert_eq!(reg.link(H, NEW), None);
assert_eq!(reg.unlink(H, OLD), None);
assert_eq!(reg.kernel(H), Some(Kernel::Registered));
assert_eq!(reg.unlink(H, NEW), Some(DeathCmd::Clear));
}
#[test]
fn a_link_during_a_clear_requests_after_the_done() {
let mut reg = DeathRegistry::default();
reg.link(H, OLD);
assert_eq!(reg.unlink(H, OLD), Some(DeathCmd::Clear));
assert_eq!(
reg.link(H, NEW),
None,
"no request while the clear is in flight"
);
assert_eq!(reg.clear_done(H), ClearDone::Rerequest);
assert_eq!(reg.kernel(H), Some(Kernel::Registered));
assert_eq!(reg.unlink(H, NEW), Some(DeathCmd::Clear));
}
#[test]
fn a_link_undone_during_the_clear_does_not_rerequest() {
let mut reg = DeathRegistry::default();
reg.link(H, OLD);
reg.unlink(H, OLD);
reg.link(H, NEW);
assert_eq!(reg.unlink(H, NEW), None);
assert_eq!(reg.clear_done(H), ClearDone::Removed);
assert_eq!(reg.link(H, NEW), Some(DeathCmd::Request));
}
#[test]
fn stray_events_change_nothing() {
let mut reg = DeathRegistry::default();
assert_eq!(reg.unlink(H, OLD), None);
assert_eq!(reg.clear_done(H), ClearDone::Unexpected);
reg.link(H, OLD);
assert_eq!(reg.clear_done(H), ClearDone::Unexpected);
assert_eq!(reg.kernel(H), Some(Kernel::Registered));
reg.abort_request(H);
assert_eq!(reg.kernel(H), None);
}
}