summaryrefslogtreecommitdiff
path: root/crates/tor-memquota/src/refcount.rs
blob: 153de07cca59cf2b0fb61702a3c9ee8de7b4436f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//! Helpers for reference counting
//!
//! Two main purposes:
//!
//!  * Consistent handling of overflow and underflow
//!  * Assurance of incrementing/decrementing as appropriate,
//!    including in combination with a slotmap containing the referenced data.
//!
//! The caller is responsible for making sure that the *right instance*'s
//! [`Count`] is passed to the methods on [`Ref`].
//
// There are no separate tests for this module.  Many of the tests would want to
// exercise the `Ref`s drop bomb, which is troublesome since it's panic in drop,
// which they're making Rust treat as an abort upstream.
// (This scheme did detect a bug or two during development testing,
// so the drop bomb is known to work.)
//
// Anyway, these functions are mostly newtype veneers over existing functionality.
// They're tested by the MemoryQuotaTracker's tests.

use crate::internal_prelude::*;

/// Local alias for the counter type
pub(crate) type RawCount = u32;

/// Decrement a refcount and maybe remove a corresponding slotmap entry
///
/// ```rust,ignore
/// fn slotmap_dec_ref!<K, V>(
///    slotmap: &mut SlotMap<K, V>,
///    ref_: Ref<K>,
///    refcount: &mut Count<K>, // (typically) borrows from slotmap
/// )
/// ```
//
// This macro is a bit out-of-position, up here, because we want to be able to link
// to it in our rustdocs.
macro_rules! slotmap_dec_ref { { $slotmap:expr, $ref_:expr, $refcount:expr } => { { {
    use $crate::refcount::*;
    let key: Ref<_> = $ref_;
    let refcount: &mut Count<_> = $refcount;
    if let Some(Garbage(key)) = key.dispose(refcount) {
        let slotmap: &mut SlotMap<_, _> = $slotmap;
        let removed = slotmap.remove(key).expect("entry vanished or wrong key passed?!");
        Some(Garbage(removed))
    } else {
        None
    }
} } } }

/// A reference count, counting references with id type `K`
#[derive(Default, Educe, Ord, PartialOrd, Eq, PartialEq, Deref)]
#[educe(Debug)]
pub(crate) struct Count<K> {
    /// Actual count of references
    #[deref]
    count: RawCount,
    /// Bind to the specific key type
    // K is generally Send + Sync + 'static so we don't care about variance etc.
    #[educe(Debug(ignore))]
    marker: PhantomData<K>,
}

/// An copy of a [`slotmap_careful::Key`] `K`, which is counted by a `RefCount`
///
/// Ie, a key of type `K` with the property that it
/// keeps the refcounted data structure alive.
///
/// Must always be deleted using [`dispose`](Ref::dispose), not dropped.
/// In tests, dropping a `RefCounted` will panic.
///
/// The `Default` value does *not* contribute to a reference count,
/// and is fine to drop.
#[derive(Deref, Educe)]
#[educe(Debug, Default, Ord, Eq, PartialEq)]
pub(crate) struct Ref<K: slotmap_careful::Key> {
    /// Actual key (without generics)
    #[deref]
    raw_key: K,
    /// Bind to the specific key type
    #[educe(Debug(ignore))]
    marker: PhantomData<K>,
    /// Drop bomb
    ///
    /// Also forces `Ref` not to be Clone
    #[educe(Debug(ignore), Ord(ignore), Eq(ignore), PartialEq(ignore))]
    #[allow(dead_code)]
    bomb: DropBombCondition,
}

// educe's Ord is open-coded and triggers clippy::non_canonical_partial_ord_impl
impl<K: slotmap_careful::Key> PartialOrd for Ref<K> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// Ideally we'd assert_not_impl on Ref but it has generics
assert_not_impl_any!(DropBombCondition: Clone);

/// Error: refcount overflowed
#[derive(Debug, Clone, Error, Eq, PartialEq)]
#[error("memory tracking refcount overflowed")]
pub(crate) struct Overflow;

/// Something which has become garbage
///
/// Often used within `Option`, for clarity.  Examples:
///
///  * Key whose reference count has reached zero - see [`Ref::dispose`]
///  * Value removed from a SlotMap - see [`slotmap_dec_ref!`]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct Garbage<K>(pub(crate) K);

impl<K> Count<K> {
    /// Make a new refcount with a specified value
    const fn new_raw(count: RawCount) -> Self {
        Count {
            count,
            marker: PhantomData,
        }
    }

    /// Obtain this counter as a `usize`
    ///
    /// (Reference counts are `u32`, so this might be a conversion.)
    pub(crate) fn as_usize(&self) -> usize {
        // On a 16-bit platform this could theoretically overflow,
        // but there would have to be >2^16 clones, which would be impossible.
        let r: u32 = **self;
        r as usize
    }
}

/// Increment this refcount, but don't care about any [`Ref`]s
fn inc_raw(c: &mut RawCount) -> Result<(), Overflow> {
    *c = c.checked_add(1).ok_or(Overflow)?;
    Ok(())
}

/// Decrement this refcount, but don't care about any [`Ref`]s
///
/// Returns `Some(Garbage(()))` if the count reached zero.
fn dec_raw(c: &mut RawCount) -> Option<Garbage<()>> {
    *c = c
        .checked_sub(1)
        // if this happens, our data structure is corrupted, very bad
        .expect("refcount underflow");
    (*c == 0).then_some(Garbage(()))
}

impl<K: slotmap_careful::Key> Ref<K> {
    /// Create a refcounted reference `Ref` from an un-counted key, incrementing the count
    pub(crate) fn new(key: K, count: &mut Count<K>) -> Result<Self, Overflow> {
        inc_raw(&mut count.count)?;
        Ok(Ref::from_raw(key))
    }

