canic-memory 0.27.0

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use crate::registry::{
    MemoryRange, MemoryRangeEntry, MemoryRangeSnapshot, MemoryRegistry, MemoryRegistryEntry,
    MemoryRegistryError, drain_pending_ranges, drain_pending_registrations,
};
use std::cell::Cell;

thread_local! {
    static MEMORY_REGISTRY_INITIALIZED: Cell<bool> = const { Cell::new(false) };
}

///
/// MemoryRegistryInitSummary
///
/// Substrate-level summary of registry state after initialization.
/// This is intended for diagnostics and testing only.
/// It is NOT a stable API contract or external view.
///

#[derive(Debug)]
pub struct MemoryRegistryInitSummary {
    pub ranges: Vec<(String, MemoryRange)>,
    pub entries: Vec<(u8, MemoryRegistryEntry)>,
}

///
/// MemoryRegistryRuntime
///
/// Substrate runtime controller responsible for initializing the
/// global memory registry.
///
/// This type performs mechanical coordination only:
/// - ordering
/// - conflict detection
/// - idempotent initialization
///
/// It encodes no application semantics.
///
pub struct MemoryRegistryRuntime;

impl MemoryRegistryRuntime {
    /// Initialize the memory registry.
    ///
    /// - Optionally reserves an initial range for the caller.
    /// - Applies all deferred range reservations.
    /// - Applies all deferred ID registrations.
    ///
    /// This function is idempotent for the same initial range.
    pub fn init(
        initial_range: Option<(&str, u8, u8)>,
    ) -> Result<MemoryRegistryInitSummary, MemoryRegistryError> {
        // Reserve the caller's initial range first (if provided)
        if let Some((crate_name, start, end)) = initial_range {
            MemoryRegistry::reserve_range(crate_name, start, end)?;
        }

        // Apply deferred range reservations deterministically
        let mut ranges = drain_pending_ranges();
        ranges.sort_by_key(|(_, start, _)| *start);
        for (crate_name, start, end) in ranges {
            MemoryRegistry::reserve_range(&crate_name, start, end)?;
        }

        // Apply deferred registrations deterministically
        let mut regs = drain_pending_registrations();
        regs.sort_by_key(|(id, _, _)| *id);
        for (id, crate_name, label) in regs {
            MemoryRegistry::register(id, &crate_name, &label)?;
        }

        let summary = MemoryRegistryInitSummary {
            ranges: MemoryRegistry::export_ranges(),
            entries: MemoryRegistry::export(),
        };
        MEMORY_REGISTRY_INITIALIZED.with(|ready| ready.set(true));

        Ok(summary)
    }

    #[must_use]
    pub fn is_initialized() -> bool {
        MEMORY_REGISTRY_INITIALIZED.with(Cell::get)
    }

    /// Snapshot all registry entries.
    #[must_use]
    pub fn snapshot_entries() -> Vec<(u8, MemoryRegistryEntry)> {
        MemoryRegistry::export()
    }

    /// Snapshot all reserved memory ranges.
    #[must_use]
    pub fn snapshot_ranges() -> Vec<(String, MemoryRange)> {
        MemoryRegistry::export_ranges()
    }

    /// Snapshot all reserved memory ranges with owners.
    #[must_use]
    pub fn snapshot_range_entries() -> Vec<MemoryRangeEntry> {
        MemoryRegistry::export_range_entries()
    }

    /// Snapshot registry entries grouped by range.
    #[must_use]
    pub fn snapshot_ids_by_range() -> Vec<MemoryRangeSnapshot> {
        MemoryRegistry::export_ids_by_range()
    }

    /// Retrieve a single registry entry by ID.
    #[must_use]
    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
        MemoryRegistry::get(id)
    }

    /// Apply any newly deferred registrations/ranges after runtime init.
    ///
    /// This is a no-op until initialization has completed. Once initialized,
    /// this drains pending range/ID registrations so lazily touched statics can
    /// become visible during the same request.
    pub fn commit_pending_if_initialized() -> Result<(), MemoryRegistryError> {
        if !Self::is_initialized() || crate::runtime::is_eager_tls_initializing() {
            return Ok(());
        }

        let _ = Self::init(None)?;
        Ok(())
    }
}

///
/// TESTS
///

#[cfg(test)]
mod tests {
    use super::*;
    use crate::registry::{defer_register, defer_reserve_range, reset_for_tests};

    #[test]
    fn init_applies_initial_and_pending() {
        reset_for_tests();
        defer_reserve_range("crate_b", 5, 6).expect("defer range");
        defer_register(5, "crate_b", "B5").expect("defer register");

        let summary =
            MemoryRegistryRuntime::init(Some(("crate_a", 1, 3))).expect("init should succeed");

        assert_eq!(summary.ranges.len(), 2);
        assert_eq!(summary.entries.len(), 1);
        assert_eq!(summary.entries[0].0, 5);
        assert_eq!(summary.entries[0].1.label, "B5");
    }

    #[test]
    fn init_is_idempotent_for_same_initial_range() {
        reset_for_tests();

        MemoryRegistryRuntime::init(Some(("crate_a", 1, 3))).expect("first init should succeed");
        MemoryRegistryRuntime::init(Some(("crate_a", 1, 3))).expect("second init should succeed");
    }

    #[test]
    fn init_returns_error_on_conflict() {
        reset_for_tests();
        defer_reserve_range("crate_a", 1, 3).expect("defer range A");
        defer_reserve_range("crate_b", 3, 4).expect("defer range B");

        let err = MemoryRegistryRuntime::init(None).unwrap_err();
        assert!(matches!(err, MemoryRegistryError::Overlap { .. }));
    }

    #[test]
    fn commit_pending_after_init_applies_late_deferred_items() {
        reset_for_tests();

        MemoryRegistryRuntime::init(Some(("core", 1, 10))).expect("init should succeed");
        defer_reserve_range("late", 20, 30).expect("defer late range");
        defer_register(22, "late", "late_slot").expect("defer late register");

        MemoryRegistryRuntime::commit_pending_if_initialized()
            .expect("late pending commit should succeed");

        let entry = MemoryRegistryRuntime::get(22).expect("late entry should be registered");
        assert_eq!(entry.crate_name, "late");
        assert_eq!(entry.label, "late_slot");
    }
}