Skip to main content

canic_memory/runtime/
mod.rs

1pub mod registry;
2
3use std::sync::{
4    Mutex,
5    atomic::{AtomicBool, Ordering},
6};
7
8// -----------------------------------------------------------------------------
9// CANIC_EAGER_TLS
10// -----------------------------------------------------------------------------
11// Internal registry of "TLS touch" functions.
12//
13// Each function must be a plain `fn()` pointer (not a closure). When invoked,
14// the function must perform a `.with(|_| {})` on a thread_local! static.
15// This guarantees that the TLS slot is *initialized eagerly*, not lazily, so
16// stable memory pages or other backing buffers are allocated in a deterministic
17// order before any canister entry points are executed.
18//
19// These functions are registered by the `eager_static!` macro via
20// `defer_tls_initializer()`, and run once during process startup by
21// `init_eager_tls()`.
22// -----------------------------------------------------------------------------
23
24static CANIC_EAGER_TLS: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
25static CANIC_EAGER_TLS_RUNNING: AtomicBool = AtomicBool::new(false);
26static CANIC_EAGER_INIT: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
27
28/// Run all deferred TLS initializers and clear the registry.
29///
30/// This drains the internal queue of initializer functions and invokes
31/// each *exactly once*. The use of `std::mem::take` ensures:
32///
33/// - the vector is fully emptied before we run any initializers
34/// - we drop the borrow before calling user code (prevents borrow panics)
35/// - functions cannot be re-run accidentally
36/// - reentrant modifications of the queue become visible *after* this call
37///
38/// This should be invoked before any IC canister lifecycle hooks (init, update,
39/// heartbeat, etc.) so that thread-local caches are in a fully-initialized state
40/// before the canister performs memory-dependent work.
41pub fn init_eager_tls() {
42    ///
43    /// RunningGuard
44    ///
45
46    struct RunningGuard;
47
48    impl Drop for RunningGuard {
49        fn drop(&mut self) {
50            CANIC_EAGER_TLS_RUNNING.store(false, Ordering::SeqCst);
51        }
52    }
53
54    CANIC_EAGER_TLS_RUNNING.store(true, Ordering::SeqCst);
55    let _running_guard = RunningGuard;
56
57    let funcs = {
58        let mut funcs = CANIC_EAGER_TLS.lock().expect("eager tls queue poisoned");
59        std::mem::take(&mut *funcs)
60    };
61
62    debug_assert!(
63        CANIC_EAGER_TLS
64            .lock()
65            .expect("eager tls queue poisoned")
66            .is_empty(),
67        "CANIC_EAGER_TLS was modified during init_eager_tls() execution"
68    );
69
70    for f in funcs {
71        f();
72    }
73}
74
75/// Run all registered eager-init hooks and clear the registry.
76///
77/// This drains the internal queue of eager-init functions and invokes each
78/// exactly once. Canic uses this during synchronous lifecycle bootstrap after
79/// eager TLS initialization and before the memory registry is committed.
80pub fn run_registered_eager_init() {
81    let funcs = {
82        let mut funcs = CANIC_EAGER_INIT.lock().expect("eager init queue poisoned");
83        std::mem::take(&mut *funcs)
84    };
85
86    debug_assert!(
87        CANIC_EAGER_INIT
88            .lock()
89            .expect("eager init queue poisoned")
90            .is_empty(),
91        "CANIC_EAGER_INIT was modified during eager init execution"
92    );
93
94    for f in funcs {
95        f();
96    }
97}
98
99#[must_use]
100pub fn is_eager_tls_initializing() -> bool {
101    CANIC_EAGER_TLS_RUNNING.load(Ordering::SeqCst)
102}
103
104#[must_use]
105pub fn is_memory_bootstrap_ready() -> bool {
106    is_eager_tls_initializing() || registry::MemoryRegistryRuntime::is_initialized()
107}
108
109pub fn assert_memory_bootstrap_ready(label: &str, id: u8) {
110    if is_memory_bootstrap_ready() {
111        return;
112    }
113
114    panic!(
115        "stable memory slot '{label}' (id {id}) accessed before memory bootstrap; call init_eager_tls() and MemoryRegistryRuntime::init(...) first"
116    );
117}
118
119/// Register a TLS initializer function for eager execution.
120///
121/// This is called by the `eager_static!` macro. The function pointer `f`
122/// must be a zero-argument function (`fn()`) that performs a `.with(|_| {})`
123/// on the thread-local static it is meant to initialize.
124pub fn defer_tls_initializer(f: fn()) {
125    CANIC_EAGER_TLS
126        .lock()
127        .expect("eager tls queue poisoned")
128        .push(f);
129}
130
131/// Register an eager-init function for lifecycle bootstrap execution.
132pub fn defer_eager_init(f: fn()) {
133    CANIC_EAGER_INIT
134        .lock()
135        .expect("eager init queue poisoned")
136        .push(f);
137}
138
139///
140/// TESTS
141///
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::sync::atomic::{AtomicU32, Ordering};
147
148    static COUNT: AtomicU32 = AtomicU32::new(0);
149
150    fn bump() {
151        COUNT.fetch_add(1, Ordering::SeqCst);
152    }
153
154    #[test]
155    fn init_eager_tls_runs_and_clears_queue() {
156        COUNT.store(0, Ordering::SeqCst);
157        CANIC_EAGER_TLS
158            .lock()
159            .expect("eager tls queue poisoned")
160            .push(bump);
161        init_eager_tls();
162        let first = COUNT.load(Ordering::SeqCst);
163        assert_eq!(first, 1);
164
165        // second call sees empty queue
166        init_eager_tls();
167        let second = COUNT.load(Ordering::SeqCst);
168        assert_eq!(second, 1);
169    }
170
171    #[test]
172    fn run_registered_eager_init_runs_and_clears_queue() {
173        COUNT.store(0, Ordering::SeqCst);
174        CANIC_EAGER_INIT
175            .lock()
176            .expect("eager init queue poisoned")
177            .push(bump);
178        run_registered_eager_init();
179        let first = COUNT.load(Ordering::SeqCst);
180        assert_eq!(first, 1);
181
182        // second call sees empty queue
183        run_registered_eager_init();
184        let second = COUNT.load(Ordering::SeqCst);
185        assert_eq!(second, 1);
186    }
187}