Skip to main content

canic_core/memory/admission/
mod.rs

1//! Module: memory::admission
2//!
3//! Responsibility: seal one composed consumer admission hook before Canic memory bootstrap.
4//! Does not own: allocation grants, placement, memory handles or lifecycle restoration.
5//! Boundary: static registration supplies policy identity and a fallible pre-commit callback.
6
7use crate::memory::registry::MemoryRegistryError;
8use ic_memory::BootstrapAdmission;
9use std::sync::Mutex;
10
11pub use ic_memory::PolicyIdentity;
12
13/// One host-owned admission over the complete sealed declaration snapshot.
14///
15/// The identity must cover the callback's semantics and configuration. The callback
16/// must not open memory or start a second bootstrap. Compose multiple consumers
17/// inside this single callback; return the original typed failure on rejection.
18#[derive(Clone, Debug)]
19pub struct MemoryBootstrapAdmission {
20    pub(super) identity: PolicyIdentity,
21    pub(super) prepare: fn(&mut BootstrapAdmission<'_>) -> Result<(), MemoryRegistryError>,
22}
23
24impl MemoryBootstrapAdmission {
25    /// Bind the callback to its semantic identity, including any configuration digest.
26    #[must_use]
27    pub const fn new(
28        identity: PolicyIdentity,
29        prepare: fn(&mut BootstrapAdmission<'_>) -> Result<(), MemoryRegistryError>,
30    ) -> Self {
31        Self { identity, prepare }
32    }
33}
34
35#[derive(Default)]
36struct Registration {
37    participant: Option<MemoryBootstrapAdmission>,
38    sealed: bool,
39}
40
41impl Registration {
42    fn register(
43        &mut self,
44        participant: MemoryBootstrapAdmission,
45    ) -> Result<(), MemoryRegistryError> {
46        if self.sealed {
47            return Err(MemoryRegistryError::AdmissionRegistrationSealed);
48        }
49        if self.participant.is_some() {
50            return Err(MemoryRegistryError::AdmissionAlreadyRegistered);
51        }
52        self.participant = Some(participant);
53        Ok(())
54    }
55
56    fn seal(&mut self) -> Option<MemoryBootstrapAdmission> {
57        self.sealed = true;
58        self.participant.clone()
59    }
60}
61
62static ADMISSION: Mutex<Registration> = Mutex::new(Registration {
63    participant: None,
64    sealed: false,
65});
66
67/// Macro registration boundary; applications should use `memory_bootstrap_admission!`.
68///
69/// # Errors
70/// Rejects duplicate or late registration and poisoned registry access.
71#[doc(hidden)]
72pub fn register(participant: MemoryBootstrapAdmission) -> Result<(), MemoryRegistryError> {
73    ADMISSION
74        .lock()
75        .map_err(|_| MemoryRegistryError::AdmissionRegistryPoisoned)?
76        .register(participant)
77}
78
79pub(super) fn seal() -> Result<Option<MemoryBootstrapAdmission>, MemoryRegistryError> {
80    Ok(ADMISSION
81        .lock()
82        .map_err(|_| MemoryRegistryError::AdmissionRegistryPoisoned)?
83        .seal())
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    fn participant() -> MemoryBootstrapAdmission {
91        MemoryBootstrapAdmission::new(
92            PolicyIdentity::new("test.admission", 1).unwrap(),
93            |_| Ok(()),
94        )
95    }
96
97    #[test]
98    fn registration_is_single_and_sealed_even_without_a_participant() {
99        let mut registration = Registration::default();
100        registration.register(participant()).unwrap();
101        assert!(matches!(
102            registration.register(participant()),
103            Err(MemoryRegistryError::AdmissionAlreadyRegistered)
104        ));
105        assert!(registration.seal().is_some());
106        assert!(registration.seal().is_some());
107        assert!(matches!(
108            registration.register(participant()),
109            Err(MemoryRegistryError::AdmissionRegistrationSealed)
110        ));
111        let mut absent = Registration::default();
112        assert!(absent.seal().is_none());
113        assert!(matches!(
114            absent.register(participant()),
115            Err(MemoryRegistryError::AdmissionRegistrationSealed)
116        ));
117    }
118}