canic_memory/runtime/
mod.rs

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