Skip to main content

ic_memory/runtime/
mod.rs

1mod admission;
2#[cfg(test)]
3mod admission_tests;
4mod adoption;
5#[cfg(test)]
6mod adoption_tests;
7mod allocations;
8mod backing;
9mod bootstrap;
10mod config;
11mod construction;
12mod custody;
13mod diagnostics;
14mod error;
15#[cfg(any(test, feature = "test-support"))]
16pub mod fixture_diagnostics;
17#[cfg(test)]
18mod fixtures;
19mod layout;
20mod policy;
21
22#[cfg(all(test, not(target_arch = "wasm32")))]
23mod allocation_tests;
24#[cfg(test)]
25mod custody_tests;
26#[cfg(test)]
27mod growth_tests;
28#[cfg(test)]
29#[expect(
30    unsafe_code,
31    reason = "exercise raw reads with valid uninitialized destinations"
32)]
33mod read_tests;
34#[cfg(test)]
35mod request_tests;
36#[cfg(test)]
37mod tests;
38
39pub use admission::{BootstrapAdmission, BootstrapAdmissionError};
40pub use adoption::RuntimeAdoptionError;
41
42pub use allocations::{
43    MemoryAllocation, MemoryAllocationSummary, MemoryAllocations, MemoryBindingSummary,
44};
45pub use backing::RuntimeMemory;
46pub use config::MemoryManagerConfig;
47pub use custody::{BackingIdentity, MemoryBacking};
48pub use error::{
49    MemoryResolutionError, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
50    RuntimeGrowError, RuntimeOpenError,
51};
52pub use layout::MemoryManagerLayoutError;
53pub use policy::GenericAllocationPolicy;
54
55use crate::{
56    LedgerCommitStore, PolicyIdentity, StableCellLedgerError, requests::SealedDeclarationSnapshot,
57    slot::MEMORY_MANAGER_LEDGER_ID, stable_cell::decode_stable_cell_commit_store_from_memory,
58};
59use ic_platform::stable_structures::{
60    Memory,
61    memory_manager::{MemoryId, MemoryManager},
62};
63
64use std::rc::Rc;
65
66enum RuntimeLifecycle {
67    Unbootstrapped,
68    Bootstrapped(CommittedRuntime),
69}
70
71struct CommittedRuntime {
72    allocations: Vec<crate::AllocationDeclaration>,
73    generation: u64,
74    binding: RuntimeBootstrapBinding,
75}
76
77struct RuntimeBootstrapBinding {
78    source: SealedDeclarationSnapshot,
79    policy_identity: PolicyIdentity,
80    pool: crate::MemoryAllocationPool,
81}
82
83///
84/// MemoryRuntime
85///
86/// Canonical owner of allocation bootstrap state for one backing memory.
87///
88/// The runtime owns its `MemoryManager`, allocation-ledger persistence, bootstrap
89/// lifecycle, committed allocation capability, opens, and diagnostics. Static
90/// linked-program declarations are supplied separately as one immutable
91/// [`SealedDeclarationSnapshot`].
92///
93/// Each bootstrap attempt fallibly decodes its ledger record from persisted
94/// memory. Capacity-checked writes use `Cell`; diagnostics also read persisted
95/// memory directly.
96///
97/// Construction requires [`MemoryBacking`], which identifies physical aliases.
98/// The backing need not be `Send`, `Sync`, `Clone`, or `'static`.
99///
100/// A backing claim rejects another runtime before backing IO while this runtime
101/// or any detached lease lives. Drop them all before constructing a replacement.
102/// Standard VectorMemory/default canister backings identify aliases automatically;
103/// custom hosts must uphold [`MemoryBacking`]'s identity contract. A composition
104/// selects one IC Memory crate identity and shares one runtime. Raw aliased IO or
105/// unmanaged managers remain prohibited. Bounded metadata checks refuse stale
106/// manager state; they are not transactions against arbitrary concurrent raw IO.
107///
108
109pub struct MemoryRuntime<M: Memory> {
110    memory_manager: MemoryManager<Rc<M>>,
111    // Shared backing, immutable geometry and live accounting belong to growth.
112    // Handles reserve physical capacity; the manager owns bucket metadata.
113    growth: Rc<backing::GrowthState<M>>,
114    lifecycle: RuntimeLifecycle,
115}
116
117impl<M: Memory> MemoryRuntime<M> {
118    /// Return whether this runtime has published committed allocation authority.
119    #[must_use]
120    pub const fn is_bootstrapped(&self) -> bool {
121        matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped(_))
122    }
123
124    const fn committed_state(&self) -> Option<&CommittedRuntime> {
125        match &self.lifecycle {
126            RuntimeLifecycle::Unbootstrapped => None,
127            RuntimeLifecycle::Bootstrapped(committed) => Some(committed),
128        }
129    }
130
131    fn committed(&self) -> Result<&CommittedRuntime, RuntimeOpenError> {
132        self.committed_state()
133            .ok_or(RuntimeOpenError::NotBootstrapped)
134    }
135
136    /// Borrow this runtime's persisted application bindings without IO.
137    /// These passive views cannot open memory independently of the runtime.
138    pub fn committed_allocations(
139        &self,
140    ) -> Result<&[crate::AllocationDeclaration], RuntimeOpenError> {
141        Ok(&self.committed()?.allocations)
142    }
143
144    /// Return the in-memory persisted generation without decoding the ledger.
145    pub const fn committed_generation(&self) -> Result<u64, RuntimeOpenError> {
146        match self.committed_state() {
147            Some(committed) => Ok(committed.generation),
148            None => Err(RuntimeOpenError::NotBootstrapped),
149        }
150    }
151
152    /// Borrow the host pool bound by successful bootstrap for host projections.
153    /// This is current policy, never evidence of historical ownership or free IDs.
154    pub const fn allocation_pool(&self) -> Result<&crate::MemoryAllocationPool, RuntimeOpenError> {
155        match self.committed_state() {
156            Some(committed) => Ok(&committed.binding.pool),
157            None => Err(RuntimeOpenError::NotBootstrapped),
158        }
159    }
160
161    /// Lease by durable key using this runtime's persisted current bindings.
162    ///
163    /// A second live open returns [`RuntimeOpenError::AlreadyOpen`] without IO.
164    /// Dropping the handle, or a collection that owns it, permits reopening.
165    /// A successful new lease validates the complete persisted manager image;
166    /// detected divergence poisons all of this runtime's existing handles.
167    pub fn open_memory(&self, stable_key: &str) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
168        self.memory(self.memory_id(stable_key)?)
169    }
170
171    /// Resolve an application key's committed ID without opening memory,
172    /// reading history, or changing the host's policy or bucket configuration.
173    pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
174        crate::key::validate(stable_key)?;
175        if crate::is_ic_memory_stable_key(stable_key) {
176            return Err(RuntimeOpenError::ReservedStableKey {
177                stable_key: stable_key.to_string(),
178            });
179        }
180        let declaration =
181            crate::declaration::declaration_for_key(self.committed_allocations()?, stable_key)
182                .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
183        Ok(declaration.slot().id())
184    }
185
186    fn memory(&self, id: u8) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
187        let (word, mask) = self.growth.lease_bit(id);
188        if word.get() & mask != 0 {
189            return Err(RuntimeOpenError::AlreadyOpen { id });
190        }
191        self.growth.verify()?;
192        let memory = self.memory_manager.get(MemoryId::new(id));
193        word.set(word.get() | mask);
194        Ok(RuntimeMemory {
195            memory,
196            growth: Rc::clone(&self.growth),
197            id,
198        })
199    }
200
201    // Size-only observations need no handle carrying runtime growth authority.
202    fn memory_size_pages(&self, id: u8) -> u64 {
203        self.memory_manager.get(MemoryId::new(id)).size()
204    }
205
206    fn ledger_record_from_memory(&self) -> Result<LedgerCommitStore, StableCellLedgerError> {
207        // Decoding only reads; the manager handle retains backing lifetime
208        // without carrying the growth owner needed by writable runtime handles.
209        let memory = self
210            .memory_manager
211            .get(MemoryId::new(MEMORY_MANAGER_LEDGER_ID));
212        decode_stable_cell_commit_store_from_memory(&memory)
213    }
214}