use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Range {
pub start: u64,
pub length: u64,
}
impl Range {
#[must_use]
pub fn new(start: u64, length: u64) -> Self {
Self { start, length }
}
#[must_use]
pub fn end(self) -> u64 {
self.start + self.length
}
#[must_use]
pub fn is_empty(self) -> bool {
self.length == 0
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct RangeList {
ranges: Vec<Range>,
}
impl RangeList {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_sorted_unchecked(ranges: Vec<Range>) -> Self {
Self { ranges }
}
#[must_use]
pub fn as_slice(&self) -> &[Range] {
&self.ranges
}
#[must_use]
pub fn into_vec(self) -> Vec<Range> {
self.ranges
}
#[must_use]
pub fn len(&self) -> usize {
self.ranges.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
pub fn insert(&mut self, range: Range) {
if range.is_empty() {
return;
}
let mut start = range.start;
let mut end = range.end();
let first = self
.ranges
.iter()
.position(|r| r.end() >= start)
.unwrap_or(self.ranges.len());
let mut last = first;
while last < self.ranges.len() && self.ranges[last].start <= end {
start = start.min(self.ranges[last].start);
end = end.max(self.ranges[last].end());
last += 1;
}
let merged = Range::new(start, end - start);
self.ranges.splice(first..last, std::iter::once(merged));
}
pub fn subtract(&mut self, range: Range) {
if range.is_empty() {
return;
}
let sub_start = range.start;
let sub_end = range.end();
let mut i = 0;
while i < self.ranges.len() {
let r = self.ranges[i];
if r.end() <= sub_start {
i += 1;
continue;
}
if r.start >= sub_end {
break;
}
let left = if r.start < sub_start {
Some(Range::new(r.start, sub_start - r.start))
} else {
None
};
let right = if r.end() > sub_end {
Some(Range::new(sub_end, r.end() - sub_end))
} else {
None
};
match (left, right) {
(None, None) => {
self.ranges.remove(i);
}
(Some(l), None) => {
self.ranges[i] = l;
i += 1;
}
(None, Some(rt)) => {
self.ranges[i] = rt;
i += 1;
}
(Some(l), Some(rt)) => {
self.ranges[i] = l;
self.ranges.insert(i + 1, rt);
i += 2;
}
}
}
}
#[must_use]
pub fn total_bytes(&self) -> u64 {
self.ranges.iter().map(|r| r.length).sum()
}
}
#[derive(Debug, Default, Clone)]
pub struct BlockGroupRangeDeltas {
bgs: BTreeMap<u64, BlockGroupDelta>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct BlockGroupDelta {
pub allocated: RangeList,
pub freed: RangeList,
}
impl BlockGroupRangeDeltas {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn record_allocated(&mut self, bg_start: u64, range: Range) {
let entry = self.bgs.entry(bg_start).or_default();
entry.allocated.insert(range);
}
pub fn record_freed(&mut self, bg_start: u64, range: Range) {
let entry = self.bgs.entry(bg_start).or_default();
entry.freed.insert(range);
}
pub fn cancel_within_transaction(&mut self) {
for delta in self.bgs.values_mut() {
let to_cancel: Vec<Range> = delta.allocated.as_slice().to_vec();
for r in to_cancel {
let intersected = intersect(&delta.freed, r);
for piece in intersected {
delta.allocated.subtract(piece);
delta.freed.subtract(piece);
}
}
}
self.bgs
.retain(|_, d| !(d.allocated.is_empty() && d.freed.is_empty()));
}
pub fn iter(&self) -> impl Iterator<Item = (&u64, &BlockGroupDelta)> {
self.bgs.iter()
}
#[must_use]
pub fn get(&self, bg_start: u64) -> Option<&BlockGroupDelta> {
self.bgs.get(&bg_start)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bgs.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.bgs.len()
}
pub fn clear(&mut self) {
self.bgs.clear();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApplyError {
BitmapLayout { bg_start: u64 },
AllocatedNotFree { bg_start: u64, range: Range },
FreedAlreadyFree { bg_start: u64, range: Range },
OutOfBlockGroup { bg_start: u64, range: Range },
}
impl std::fmt::Display for ApplyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BitmapLayout { bg_start } => write!(
f,
"free space tree block group {bg_start} uses bitmap layout (unsupported in v1)"
),
Self::AllocatedNotFree { bg_start, range } => write!(
f,
"allocated range {}..{} in block group {bg_start} is not contained in any free extent",
range.start,
range.end()
),
Self::FreedAlreadyFree { bg_start, range } => write!(
f,
"freed range {}..{} in block group {bg_start} overlaps an existing free extent",
range.start,
range.end()
),
Self::OutOfBlockGroup { bg_start, range } => write!(
f,
"resulting free range {}..{} lies outside block group {bg_start}",
range.start,
range.end()
),
}
}
}
impl std::error::Error for ApplyError {}
pub fn apply_delta(
bg_start: u64,
bg: Range,
existing: &RangeList,
delta: &BlockGroupDelta,
) -> Result<RangeList, ApplyError> {
let mut out = existing.clone();
for &alloc in delta.allocated.as_slice() {
if !out.contains(alloc) {
return Err(ApplyError::AllocatedNotFree {
bg_start,
range: alloc,
});
}
out.subtract(alloc);
}
for &freed in delta.freed.as_slice() {
if out.overlaps(freed) {
return Err(ApplyError::FreedAlreadyFree {
bg_start,
range: freed,
});
}
out.insert(freed);
}
let bg_end = bg.end();
for &r in out.as_slice() {
if r.start < bg.start || r.end() > bg_end {
return Err(ApplyError::OutOfBlockGroup { bg_start, range: r });
}
}
Ok(out)
}
impl RangeList {
#[must_use]
pub fn contains(&self, range: Range) -> bool {
if range.is_empty() {
return true;
}
self.ranges
.iter()
.any(|r| r.start <= range.start && r.end() >= range.end())
}
#[must_use]
pub fn overlaps(&self, range: Range) -> bool {
if range.is_empty() {
return false;
}
self.ranges
.iter()
.any(|r| r.start < range.end() && range.start < r.end())
}
}
fn intersect(list: &RangeList, range: Range) -> Vec<Range> {
let mut out = Vec::new();
if range.is_empty() {
return out;
}
let r_start = range.start;
let r_end = range.end();
for piece in list.as_slice() {
if piece.end() <= r_start {
continue;
}
if piece.start >= r_end {
break;
}
let s = piece.start.max(r_start);
let e = piece.end().min(r_end);
if e > s {
out.push(Range::new(s, e - s));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn rl(items: &[(u64, u64)]) -> RangeList {
let mut l = RangeList::new();
for &(s, n) in items {
l.insert(Range::new(s, n));
}
l
}
fn collect(l: &RangeList) -> Vec<(u64, u64)> {
l.as_slice().iter().map(|r| (r.start, r.length)).collect()
}
#[test]
fn insert_into_empty() {
let l = rl(&[(100, 10)]);
assert_eq!(collect(&l), &[(100, 10)]);
}
#[test]
fn insert_disjoint_sorted() {
let l = rl(&[(100, 10), (200, 10), (50, 5)]);
assert_eq!(collect(&l), &[(50, 5), (100, 10), (200, 10)]);
}
#[test]
fn insert_merges_touching() {
let l = rl(&[(100, 10), (110, 10)]);
assert_eq!(collect(&l), &[(100, 20)]);
}
#[test]
fn insert_merges_overlapping() {
let l = rl(&[(100, 20), (110, 20)]);
assert_eq!(collect(&l), &[(100, 30)]);
}
#[test]
fn insert_merges_chain() {
let mut l = rl(&[(100, 10), (200, 10)]);
l.insert(Range::new(105, 100)); assert_eq!(collect(&l), &[(100, 110)]); }
#[test]
fn insert_zero_length_noop() {
let mut l = rl(&[(100, 10)]);
l.insert(Range::new(150, 0));
assert_eq!(collect(&l), &[(100, 10)]);
}
#[test]
fn subtract_whole_range() {
let mut l = rl(&[(100, 10)]);
l.subtract(Range::new(100, 10));
assert!(l.is_empty());
}
#[test]
fn subtract_left_edge() {
let mut l = rl(&[(100, 10)]);
l.subtract(Range::new(100, 4));
assert_eq!(collect(&l), &[(104, 6)]);
}
#[test]
fn subtract_right_edge() {
let mut l = rl(&[(100, 10)]);
l.subtract(Range::new(106, 4));
assert_eq!(collect(&l), &[(100, 6)]);
}
#[test]
fn subtract_middle_splits() {
let mut l = rl(&[(100, 10)]);
l.subtract(Range::new(103, 4));
assert_eq!(collect(&l), &[(100, 3), (107, 3)]);
}
#[test]
fn subtract_spanning_multiple_ranges() {
let mut l = rl(&[(100, 10), (200, 10), (300, 10)]);
l.subtract(Range::new(105, 200));
assert_eq!(collect(&l), &[(100, 5), (305, 5)]);
}
#[test]
fn subtract_outside_noop() {
let mut l = rl(&[(100, 10)]);
l.subtract(Range::new(50, 10));
l.subtract(Range::new(120, 10));
assert_eq!(collect(&l), &[(100, 10)]);
}
#[test]
fn total_bytes() {
let l = rl(&[(100, 10), (200, 30)]);
assert_eq!(l.total_bytes(), 40);
}
#[test]
fn cancel_exact_match_removes_both() {
let mut d = BlockGroupRangeDeltas::new();
d.record_allocated(0, Range::new(1000, 16384));
d.record_freed(0, Range::new(1000, 16384));
d.cancel_within_transaction();
assert!(d.is_empty());
}
#[test]
fn cancel_disjoint_keeps_both() {
let mut d = BlockGroupRangeDeltas::new();
d.record_allocated(0, Range::new(1000, 16384));
d.record_freed(0, Range::new(50000, 16384));
d.cancel_within_transaction();
let bg = d.get(0).unwrap();
assert_eq!(bg.allocated.total_bytes(), 16384);
assert_eq!(bg.freed.total_bytes(), 16384);
}
#[test]
fn cancel_partial_overlap() {
let mut d = BlockGroupRangeDeltas::new();
d.record_allocated(0, Range::new(100, 100));
d.record_freed(0, Range::new(150, 100));
d.cancel_within_transaction();
let bg = d.get(0).unwrap();
assert_eq!(collect(&bg.allocated), &[(100, 50)]);
assert_eq!(collect(&bg.freed), &[(200, 50)]);
}
fn delta(alloc: &[(u64, u64)], freed: &[(u64, u64)]) -> BlockGroupDelta {
BlockGroupDelta {
allocated: rl(alloc),
freed: rl(freed),
}
}
#[test]
fn apply_subtract_middle_splits() {
let bg = Range::new(0, 1024);
let existing = rl(&[(0, 1024)]);
let d = delta(&[(400, 100)], &[]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert_eq!(collect(&out), &[(0, 400), (500, 524)]);
}
#[test]
fn apply_subtract_whole_range_removes() {
let bg = Range::new(0, 1024);
let existing = rl(&[(100, 100)]);
let d = delta(&[(100, 100)], &[]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert!(out.is_empty());
}
#[test]
fn apply_subtract_left_edge() {
let bg = Range::new(0, 1024);
let existing = rl(&[(100, 100)]);
let d = delta(&[(100, 30)], &[]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert_eq!(collect(&out), &[(130, 70)]);
}
#[test]
fn apply_add_merges_both_sides() {
let bg = Range::new(0, 1024);
let existing = rl(&[(0, 100), (200, 100)]);
let d = delta(&[], &[(100, 100)]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert_eq!(collect(&out), &[(0, 300)]);
}
#[test]
fn apply_add_inserts_in_middle() {
let bg = Range::new(0, 1024);
let existing = rl(&[(0, 100), (500, 100)]);
let d = delta(&[], &[(200, 100)]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert_eq!(collect(&out), &[(0, 100), (200, 100), (500, 100)]);
}
#[test]
fn apply_alloc_outside_free_errors() {
let bg = Range::new(0, 1024);
let existing = rl(&[(100, 100)]);
let d = delta(&[(400, 50)], &[]);
let err = apply_delta(0, bg, &existing, &d).unwrap_err();
assert!(matches!(err, ApplyError::AllocatedNotFree { .. }));
}
#[test]
fn apply_alloc_partial_outside_free_errors() {
let bg = Range::new(0, 1024);
let existing = rl(&[(100, 100)]);
let d = delta(&[(180, 50)], &[]);
let err = apply_delta(0, bg, &existing, &d).unwrap_err();
assert!(matches!(err, ApplyError::AllocatedNotFree { .. }));
}
#[test]
fn apply_freed_overlaps_free_errors() {
let bg = Range::new(0, 1024);
let existing = rl(&[(100, 100)]);
let d = delta(&[], &[(150, 50)]);
let err = apply_delta(0, bg, &existing, &d).unwrap_err();
assert!(matches!(err, ApplyError::FreedAlreadyFree { .. }));
}
#[test]
fn apply_freed_touching_is_ok_and_merges() {
let bg = Range::new(0, 1024);
let existing = rl(&[(100, 100)]);
let d = delta(&[], &[(200, 50)]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert_eq!(collect(&out), &[(100, 150)]);
}
#[test]
fn apply_result_outside_bg_errors() {
let bg = Range::new(1000, 1024);
let existing =
RangeList::from_sorted_unchecked(vec![Range::new(2000, 100)]);
let d = delta(&[], &[]);
let err = apply_delta(1000, bg, &existing, &d).unwrap_err();
assert!(matches!(err, ApplyError::OutOfBlockGroup { .. }));
}
#[test]
fn apply_alloc_then_free_into_just_freed_slot() {
let bg = Range::new(0, 1024);
let existing = rl(&[(0, 400)]);
let d = delta(&[(100, 100)], &[(100, 100)]);
let out = apply_delta(0, bg, &existing, &d).unwrap();
assert_eq!(collect(&out), &[(0, 400)]);
}
#[test]
fn range_list_contains() {
let l = rl(&[(100, 100), (300, 100)]);
assert!(l.contains(Range::new(120, 50)));
assert!(l.contains(Range::new(100, 100)));
assert!(!l.contains(Range::new(150, 100)));
assert!(!l.contains(Range::new(50, 10)));
}
#[test]
fn range_list_overlaps() {
let l = rl(&[(100, 100)]);
assert!(l.overlaps(Range::new(150, 10)));
assert!(l.overlaps(Range::new(50, 100)));
assert!(!l.overlaps(Range::new(200, 50))); assert!(!l.overlaps(Range::new(50, 50))); assert!(!l.overlaps(Range::new(0, 10)));
}
#[test]
fn cancel_isolates_per_block_group() {
let mut d = BlockGroupRangeDeltas::new();
d.record_allocated(0, Range::new(100, 10));
d.record_freed(1000, Range::new(100, 10));
d.cancel_within_transaction();
assert_eq!(d.len(), 2);
}
}