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}