weaveffi/abi/leak.rs
1//! Debug leak counters behind the `leak-check` cargo feature.
2//!
3//! With the feature on, the runtime counts every resource it hands to a
4//! consumer and every release, so a test harness can assert that a consumer
5//! released everything it was given (after forcing its garbage collector or
6//! finalizers to run). `export_runtime!` exports the counters as
7//! `uint64_t {prefix}_debug_live(int32_t kind)`.
8//!
9//! The symbol is exported whether or not the feature is on, so a consumer
10//! never has to know how the producer was built: without the feature every
11//! counter reads `0`. Ask for kind [`COUNTING`] (`-1`) to tell "nothing is
12//! live" from "nothing is counted".
13
14/// Whether this build counts live resources (the `leak-check` feature).
15pub const ENABLED: bool = cfg!(feature = "leak-check");
16
17/// The pseudo-kind `{prefix}_debug_live` answers with `1` when the producer
18/// counts live resources ([`ENABLED`]) and `0` when it doesn't.
19pub const COUNTING: i32 = -1;
20
21/// Interface object references held by the consumer: one per pointer a
22/// thunk returns, per `_clone`, and per object token written into a value
23/// buffer; released by `_destroy` and by adopting a token.
24pub const OBJECTS: i32 = 0;
25
26/// Consumer callback-interface implementations the producer still holds
27/// (each is released through the vtable's `free` entry).
28pub const CALLBACKS: i32 = 1;
29
30/// Iterator handles a launcher returned that haven't been destroyed.
31pub const ITERATORS: i32 = 2;
32
33/// Cancel-token references, both the consumer's (`create` until `destroy`)
34/// and the producer's (one per in-flight cancellable call).
35pub const TOKENS: i32 = 3;
36
37/// Returned string, bytes, and value-buffer allocations (including error
38/// payloads) that haven't been passed to `{prefix}_free_bytes`.
39pub const ALLOCATIONS: i32 = 4;
40
41#[cfg(feature = "leak-check")]
42mod counters {
43 use std::sync::atomic::{AtomicI64, Ordering};
44
45 static LIVE: [AtomicI64; 5] = [
46 AtomicI64::new(0),
47 AtomicI64::new(0),
48 AtomicI64::new(0),
49 AtomicI64::new(0),
50 AtomicI64::new(0),
51 ];
52
53 pub(super) fn track(kind: i32, delta: i64) {
54 if let Some(c) = usize::try_from(kind).ok().and_then(|k| LIVE.get(k)) {
55 c.fetch_add(delta, Ordering::Relaxed);
56 }
57 }
58
59 pub(super) fn live(kind: i32) -> u64 {
60 usize::try_from(kind)
61 .ok()
62 .and_then(|k| LIVE.get(k))
63 .map_or(0, |c| u64::try_from(c.load(Ordering::Relaxed)).unwrap_or(0))
64 }
65}
66
67/// Adjust the live count of `kind` by `delta`. Compiles to nothing without
68/// the `leak-check` feature.
69#[inline]
70pub(crate) fn track(kind: i32, delta: i64) {
71 #[cfg(feature = "leak-check")]
72 counters::track(kind, delta);
73 #[cfg(not(feature = "leak-check"))]
74 let _ = (kind, delta);
75}
76
77/// The body of `{prefix}_debug_live`: how many resources of `kind` (one of
78/// [`OBJECTS`], [`CALLBACKS`], [`ITERATORS`], [`TOKENS`], or
79/// [`ALLOCATIONS`]) are live right now, or, for [`COUNTING`], `1` when this
80/// build counts at all.
81///
82/// Returns `0` for an unknown kind, and for every counter when the
83/// `leak-check` feature is off.
84#[must_use]
85pub fn debug_live(kind: i32) -> u64 {
86 if kind == COUNTING {
87 return u64::from(ENABLED);
88 }
89 #[cfg(feature = "leak-check")]
90 {
91 counters::live(kind)
92 }
93 #[cfg(not(feature = "leak-check"))]
94 {
95 let _ = kind;
96 0
97 }
98}