canic_memory/
runtime.rs

1use std::cell::RefCell;
2
3// -----------------------------------------------------------------------------
4// CANIC_EAGER_TLS
5// -----------------------------------------------------------------------------
6// Internal registry of "TLS touch" functions.
7//
8// Each function must be a plain `fn()` pointer (not a closure). When invoked,
9// the function must perform a `.with(|_| {})` on a thread_local! static.
10// This guarantees that the TLS slot is *initialized eagerly*, not lazily, so
11// stable memory pages or other backing buffers are allocated in a deterministic
12// order before any canister entry points are executed.
13//
14// These functions are registered by the `eager_static!` macro via
15// `defer_tls_initializer()`, and run once during process startup by
16// `init_eager_tls()`.
17// -----------------------------------------------------------------------------
18
19thread_local! {
20    static CANIC_EAGER_TLS: RefCell<Vec<fn()>> = const {
21        RefCell::new(Vec::new())
22    };
23}
24
25/// Run all deferred TLS initializers and clear the registry.
26///
27/// This drains the internal queue of initializer functions and invokes
28/// each *exactly once*. The use of `std::mem::take` ensures:
29///
30/// - the vector is fully emptied before we run any initializers
31/// - we drop the borrow before calling user code (prevents borrow panics)
32/// - functions cannot be re-run accidentally
33/// - reentrant modifications of the queue become visible *after* this call
34///
35/// This should be invoked before any IC canister lifecycle hooks (init, update,
36/// heartbeat, etc.) so that thread-local caches are in a fully-initialized state
37/// before the canister performs memory-dependent work.
38pub fn init_eager_tls() {
39    let funcs = CANIC_EAGER_TLS.with(|v| {
40        let mut v = v.borrow_mut();
41        std::mem::take(&mut *v)
42    });
43
44    debug_assert!(
45        CANIC_EAGER_TLS.with(|v| v.borrow().is_empty()),
46        "CANIC_EAGER_TLS was modified during init_eager_tls() execution"
47    );
48
49    for f in funcs {
50        f();
51    }
52}
53
54/// Register a TLS initializer function for eager execution.
55///
56/// This is called by the `eager_static!` macro. The function pointer `f`
57/// must be a zero-argument function (`fn()`) that performs a `.with(|_| {})`
58/// on the thread-local static it is meant to initialize.
59pub fn defer_tls_initializer(f: fn()) {
60    CANIC_EAGER_TLS.with_borrow_mut(|v| v.push(f));
61}
62
63///
64/// TESTS
65///
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use std::cell::Cell;
71
72    thread_local! {
73        static COUNT: Cell<u32> = const { Cell::new(0) };
74    }
75
76    fn bump() {
77        COUNT.with(|c| c.set(c.get() + 1));
78    }
79
80    #[test]
81    fn init_eager_tls_runs_and_clears_queue() {
82        COUNT.with(|c| c.set(0));
83        CANIC_EAGER_TLS.with(|v| v.borrow_mut().push(bump));
84        init_eager_tls();
85        let first = COUNT.with(Cell::get);
86        assert_eq!(first, 1);
87
88        // second call sees empty queue
89        init_eager_tls();
90        let second = COUNT.with(Cell::get);
91        assert_eq!(second, 1);
92    }
93}