    /// Creates a null `Ref`, which doesn't refer to any slot (lookups always give `None`)
    pub(crate) fn null() -> Self {
        Ref::from_raw(K::null())
    }

    /// Internal function for creating a `Ref`
    fn from_raw(raw_key: K) -> Self {
        Ref {
            raw_key,
            marker: PhantomData,
            bomb: DropBombCondition::new_armed(),
        }
    }

    /// Dispose of a refcounted reference `Ref`, decrementing the count
    ///
    /// If the count reaches zero, the raw key is returned;
    /// the caller should remove the corresponding data from the data structure.
    pub(crate) fn dispose(mut self, refcount: &mut Count<K>) -> Option<Garbage<K>> {
        let was = mem::take(&mut self.raw_key);
        assert!(!was.is_null());
        dec_raw(&mut refcount.count).map(|_: Garbage<()>| Garbage(was))
    }

    /// Dispose of a refcounted reference whose container no longer exists
    ///
    /// # CORRECTNESS
    ///
    /// This just forgets the reference, without decrementing any refcount.
    /// If the container *does* still exist, a ref count ref will be leaked.
    pub(crate) fn dispose_container_destroyed(mut self) {
        let _: K = mem::take(&mut self.raw_key);
    }
}

impl<K: slotmap_careful::Key> DefaultExtTake for Ref<K> {}

/// Insert a new entry into a slotmap using refcounted keys
///
/// `value_maker` should take the provided `Count`,
/// and incorporate it into a new value.
///
/// On return, the entry will be in the map, and there will be one reference,
/// which is returned.
///
/// There is no corresponding `slotmap_remove` in this module.
/// Use [`Ref::dispose`] and handle any [`Garbage`] it returns.
pub(crate) fn slotmap_insert<K: slotmap_careful::Key, V>(
    slotmap: &mut SlotMap<K, V>,
    value_maker: impl FnOnce(Count<K>) -> V,
) -> Ref<K> {
    let (ref_, ()) = slotmap_try_insert(slotmap, move |refcount| {
        Ok::<_, Void>((value_maker(refcount), ()))
    })
    .void_unwrap();
    ref_
}

/// Insert a new entry into a slotmap using refcounted keys, fallibly and with extra data
///
/// Like [`slotmap_insert`] but:
///  * `value_maker` can also return extra return data `RD` to the caller
///  * `value_maker` is allowed to fail.
///
/// On successful return, the entry will be in the map, and
/// the new `Ref` is returned along with the data `D`.
pub(crate) fn slotmap_try_insert<K: slotmap_careful::Key, V, E, RD>(
    slotmap: &mut SlotMap<K, V>,
    value_maker: impl FnOnce(Count<K>) -> Result<(V, RD), E>,
) -> Result<(Ref<K>, RD), E> {
    let refcount = Count::new_raw(1);
    let (value, data) = value_maker(refcount)?;
    let raw_key = slotmap.insert(value);
    let ref_ = Ref {
        raw_key,
        marker: PhantomData,
        bomb: DropBombCondition::new_armed(),
    };
    Ok((ref_, data))
}

/// Unconditionally remove en entry from the slotmap, given a strong ref
///
/// Other references to this entry will become dangling.
pub(crate) fn slotmap_remove_early<K: slotmap_careful::Key, V>(
    slotmap: &mut SlotMap<K, V>,
    key: Ref<K>,
) -> Option<V> {
    let r = slotmap.remove(*key);
    // Correctness: this becomes a reference to a missing entry
    key.dispose_container_destroyed();
    r
}

#[cfg(test)]
impl<K: slotmap_careful::Key> Drop for Ref<K> {
    fn drop(&mut self) {
        drop_bomb_disarm_assert!(self.bomb, self.raw_key.is_null(),);
    }
}

impl From<Overflow> for Error {
    fn from(_overflow: Overflow) -> Error {
        internal!("reference count overflow in memory tracking (out-of-control subsystem?)").into()
    }
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_time_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    #![allow(clippy::string_slice)] // See arti#2571
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    #![allow(clippy::let_and_return)] // TODO this lint is annoying and we should disable it

    use super::*;

    slotmap_careful::new_key_type! {
        struct Id;
    }
    #[derive(Eq, PartialEq, Debug)]
    struct Record {
        refcount: Count<Id>,
    }
    type Map = SlotMap<Id, Record>;

    fn setup() -> (Map, Ref<Id>) {
        let mut map = Map::default();
        let ref_ = slotmap_insert(&mut map, |refcount| Record { refcount });
        (map, ref_)
    }

    #[test]
    fn good() {
        let (mut map, ref1) = setup();

        let ent = map.get_mut(*ref1).unwrap();
        let ref2 = Ref::new(*ref1, &mut ent.refcount).unwrap();

        let g1: Option<Garbage<Record>> = slotmap_dec_ref!(&mut map, ref1, &mut ent.refcount);
        assert_eq!(g1, None);

        let ent = map.get_mut(*ref2).unwrap();
        let g2: Option<Garbage<Record>> = slotmap_dec_ref!(&mut map, ref2, &mut ent.refcount);
        assert!(g2.is_some());
    }

    #[test]
    fn try_insert_fail() {
        let mut map = Map::default();
        let () = slotmap_try_insert::<_, _, _, String>(&mut map, |_refcount| Err(())).unwrap_err();
    }

    #[test]
    fn drop_ref_without_decrement() {
        let (_map, mut ref1) = setup();
        let h = ref1.bomb.make_simulated();
        drop(ref1);
        h.expect_exploded();
    }
}