Skip to main content

ic_memory/runtime/
default.rs

1use super::{
2    MemoryRuntime, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
3    RuntimeMemory, RuntimeOpenError, RuntimeStateError, policy::NoopPolicy,
4};
5use crate::{
6    CommittedAllocations, DiagnosticExport, MemoryRuntimeDoctorReport, RuntimeBootstrapPolicy,
7    physical::CommitStoreDiagnostic, registry::sealed_declaration_snapshot,
8};
9use ic_stable_structures::DefaultMemoryImpl;
10use std::{cell::RefCell, convert::Infallible, fmt::Display};
11
12thread_local! {
13    static DEFAULT_RUNTIME:
14        RefCell<Option<Result<MemoryRuntime<DefaultMemoryImpl>, RuntimeConstructionError>>> =
15        const { RefCell::new(None) };
16}
17
18fn with_default_runtime<T, E>(
19    operation: impl FnOnce(&MemoryRuntime<DefaultMemoryImpl>) -> Result<T, E>,
20) -> Result<T, E>
21where
22    E: From<RuntimeStateError>,
23{
24    match DEFAULT_RUNTIME.try_with(|runtime| {
25        let mut runtime = runtime
26            .try_borrow_mut()
27            .map_err(|_| E::from(RuntimeStateError::ReentrantAccess))?;
28        let runtime = runtime
29            .get_or_insert_with(|| MemoryRuntime::new(DefaultMemoryImpl::default()))
30            .as_ref()
31            .map_err(|error| E::from(RuntimeStateError::Construction(*error)))?;
32        operation(runtime)
33    }) {
34        Ok(result) => result,
35        Err(_) => Err(E::from(RuntimeStateError::Unavailable)),
36    }
37}
38
39fn with_default_runtime_mut<T, E>(
40    operation: impl FnOnce(&mut MemoryRuntime<DefaultMemoryImpl>) -> Result<T, E>,
41) -> Result<T, E>
42where
43    E: From<RuntimeStateError>,
44{
45    match DEFAULT_RUNTIME.try_with(|runtime| {
46        let mut runtime = runtime
47            .try_borrow_mut()
48            .map_err(|_| E::from(RuntimeStateError::ReentrantAccess))?;
49        let runtime = runtime
50            .get_or_insert_with(|| MemoryRuntime::new(DefaultMemoryImpl::default()))
51            .as_mut()
52            .map_err(|error| E::from(RuntimeStateError::Construction(*error)))?;
53        operation(runtime)
54    }) {
55        Ok(result) => result,
56        Err(_) => Err(E::from(RuntimeStateError::Unavailable)),
57    }
58}
59
60/// Return whether this thread's default runtime has completed bootstrap.
61pub fn is_default_memory_manager_bootstrapped() -> Result<bool, RuntimeStateError> {
62    with_default_runtime(|runtime| Ok(runtime.is_bootstrapped()))
63}
64
65/// Return this thread's default runtime committed allocation capability.
66pub fn committed_allocations() -> Result<CommittedAllocations, RuntimeOpenError> {
67    with_default_runtime(|runtime| runtime.committed_allocations().cloned())
68}
69
70/// Bootstrap this thread's default runtime using generic range policy.
71pub fn bootstrap_default_memory_manager()
72-> Result<CommittedAllocations, RuntimeBootstrapError<Infallible>> {
73    bootstrap_default_memory_manager_with_policy(&NoopPolicy)
74}
75
76/// Bootstrap this thread's default runtime with caller-supplied policy.
77///
78/// Static declarations are sealed once per linked program. Recovery, policy
79/// evaluation, persistence, and capability publication occur once for this
80/// concrete TLS runtime. Repeated calls must supply the policy identity bound
81/// by the successful bootstrap.
82pub fn bootstrap_default_memory_manager_with_policy<P: RuntimeBootstrapPolicy>(
83    policy: &P,
84) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
85    let declarations = sealed_declaration_snapshot()?;
86    with_default_runtime_mut(|runtime| runtime.bootstrap(&declarations, policy).cloned())
87}
88
89/// Open a committed memory from this thread's default runtime.
90pub fn open_default_memory_manager_memory(
91    stable_key: &str,
92    id: u8,
93) -> Result<RuntimeMemory<DefaultMemoryImpl>, RuntimeOpenError> {
94    with_default_runtime(|runtime| runtime.open_memory(stable_key, id))
95}
96
97/// Export this thread's default runtime ledger and live memory sizes.
98pub fn default_memory_manager_diagnostic_export() -> Result<DiagnosticExport, RuntimeDiagnosticError>
99{
100    with_default_runtime(MemoryRuntime::diagnostic_export)
101}
102
103/// Diagnose protected commit recovery for this thread's default runtime.
104pub fn default_memory_manager_commit_recovery_diagnostic()
105-> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
106    with_default_runtime(MemoryRuntime::commit_recovery_diagnostic)
107}
108
109/// Build preflight and lifecycle diagnostics for this thread's default runtime.
110pub fn default_memory_manager_doctor_report()
111-> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError> {
112    default_memory_manager_doctor_report_with_policy(&NoopPolicy)
113}
114
115/// Build diagnostics for this thread's default runtime under one explicit policy.
116pub fn default_memory_manager_doctor_report_with_policy<P>(
117    policy: &P,
118) -> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError>
119where
120    P: RuntimeBootstrapPolicy,
121    P::Error: Display,
122{
123    let declarations = sealed_declaration_snapshot()?;
124    with_default_runtime(|runtime| Ok(runtime.doctor_report(&declarations, policy)))
125}
126
127#[cfg(test)]
128pub(super) fn with_default_runtime_borrowed(
129    operation: impl FnOnce() -> Result<(), RuntimeStateError>,
130) -> Result<(), RuntimeStateError> {
131    DEFAULT_RUNTIME.with(|runtime| {
132        let _borrow = runtime.borrow_mut();
133        operation()
134    })
135}
136
137/// Measure the existing default runtime without constructing a manager or
138/// initializing backing memory.
139///
140/// Returns `NotBootstrapped` if no runtime exists.
141/// A constructed runtime may be measured before bootstrap with unknown bindings.
142pub fn default_memory_manager_memory_allocations()
143-> Result<super::MemoryAllocations, RuntimeDiagnosticError> {
144    DEFAULT_RUNTIME
145        .try_with(|runtime| {
146            let runtime = runtime
147                .try_borrow()
148                .map_err(|_| RuntimeStateError::ReentrantAccess)?;
149            let runtime = runtime
150                .as_ref()
151                .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
152                .as_ref()
153                .map_err(|error| RuntimeStateError::Construction(*error))?;
154            runtime.memory_allocations()
155        })
156        .map_err(|_| RuntimeStateError::Unavailable)?
157}
158
159/// Bootstrap the default runtime with an explicit bucket setting and allocation
160/// policy.
161///
162/// The first construction uses this setting; repeated calls and reopened
163/// memory must match it exactly before bootstrap effects. Call this during
164/// bootstrap before any operation that would construct the default runtime.
165pub fn bootstrap_default_memory_manager_with_config<P: RuntimeBootstrapPolicy>(
166    config: super::MemoryManagerConfig,
167    policy: &P,
168) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
169    DEFAULT_RUNTIME
170        .try_with(|runtime| {
171            let mut runtime = runtime
172                .try_borrow_mut()
173                .map_err(|_| RuntimeStateError::ReentrantAccess)?;
174            let runtime = runtime
175                .get_or_insert_with(|| {
176                    MemoryRuntime::new_with_config(DefaultMemoryImpl::default(), config)
177                })
178                .as_mut()
179                .map_err(|error| RuntimeStateError::Construction(*error))?;
180            super::check_bucket_size(runtime.bucket_size_pages, config)
181                .map_err(RuntimeStateError::Construction)?;
182            let declarations = sealed_declaration_snapshot()?;
183            runtime.bootstrap(&declarations, policy).cloned()
184        })
185        .map_err(|_| RuntimeStateError::Unavailable)?
186}