hv_lease_tracker/
lib.rs

1//! Heavy Lease Tracker - functionality for tracking borrows and providing more helpful errors on
2//! runtime borrowing/aliasing violations.
3//!
4//! - [`LeaseTracker`] type, for use in smart cells like `hv-cell`'s `AtomicRefCell` (only on debug
5//!   with `track-leases` feature enabled)
6//! - [`Lease`] type, for use in smart references/guard types like `hv-cell`'s
7//!   `AtomicRef`/`AtomicRefMut` and `ArcRef`/`ArcRefMut`
8//! - [`OpenLease`] type, representing the origin of a dynamic borrow for diagnostic usage
9//!
10//! `no_std` compatible, but requires `alloc` for `Arc`s, `Cow`s, and `String`s and uses spinlocks
11//! internally to [`LeaseTracker`] for synchronizing adding/removing [`OpenLease`]s.
12//!
13//! `hv-lease-tracker` is not a super performant crate, and should probably be disabled in your
14//! release builds if performance of borrows is critical. It is strictly for diagnostic info.
15
16#![no_std]
17#![warn(missing_docs)]
18
19use core::{num::NonZeroUsize, panic::Location};
20
21extern crate alloc;
22
23use alloc::{borrow::Cow, format, sync::Arc, vec::Vec};
24use slab::Slab;
25use spin::Mutex;
26
27/// A handle to an [`OpenLease`]. Created from a [`LeaseTracker`], and carries an index pointing to
28/// debug information about that lease inside its tracker. This type is a drop guard and should be
29/// kept in your smart reference/guard type; when dropped, it removes its associated lease from its
30/// tracker.
31///
32/// It is not clone and as such when cloning something like a shared reference guard type, you'll
33/// need to re-[`Lease`] using the tracker reference from [`Lease::tracker`].
34#[derive(Debug)]
35pub struct Lease {
36    key: NonZeroUsize,
37    tracker: LeaseTracker,
38}
39
40impl Lease {
41    /// Get a reference to the lease tracker this `Lease` came from. Useful for getting another
42    /// lease from the same tracker.
43    pub fn tracker(&self) -> &LeaseTracker {
44        &self.tracker
45    }
46}
47
48impl Drop for Lease {
49    fn drop(&mut self) {
50        self.tracker.remove_lease(self.key);
51    }
52}
53
54/// An [`OpenLease`] represents the origin of an ongoing dynamic borrow. Stored as an entry for a
55/// [`Lease`] inside a [`LeaseTracker`].
56#[derive(Debug, Clone)]
57pub struct OpenLease {
58    kind: Option<&'static str>,
59    name: Cow<'static, str>,
60}
61
62impl OpenLease {
63    /// An optional human-readable string identifying the borrow kind (most likely mutable or
64    /// immutable.)
65    pub fn kind(&self) -> Option<&str> {
66        self.kind
67    }
68
69    /// A human-readable string identifying the source of this borrow. Most of the time, created
70    /// automatically from the [`Location`] API, containing the source file/line.
71    pub fn name(&self) -> &str {
72        &self.name
73    }
74}
75
76/// A registry which tracks the origins of dynamic borrows to provide better debug information on a
77/// borrow error.
78#[derive(Debug, Clone, Default)]
79pub struct LeaseTracker {
80    leases: Arc<Mutex<Slab<OpenLease>>>,
81}
82
83impl LeaseTracker {
84    /// Create an empty [`LeaseTracker`].
85    pub fn new() -> Self {
86        Default::default()
87    }
88
89    /// Register a lease using [`Location`] info from the caller to generate the `name` field of the
90    /// [`OpenLease`].
91    #[track_caller]
92    pub fn lease_at_caller(&self, kind: Option<&'static str>) -> Lease {
93        let location = Location::caller();
94        self.lease_with(
95            kind,
96            Cow::Owned(format!(
97                "{} (line {}, column {})",
98                location.file(),
99                location.line(),
100                location.column()
101            )),
102        )
103    }
104
105    /// Register a lease using a custom name string rather than generating it from caller
106    /// information.
107    pub fn lease_with(&self, kind: Option<&'static str>, name: Cow<'static, str>) -> Lease {
108        let mut leases = self.leases.lock();
109        let entry = leases.vacant_entry();
110        let lease = Lease {
111            key: NonZeroUsize::new(entry.key() + 1).unwrap(),
112            tracker: self.clone(),
113        };
114        entry.insert(OpenLease { kind, name });
115        lease
116    }
117
118    fn remove_lease(&self, key: NonZeroUsize) {
119        self.leases.lock().remove(key.get() - 1);
120    }
121
122    /// Iterate over all currently open leases.
123    pub fn current_leases(&self) -> impl IntoIterator<Item = OpenLease> {
124        self.leases
125            .lock()
126            .iter()
127            .map(|(_, open)| open.clone())
128            .collect::<Vec<_>>()
129    }
130}