Skip to main content

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}