use super::{ErrorGraph, FailureId};
use core::ops::Range;
use smallvec::SmallVec;
type Claims = SmallVec<[Tainted; 1]>;
type Ranges = SmallVec<[Range<u64>; 1]>;
#[derive(Debug, Default)]
pub struct Taint {
entries: Claims,
}
#[derive(Debug)]
struct Tainted {
failure: FailureId,
ranges: Ranges,
}
impl Taint {
pub fn taint(&mut self, range: Range<u64>, failure: FailureId, failures: &mut ErrorGraph) {
if range.is_empty() {
return;
}
let entries = &mut self.entries;
entries.retain_mut(|entry| {
if entry.failure == failure {
return true;
}
subtract(&mut entry.ranges, &range);
match entry.ranges.is_empty() {
true => {
failures.untag(Some(entry.failure));
false
}
false => true,
}
});
match entries.iter_mut().find(|entry| entry.failure == failure) {
Some(entry) => add(&mut entry.ranges, range),
None => {
failures.tag(failure);
entries.push(Tainted {
failure,
ranges: Ranges::from_buf([range]),
});
}
}
}
pub fn written(&mut self, range: Range<u64>, failures: &mut ErrorGraph) {
if range.is_empty() {
return;
}
self.entries.retain_mut(|entry| {
subtract(&mut entry.ranges, &range);
match entry.ranges.is_empty() {
true => {
failures.untag(Some(entry.failure));
false
}
false => true,
}
});
}
pub fn failure(&self, range: &Range<u64>) -> Option<FailureId> {
self.entries
.iter()
.find(|entry| entry.ranges.iter().any(|held| overlaps(held, range)))
.map(|entry| entry.failure)
}
pub fn clear(&mut self, failures: &mut ErrorGraph) {
for entry in core::mem::take(&mut self.entries) {
failures.untag(Some(entry.failure));
}
}
pub fn is_clean(&self) -> bool {
self.entries.is_empty()
}
}
fn overlaps(a: &Range<u64>, b: &Range<u64>) -> bool {
a.start.max(b.start) < a.end.min(b.end)
}
fn subtract(ranges: &mut Ranges, cut: &Range<u64>) {
let mut index = 0;
while index < ranges.len() {
let held = ranges[index].clone();
if !overlaps(&held, cut) {
index += 1;
continue;
}
let left = held.start..cut.start.min(held.end);
let right = cut.end.max(held.start)..held.end;
match (left.is_empty(), right.is_empty()) {
(true, true) => {
ranges.remove(index);
}
(false, true) => {
ranges[index] = left;
index += 1;
}
(true, false) => {
ranges[index] = right;
index += 1;
}
(false, false) => {
ranges[index] = left;
ranges.insert(index + 1, right);
index += 2;
}
}
}
}
fn add(ranges: &mut Ranges, mut new: Range<u64>) {
ranges.retain(|held| {
let fuses = held.start <= new.end && new.start <= held.end;
if fuses {
new.start = new.start.min(held.start);
new.end = new.end.max(held.end);
}
!fuses
});
let at = ranges
.iter()
.position(|held| new.end < held.start)
.unwrap_or(ranges.len());
ranges.insert(at, new);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::ServerError;
use alloc::string::ToString;
fn error(reason: &str) -> ServerError {
ServerError::Generic {
reason: reason.to_string(),
backtrace: Default::default(),
}
}
#[test]
fn a_failure_id_is_free_in_the_carrier() {
struct Narrow {
_failure: core::num::NonZeroU32,
_ranges: Ranges,
}
assert_eq!(
core::mem::size_of::<Tainted>(),
core::mem::size_of::<Narrow>(),
"a 64-bit failure id must fit in the padding a 32-bit one leaves"
);
}
#[test]
fn a_partial_write_releases_only_the_bytes_it_covers() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let failure = graph.insert(error("launch"));
taint.taint(0..100, failure, &mut graph);
taint.written(40..60, &mut graph);
assert_eq!(taint.failure(&(0..40)), Some(failure));
assert_eq!(taint.failure(&(40..60)), None, "these bytes were written");
assert_eq!(taint.failure(&(60..100)), Some(failure));
assert!(!graph.is_empty(), "the split claim still pins the node");
taint.written(0..40, &mut graph);
taint.written(60..100, &mut graph);
assert!(taint.is_clean());
assert!(graph.is_empty(), "the last byte released the node");
}
#[test]
fn disjoint_claims_keep_their_own_failures() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let first = graph.insert(error("first"));
let second = graph.insert(error("second"));
taint.taint(0..50, first, &mut graph);
taint.taint(50..100, second, &mut graph);
assert_eq!(taint.failure(&(10..20)), Some(first));
assert_eq!(taint.failure(&(60..70)), Some(second));
assert_eq!(graph.len(), 2);
taint.written(0..50, &mut graph);
assert!(graph.error(first).is_none(), "first has no carrier left");
assert_eq!(taint.failure(&(60..70)), Some(second));
}
#[test]
fn a_new_failure_takes_the_bytes_it_claims() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let old = graph.insert(error("old"));
let new = graph.insert(error("new"));
taint.taint(0..100, old, &mut graph);
taint.taint(25..75, new, &mut graph);
assert_eq!(taint.failure(&(0..25)), Some(old));
assert_eq!(taint.failure(&(30..40)), Some(new));
assert_eq!(taint.failure(&(75..100)), Some(old));
taint.taint(0..100, new, &mut graph);
assert!(graph.error(old).is_none(), "old claims nothing any more");
assert_eq!(taint.failure(&(0..100)), Some(new));
}
#[test]
fn retainting_the_same_bytes_counts_once() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let failure = graph.insert(error("launch"));
for _ in 0..3 {
taint.taint(0..100, failure, &mut graph);
}
assert_eq!(graph.len(), 1);
taint.written(0..100, &mut graph);
assert!(graph.is_empty(), "one entry, one tag, one untag");
}
#[test]
fn adjacent_claims_of_one_failure_fuse() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let failure = graph.insert(error("launch"));
taint.taint(0..10, failure, &mut graph);
taint.taint(20..30, failure, &mut graph);
taint.taint(10..20, failure, &mut graph);
assert_eq!(taint.entries.len(), 1);
assert_eq!(taint.entries[0].ranges.len(), 1);
assert_eq!(taint.entries[0].ranges[0], 0..30);
}
#[test]
fn clearing_releases_every_claim() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let first = graph.insert(error("first"));
let second = graph.insert(error("second"));
taint.taint(0..50, first, &mut graph);
taint.taint(50..100, second, &mut graph);
taint.clear(&mut graph);
assert!(taint.is_clean());
assert!(graph.is_empty());
}
#[test]
fn an_empty_range_claims_nothing() {
let mut graph = ErrorGraph::default();
let mut taint = Taint::default();
let failure = graph.insert(error("launch"));
taint.taint(10..10, failure, &mut graph);
assert!(taint.is_clean());
taint.taint(0..100, failure, &mut graph);
assert_eq!(taint.failure(&(50..50)), None);
}
}