Skip to main content

canic_memory/runtime/
mod.rs

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