use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DelayedRefKey {
Metadata {
bytenr: u64,
owner_root: u64,
level: u8,
},
Data {
bytenr: u64,
owner_root: u64,
owner_ino: u64,
owner_offset: u64,
},
}
impl DelayedRefKey {
#[must_use]
pub fn bytenr(&self) -> u64 {
match self {
Self::Metadata { bytenr, .. } | Self::Data { bytenr, .. } => {
*bytenr
}
}
}
#[must_use]
pub fn is_metadata(&self) -> bool {
matches!(self, Self::Metadata { .. })
}
}
#[derive(Debug, Clone)]
pub struct DelayedRef {
pub key: DelayedRefKey,
pub delta: i64,
pub num_bytes: u64,
}
#[derive(Debug, Clone, Copy, Default)]
struct Entry {
delta: i64,
num_bytes: u64,
}
#[derive(Debug, Default)]
pub struct DelayedRefQueue {
refs: BTreeMap<DelayedRefKey, Entry>,
}
impl DelayedRefQueue {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_ref(
&mut self,
bytenr: u64,
is_metadata: bool,
owner_root: u64,
level: u8,
) {
debug_assert!(
is_metadata,
"add_ref is metadata-only; call add_data_ref for data refs"
);
let key = DelayedRefKey::Metadata {
bytenr,
owner_root,
level,
};
self.refs.entry(key).or_default().delta += 1;
}
pub fn drop_ref(
&mut self,
bytenr: u64,
is_metadata: bool,
owner_root: u64,
level: u8,
) {
debug_assert!(
is_metadata,
"drop_ref is metadata-only; call drop_data_ref for data refs"
);
let key = DelayedRefKey::Metadata {
bytenr,
owner_root,
level,
};
self.refs.entry(key).or_default().delta -= 1;
}
pub fn add_data_ref(
&mut self,
bytenr: u64,
num_bytes: u64,
owner_root: u64,
owner_ino: u64,
owner_offset: u64,
refs_to_add: i32,
) {
let key = DelayedRefKey::Data {
bytenr,
owner_root,
owner_ino,
owner_offset,
};
let e = self.refs.entry(key).or_default();
e.delta += i64::from(refs_to_add);
debug_assert!(e.num_bytes == 0 || e.num_bytes == num_bytes);
e.num_bytes = num_bytes;
}
pub fn drop_data_ref(
&mut self,
bytenr: u64,
num_bytes: u64,
owner_root: u64,
owner_ino: u64,
owner_offset: u64,
refs_to_drop: i32,
) {
self.add_data_ref(
bytenr,
num_bytes,
owner_root,
owner_ino,
owner_offset,
-refs_to_drop,
);
}
pub fn drain(&mut self) -> Vec<DelayedRef> {
std::mem::take(&mut self.refs)
.into_iter()
.filter(|(_, e)| e.delta != 0)
.map(|(key, e)| DelayedRef {
key,
delta: e.delta,
num_bytes: e.num_bytes,
})
.collect()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.refs.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.refs.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_and_drop_cancel() {
let mut q = DelayedRefQueue::new();
q.add_ref(65536, true, 5, 0);
q.drop_ref(65536, true, 5, 0);
let refs = q.drain();
assert!(refs.is_empty());
}
#[test]
fn net_positive() {
let mut q = DelayedRefQueue::new();
q.add_ref(65536, true, 5, 0);
let refs = q.drain();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].delta, 1);
}
#[test]
fn net_negative() {
let mut q = DelayedRefQueue::new();
q.drop_ref(65536, true, 5, 0);
let refs = q.drain();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].delta, -1);
}
#[test]
fn distinct_owners_dont_merge() {
let mut q = DelayedRefQueue::new();
q.add_ref(65536, true, 5, 0);
q.add_ref(65536, true, 6, 0);
let refs = q.drain();
assert_eq!(refs.len(), 2);
}
#[test]
fn data_refs_keyed_by_owner_triple() {
let mut q = DelayedRefQueue::new();
q.drop_data_ref(0x10000, 4096, 5, 257, 0, 1);
q.drop_data_ref(0x10000, 4096, 5, 258, 0, 1);
let refs = q.drain();
assert_eq!(refs.len(), 2);
for r in &refs {
assert_eq!(r.delta, -1);
assert_eq!(r.num_bytes, 4096);
assert!(matches!(r.key, DelayedRefKey::Data { .. }));
}
}
#[test]
fn data_add_drop_cancel() {
let mut q = DelayedRefQueue::new();
q.add_data_ref(0x10000, 4096, 5, 257, 0, 1);
q.drop_data_ref(0x10000, 4096, 5, 257, 0, 1);
assert!(q.drain().is_empty());
}
#[test]
fn drain_empties_queue() {
let mut q = DelayedRefQueue::new();
q.add_ref(65536, true, 5, 0);
assert!(!q.is_empty());
assert_eq!(q.len(), 1);
let _ = q.drain();
assert!(q.is_empty());
assert_eq!(q.len(), 0);
}
#[test]
fn double_add_ref() {
let mut q = DelayedRefQueue::new();
q.add_ref(65536, true, 5, 0);
q.add_ref(65536, true, 5, 0);
let refs = q.drain();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].delta, 2);
}
#[test]
fn empty_queue_drain() {
let mut q = DelayedRefQueue::new();
let refs = q.drain();
assert!(refs.is_empty());
}
}