cubecl_server/memory_management/
taint.rs1use super::{ErrorGraph, FailureId};
29use core::ops::Range;
30use smallvec::SmallVec;
31
32type Claims = SmallVec<[Tainted; 1]>;
36
37type Ranges = SmallVec<[Range<u64>; 1]>;
40
41#[derive(Debug, Default)]
44pub struct Taint {
45 entries: Claims,
46}
47
48#[derive(Debug)]
50struct Tainted {
51 failure: FailureId,
52 ranges: Ranges,
55}
56
57impl Taint {
58 pub fn taint(&mut self, range: Range<u64>, failure: FailureId, failures: &mut ErrorGraph) {
67 if range.is_empty() {
68 return;
69 }
70 let entries = &mut self.entries;
71 entries.retain_mut(|entry| {
72 if entry.failure == failure {
73 return true;
74 }
75 subtract(&mut entry.ranges, &range);
76 match entry.ranges.is_empty() {
77 true => {
78 failures.untag(Some(entry.failure));
79 false
80 }
81 false => true,
82 }
83 });
84 match entries.iter_mut().find(|entry| entry.failure == failure) {
85 Some(entry) => add(&mut entry.ranges, range),
86 None => {
87 failures.tag(failure);
88 entries.push(Tainted {
89 failure,
90 ranges: Ranges::from_buf([range]),
91 });
92 }
93 }
94 }
95
96 pub fn written(&mut self, range: Range<u64>, failures: &mut ErrorGraph) {
100 if range.is_empty() {
101 return;
102 }
103 self.entries.retain_mut(|entry| {
104 subtract(&mut entry.ranges, &range);
105 match entry.ranges.is_empty() {
106 true => {
107 failures.untag(Some(entry.failure));
108 false
109 }
110 false => true,
111 }
112 });
113 }
114
115 pub fn failure(&self, range: &Range<u64>) -> Option<FailureId> {
120 self.entries
121 .iter()
122 .find(|entry| entry.ranges.iter().any(|held| overlaps(held, range)))
123 .map(|entry| entry.failure)
124 }
125
126 pub fn clear(&mut self, failures: &mut ErrorGraph) {
130 for entry in core::mem::take(&mut self.entries) {
131 failures.untag(Some(entry.failure));
132 }
133 }
134
135 pub fn is_clean(&self) -> bool {
137 self.entries.is_empty()
138 }
139}
140
141fn overlaps(a: &Range<u64>, b: &Range<u64>) -> bool {
142 a.start.max(b.start) < a.end.min(b.end)
145}
146
147fn subtract(ranges: &mut Ranges, cut: &Range<u64>) {
149 let mut index = 0;
150 while index < ranges.len() {
151 let held = ranges[index].clone();
152 if !overlaps(&held, cut) {
153 index += 1;
154 continue;
155 }
156 let left = held.start..cut.start.min(held.end);
157 let right = cut.end.max(held.start)..held.end;
158 match (left.is_empty(), right.is_empty()) {
159 (true, true) => {
160 ranges.remove(index);
161 }
162 (false, true) => {
163 ranges[index] = left;
164 index += 1;
165 }
166 (true, false) => {
167 ranges[index] = right;
168 index += 1;
169 }
170 (false, false) => {
171 ranges[index] = left;
172 ranges.insert(index + 1, right);
173 index += 2;
174 }
175 }
176 }
177}
178
179fn add(ranges: &mut Ranges, mut new: Range<u64>) {
182 ranges.retain(|held| {
183 let fuses = held.start <= new.end && new.start <= held.end;
185 if fuses {
186 new.start = new.start.min(held.start);
187 new.end = new.end.max(held.end);
188 }
189 !fuses
190 });
191 let at = ranges
192 .iter()
193 .position(|held| new.end < held.start)
194 .unwrap_or(ranges.len());
195 ranges.insert(at, new);
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::server::ServerError;
202 use alloc::string::ToString;
203
204 fn error(reason: &str) -> ServerError {
205 ServerError::Generic {
206 reason: reason.to_string(),
207 backtrace: Default::default(),
208 }
209 }
210
211 #[test]
219 fn a_failure_id_is_free_in_the_carrier() {
220 struct Narrow {
221 _failure: core::num::NonZeroU32,
222 _ranges: Ranges,
223 }
224
225 assert_eq!(
226 core::mem::size_of::<Tainted>(),
227 core::mem::size_of::<Narrow>(),
228 "a 64-bit failure id must fit in the padding a 32-bit one leaves"
229 );
230 }
231
232 #[test]
235 fn a_partial_write_releases_only_the_bytes_it_covers() {
236 let mut graph = ErrorGraph::default();
237 let mut taint = Taint::default();
238 let failure = graph.insert(error("launch"));
239
240 taint.taint(0..100, failure, &mut graph);
241 taint.written(40..60, &mut graph);
242
243 assert_eq!(taint.failure(&(0..40)), Some(failure));
244 assert_eq!(taint.failure(&(40..60)), None, "these bytes were written");
245 assert_eq!(taint.failure(&(60..100)), Some(failure));
246 assert!(!graph.is_empty(), "the split claim still pins the node");
247
248 taint.written(0..40, &mut graph);
249 taint.written(60..100, &mut graph);
250 assert!(taint.is_clean());
251 assert!(graph.is_empty(), "the last byte released the node");
252 }
253
254 #[test]
257 fn disjoint_claims_keep_their_own_failures() {
258 let mut graph = ErrorGraph::default();
259 let mut taint = Taint::default();
260 let first = graph.insert(error("first"));
261 let second = graph.insert(error("second"));
262
263 taint.taint(0..50, first, &mut graph);
264 taint.taint(50..100, second, &mut graph);
265
266 assert_eq!(taint.failure(&(10..20)), Some(first));
267 assert_eq!(taint.failure(&(60..70)), Some(second));
268 assert_eq!(graph.len(), 2);
269
270 taint.written(0..50, &mut graph);
271 assert!(graph.error(first).is_none(), "first has no carrier left");
272 assert_eq!(taint.failure(&(60..70)), Some(second));
273 }
274
275 #[test]
278 fn a_new_failure_takes_the_bytes_it_claims() {
279 let mut graph = ErrorGraph::default();
280 let mut taint = Taint::default();
281 let old = graph.insert(error("old"));
282 let new = graph.insert(error("new"));
283
284 taint.taint(0..100, old, &mut graph);
285 taint.taint(25..75, new, &mut graph);
286
287 assert_eq!(taint.failure(&(0..25)), Some(old));
288 assert_eq!(taint.failure(&(30..40)), Some(new));
289 assert_eq!(taint.failure(&(75..100)), Some(old));
290
291 taint.taint(0..100, new, &mut graph);
292 assert!(graph.error(old).is_none(), "old claims nothing any more");
293 assert_eq!(taint.failure(&(0..100)), Some(new));
294 }
295
296 #[test]
299 fn retainting_the_same_bytes_counts_once() {
300 let mut graph = ErrorGraph::default();
301 let mut taint = Taint::default();
302 let failure = graph.insert(error("launch"));
303
304 for _ in 0..3 {
305 taint.taint(0..100, failure, &mut graph);
306 }
307 assert_eq!(graph.len(), 1);
308
309 taint.written(0..100, &mut graph);
310 assert!(graph.is_empty(), "one entry, one tag, one untag");
311 }
312
313 #[test]
316 fn adjacent_claims_of_one_failure_fuse() {
317 let mut graph = ErrorGraph::default();
318 let mut taint = Taint::default();
319 let failure = graph.insert(error("launch"));
320
321 taint.taint(0..10, failure, &mut graph);
322 taint.taint(20..30, failure, &mut graph);
323 taint.taint(10..20, failure, &mut graph);
324
325 assert_eq!(taint.entries.len(), 1);
326 assert_eq!(taint.entries[0].ranges.len(), 1);
327 assert_eq!(taint.entries[0].ranges[0], 0..30);
328 }
329
330 #[test]
333 fn clearing_releases_every_claim() {
334 let mut graph = ErrorGraph::default();
335 let mut taint = Taint::default();
336 let first = graph.insert(error("first"));
337 let second = graph.insert(error("second"));
338
339 taint.taint(0..50, first, &mut graph);
340 taint.taint(50..100, second, &mut graph);
341 taint.clear(&mut graph);
342
343 assert!(taint.is_clean());
344 assert!(graph.is_empty());
345 }
346
347 #[test]
350 fn an_empty_range_claims_nothing() {
351 let mut graph = ErrorGraph::default();
352 let mut taint = Taint::default();
353 let failure = graph.insert(error("launch"));
354
355 taint.taint(10..10, failure, &mut graph);
356 assert!(taint.is_clean());
357
358 taint.taint(0..100, failure, &mut graph);
359 assert_eq!(taint.failure(&(50..50)), None);
360 }
361}