Skip to main content

ic_storage/runtime/
custody.rs

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