Skip to main content

ic_memory/runtime/
custody.rs

1//! Process-local backing claims, retained by every issued runtime handle.
2use super::RuntimeConstructionError;
3use ic_platform::stable_structures::{Memory, VectorMemory};
4use std::{collections::BTreeSet, rc::Rc, sync::Arc};
5
6///
7/// BackingIdentity
8///
9/// Identity of a physical backing allocation, shared by all of its wrappers.
10/// This is host custody metadata, not a virtual memory ID or durable record.
11///
12
13#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
14pub struct BackingIdentity(usize);
15
16impl BackingIdentity {
17    /// Identify an Rc-owned backing. Its allocation must remain alive through
18    /// every runtime/handle using this identity. Identify the underlying backing,
19    /// not a newly allocated wrapper or an address inside a growable byte buffer.
20    #[must_use]
21    pub fn of_rc<T: ?Sized>(backing: &Rc<T>) -> Self {
22        Self(Rc::as_ptr(backing).cast::<()>() as usize)
23    }
24
25    /// Identify an Arc-owned backing under the same contract as [`Self::of_rc`].
26    #[must_use]
27    pub fn of_arc<T: ?Sized>(backing: &Arc<T>) -> Self {
28        Self(Arc::as_ptr(backing).cast::<()>() as usize)
29    }
30}
31
32///
33/// MemoryBacking
34///
35/// Host boundary for a backing whose aliases can be identified consistently.
36///
37/// All wrappers and clones over the same physical backing must return the same
38/// stable identity; independent backings must have different identities. Keep
39/// its identity allocation alive for the backing's complete runtime/handle
40/// lifetime. A wrapper delegates to its underlying backing's implementation.
41/// Obtaining the identity must not read, grow or write backing memory or create
42/// a manager/runtime; use a retained heap identity, not persisted contents.
43/// Implementing Memory alone does not establish this custody contract. Raw IO
44/// and unmanaged managers over a claimed backing remain prohibited; this is not
45/// an isolation boundary against arbitrary linked code or a dishonest backing.
46///
47pub trait MemoryBacking: Memory {
48    /// Return the physical backing identity under the trait's host contract.
49    fn backing_identity(&self) -> BackingIdentity;
50}
51
52impl MemoryBacking for VectorMemory {
53    fn backing_identity(&self) -> BackingIdentity {
54        BackingIdentity::of_rc(self)
55    }
56}
57
58impl<M: MemoryBacking> MemoryBacking for Rc<M> {
59    fn backing_identity(&self) -> BackingIdentity {
60        self.as_ref().backing_identity()
61    }
62}
63
64#[cfg(target_arch = "wasm32")]
65impl MemoryBacking for ic_platform::stable_structures::DefaultMemoryImpl {
66    fn backing_identity(&self) -> BackingIdentity {
67        // Every default handle addresses the same canister stable memory.
68        BackingIdentity(0)
69    }
70}
71
72#[cfg(not(target_arch = "wasm32"))]
73static CLAIMS: std::sync::Mutex<BTreeSet<BackingIdentity>> = std::sync::Mutex::new(BTreeSet::new());
74
75#[cfg(target_arch = "wasm32")]
76thread_local! {
77    static CLAIMS: std::cell::RefCell<BTreeSet<BackingIdentity>> = const {
78        std::cell::RefCell::new(BTreeSet::new())
79    };
80}
81
82fn with_claims<T>(operation: impl FnOnce(&mut BTreeSet<BackingIdentity>) -> T) -> T {
83    #[cfg(not(target_arch = "wasm32"))]
84    {
85        let mut claims = CLAIMS
86            .lock()
87            .unwrap_or_else(std::sync::PoisonError::into_inner);
88        operation(&mut claims)
89    }
90    #[cfg(target_arch = "wasm32")]
91    CLAIMS.with(|claims| operation(&mut claims.borrow_mut()))
92}
93
94pub(super) struct BackingClaim(BackingIdentity);
95
96impl BackingClaim {
97    pub fn acquire(memory: &impl MemoryBacking) -> Result<Self, RuntimeConstructionError> {
98        let identity = memory.backing_identity();
99        if with_claims(|claims| claims.insert(identity)) {
100            Ok(Self(identity))
101        } else {
102            Err(RuntimeConstructionError::BackingAlreadyClaimed)
103        }
104    }
105}
106
107impl Drop for BackingClaim {
108    fn drop(&mut self) {
109        with_claims(|claims| claims.remove(&self.0));
110    }
111}
112
113#[cfg(all(test, not(target_arch = "wasm32")))]
114impl<M: MemoryBacking> MemoryBacking for ic_testkit_native::memory::MeteredMemory<M> {
115    fn backing_identity(&self) -> BackingIdentity {
116        self.backing().backing_identity()
117    }
118}