summaryrefslogtreecommitdiff
path: root/crates/tor-memtrack/src/refcount.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/tor-memtrack/src/refcount.rs')
-rw-r--r--crates/tor-memtrack/src/refcount.rs315
1 files changed, 0 insertions, 315 deletions
diff --git a/crates/tor-memtrack/src/refcount.rs b/crates/tor-memtrack/src/refcount.rs
deleted file mode 100644
index cbe17b7f0..000000000
--- a/crates/tor-memtrack/src/refcount.rs
+++ /dev/null
@@ -1,315 +0,0 @@
-//! 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::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::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::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::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::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::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::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))
-}
-
-#[cfg(test)]
-impl<K: slotmap::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_duration_subtraction)]
- #![allow(clippy::useless_vec)]
- #![allow(clippy::needless_pass_by_value)]
- //! <!-- @@ 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::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();
- }
-}