Skip to main content

tor_netdoc/util/
rangemap_ext.rs

1//! Read-modify-write values for a contiguous range of keys, in a range map
2//!
3//! Separate module mostly so that the tests have somewhere convenient to live.
4//!
5//! Arguably this should be in another crate.
6//! But it brings in rangemap as a dependency which seems undesirable for tor-basic-utils.
7//! Right now it is here because this is where its (lowest in stack) call site will be.
8
9use std::ops::{Bound, RangeInclusive};
10
11use itertools::{Itertools, chain};
12use rangemap::{RangeInclusiveMap, StepFns, StepLite};
13
14use tor_basic_utils::rangebounds::RangeBoundsExt;
15
16/// Read-modify-write values for a contiguous range of keys, in a range map
17///
18/// Since the map might contain different values for various parts of the specified range,
19/// multiple possible old values might need to be handled.
20/// `rangemap_mutate_range` is suitable if different old values can be handled independently,
21/// or sequentially.
22///
23/// Calls `update` for every range currently in the map overlapping with `range0`,
24/// and for every gap overlapping with `range`.
25///
26/// If the mutated value is equal (`PartialEq`), no actual update is made.
27///
28/// If `update` throws `Err`, any mutations to its first argument *will* be stored in the map,
29/// but no further calls to `update` will be made (so only part of the range
30/// might be updated).
31///
32/// `update` should probably not use the provided `&RangeInclusive` argument as an input
33/// to calculating how to update the `&mut V`.  Doing so would make the results depend
34/// on the details of range fragmentation in the rangemap,
35/// which would be inconsistent with the usual use of a rangemap as an optimisation
36/// of an abstract data structure which stores a separate value for each key.
37///
38/// Note that `update` doesn't get a mutable reference *into the map*.
39/// so if it mutates its argument and then panics, the update might not be applied.
40// RangeInclusiveMap doesn't provide any in-place update API, and an in-place update is also
41// incompatible with passing `&mut Option<V>`.
42//
43// Other APIs that were considered, include:
44//
45//  * FnMut(Option<V>) -> Option<V>
46//
47//    This seems less idiomatic.
48//
49//  * Don't coalesce equal values, skipping a comparison.
50//
51//    `RangeInclusiveMap` already coalesces adjacent identical ranges.
52//    Comparing the old and new value is O(sizeof(V)), whereas RangeInclusiveMap::insert
53//    is at least O(log N) and will often involves that comparison against adjacent ranges.
54//
55//    In theory this optimisation might be done by `RangeInclusiveMap` already, but it's
56//    not mentioned.  Probably, Eq is quite cheap.
57pub fn rangemap_mutate_range<K, V, StepFnsT, E>(
58    map: &mut RangeInclusiveMap<K, V, StepFnsT>,
59    range0: &RangeInclusive<K>,
60    mut update: impl FnMut(&mut Option<V>, &RangeInclusive<K>) -> Result<(), E>,
61) -> Result<(), E>
62where
63    K: Ord + Clone + StepLite,
64    V: PartialEq + Clone,
65    StepFnsT: StepFns<K>,
66{
67    let relevants = chain!(
68        map.overlapping(range0).map(|(k, _v)| k.clone()),
69        map.gaps(range0),
70    )
71    .collect_vec();
72
73    for relevant in relevants {
74        // `relevant` is a range stored in the rangemap, or a gap, which overlaps with `range0`,
75        // but which might be bigger or smaller (or both) than `range0`.
76        //
77        // Insofar as it's smaller, then if there are other ranges in the rangemap with overlap,
78        // they'll be handled separately, in other loop iterations.
79        //
80        // But insofar as it's bigger, we need to trim it down, because we're only supposed
81        // to modify `range0`.
82        //
83        // We distinguish `k`, the range we are updating in this iteration,
84        // from `range0`, the overall range from our caller.
85        let k = {
86            let k = relevant
87                .intersect(range0)
88                .expect("intersection of overlapping ranges was empty");
89
90            // RangeBoundsExt::intersect works at the level of the RangeBounds trait,
91            // returning `Option<(Bound, Bound)>`,  This helper closure converts one of the
92            // returned `Bound`s back to a plain value.  The returned intersection will
93            // always be an inclusive range because the input ranges were inclusive
94            // (ie, closed, in set theory terms) and intersection preserves set closedness.
95            let fix = |b: Bound<&K>| match b {
96                Bound::Included(y) => y.clone(),
97                _other => unreachable!("intersection of closed ranges wasn't closed"),
98            };
99
100            fix(k.0)..=fix(k.1)
101        };
102
103        let v0 = map.get(k.start());
104        let mut v = v0.cloned();
105        // avoid losing an update if Err is returned, so park any `Err` in `r`
106        let r = update(&mut v, &k);
107        if v.as_ref() != v0 {
108            if let Some(v) = v {
109                map.insert(k, v);
110            } else {
111                map.remove(k);
112            }
113        }
114        r?;
115    }
116    Ok(())
117}
118
119#[cfg(test)]
120mod test {
121    // @@ begin test lint list maintained by maint/add_warning @@
122    #![allow(clippy::bool_assert_comparison)]
123    #![allow(clippy::clone_on_copy)]
124    #![allow(clippy::dbg_macro)]
125    #![allow(clippy::mixed_attributes_style)]
126    #![allow(clippy::print_stderr)]
127    #![allow(clippy::print_stdout)]
128    #![allow(clippy::single_char_pattern)]
129    #![allow(clippy::unwrap_used)]
130    #![allow(clippy::unchecked_time_subtraction)]
131    #![allow(clippy::useless_vec)]
132    #![allow(clippy::needless_pass_by_value)]
133    #![allow(clippy::string_slice)] // See arti#2571
134    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
135    use super::*;
136    use educe::Educe;
137    use std::fmt::Debug;
138    use void::{ResultVoidExt as _, Void};
139
140    type Range = RangeInclusive<u8>;
141    const ALL_K: Range = 0..=255;
142    type Id = u32;
143
144    /// Magic value
145    ///
146    /// Comparisons use only the value in `v`, but we track its value identity,
147    /// which lets us see (for example) whether an "equal" update did anything.
148    #[derive(Debug, Clone, Educe)]
149    #[educe(PartialEq)]
150    struct Value {
151        v: char,
152        #[educe(PartialEq(ignore))]
153        id: Id,
154    }
155
156    /// Test wrapper for `RangeInclusiveMap`
157    ///
158    /// Maintains a separate copy of the expected current V for each K, in `reference`.
159    /// Cross-checks it.
160    #[derive(Debug, Clone, Educe)]
161    #[educe(Default, PartialEq)]
162    struct TestState {
163        map: RangeInclusiveMap<u8, Value>,
164        #[educe(Default(expression = "[None; _]"))] // Only short arrays are Default :-/
165        reference: [Option<char>; 256],
166        #[educe(PartialEq(ignore))]
167        ids: IdGenerator,
168    }
169
170    /// avoids constant repetition of `let ; += 1;` pattern
171    #[derive(Debug, Clone, Default, PartialEq, Eq)]
172    struct IdGenerator(Id);
173
174    impl IdGenerator {
175        fn next(&mut self) -> Id {
176            let r = self.0;
177            self.0 += 1;
178            r
179        }
180    }
181
182    impl TestState {
183        fn from_iter(elems: impl IntoIterator<Item = (Range, char)>) -> Self {
184            let mut self_ = TestState::default();
185            let id = self_.ids.next();
186            for (range, v) in elems {
187                self_.map.insert(range.clone(), Value { v, id });
188                for k in range.clone() {
189                    self_.reference[k as usize] = Some(v);
190                }
191            }
192            self_
193        }
194
195        /// Calls `rangemap_mutate_range`, but also updates `reference`, makes some checks, etc.
196        fn mutate_range<E: Debug>(
197            &mut self,
198            range0: Range,
199            mut real_update: impl FnMut(&Range, &mut Option<Value>) -> Result<(), E>,
200        ) -> Result<(), E> {
201            let mut updated = [false; 256];
202            println!("updating range0={range0:?}");
203
204            let r = rangemap_mutate_range(
205                &mut self.map,
206                &range0,
207                |value: &mut Option<Value>, range| {
208                    println!("updating range0={range:?} range={range:?}");
209                    assert!(range0.contains(range.start()), "uncontained start");
210                    assert!(range0.contains(range.end()), "uncontained end");
211                    let r = real_update(range, value);
212                    println!("updating range0={range0:?} range={range:?}, to {value:?}, r={r:?}");
213                    for k in range.clone() {
214                        updated[k as usize] = true;
215                        self.reference[k as usize] = value.as_ref().map(|value| value.v);
216                    }
217                    r
218                },
219            );
220            self.check();
221
222            if r.is_ok() {
223                for k in ALL_K {
224                    assert_eq!(
225                        updated[k as usize],
226                        range0.contains(&k),
227                        "updated inconsistency k={k:?}",
228                    );
229                }
230            }
231
232            r
233        }
234
235        fn set_range(&mut self, range0: Range, val: char, expected_old_values: &str) {
236            let id = self.ids.next();
237            self.mutate_range(range0.clone(), |k, vmut| {
238                assert!(
239                    expected_old_values.contains(vmut.as_ref().map(|v| v.v).unwrap_or('_')),
240                    "{range0:?} {k:?} {val:?} {vmut:?} {expected_old_values:?}"
241                );
242                *vmut = Some(Value { v: val, id });
243                Ok::<_, Void>(())
244            })
245            .void_unwrap();
246        }
247
248        fn check(&self) {
249            for k in ALL_K {
250                assert_eq!(
251                    self.map.get(&k).map(|v| v.v),
252                    self.reference[k as usize],
253                    "map now implies wrong v at k={k:?}",
254                );
255            }
256        }
257    }
258
259    #[test]
260    fn mutations() {
261        let s0 = TestState::from_iter([
262            //
263            (0..=9, 'a'),
264            (20..=29, 'x'),
265        ]);
266
267        {
268            // mutate precisely an existing range
269            let mut s = s0.clone();
270            s.set_range(0..=9, 'b', "a");
271        }
272        {
273            // mutate precisely a gap
274            let mut s = s0.clone();
275            s.mutate_range(10..=19, |_k, v| {
276                assert_eq!(*v, None);
277                Ok::<_, Void>(())
278            })
279            .void_unwrap();
280            assert_eq!(s, s0);
281            s.set_range(10..=19, 'n', "_");
282        }
283        {
284            // mutate strictly a subset of an existing range
285            let mut s = s0.clone();
286            s.set_range(1..=8, 'b', "a");
287            // now mutate parts of several ranges, with no gap in between, including a singleton
288            s.set_range(7..=9, 'c', "ab");
289        }
290        {
291            // mutate parts of several ranges, with a gap in between
292            let mut s = s0.clone();
293            s.set_range(5..=25, 'm', "_ax");
294        }
295        {
296            // mutate parts of several ranges, throwing and error halfway through
297            let mut s = s0.clone();
298            s.mutate_range(5..=25, |k, v| {
299                *v = Some(Value { v: 'm', id: 1000 });
300                (*k.start() < 15).then_some(()).ok_or(())
301            })
302            .expect_err("terminated early");
303        }
304        {
305            let mut s = s0.clone();
306            let range = 0..=9;
307            // overwrite the whole range with the same value
308            // if the underlying insert call were made,
309            // the value in the range would be replaced
310            s.set_range(range.clone(), 'a', "a");
311            // check that the thing at 1, the start of the "mutated" range,
312            // is in fact *not* the value we inserted but the existing one.
313            // this checks that our own PartialEq skip is workign
314            let ent = s.map.get(&1).unwrap();
315            assert_eq!(ent.id, 0);
316            // check that our assumption about rangemap is true
317            let id = s.ids.next();
318            s.map.insert(range.clone(), Value { v: 'a', id });
319            let ent = s.map.get(&1).unwrap();
320            assert_eq!(ent.id, id);
321        }
322    }
323}