Skip to main content

sim_lib_mutation/managed/
arena_model.rs

1/// Receives every outgoing managed edge of an object.
2pub trait EdgeVisitor {
3    /// Visits a retaining edge.
4    fn strong(&mut self, edge: EdgeId, target: ManagedId);
5
6    /// Visits a non-retaining edge that may be cleared after tracing.
7    fn weak(&mut self, edge: EdgeId, target: ManagedId);
8
9    /// Visits a value retained only when `key` is reachable.
10    fn ephemeron(&mut self, edge: EdgeId, key: ManagedId, value: ManagedId);
11}
12/// An object stored by [`ManagedArena`].
13pub trait ManagedObject {
14    /// Enumerates all strong, weak, and ephemeron edges exactly once.
15    fn trace_edges(&self, visitor: &mut dyn EdgeVisitor);
16
17    /// Clears one weak edge if it still points at `expected`.
18    ///
19    /// Returning `true` means this invocation performed the clear. Repeating
20    /// the same request must return `false`, giving collectors at-most-once
21    /// weak-clear semantics.
22    fn clear_weak_edge(&mut self, edge: EdgeId, expected: ManagedId) -> bool;
23
24    /// Clears one ephemeron entry if it still has the expected key and value.
25    /// Repeating a successful request must return `false`.
26    fn clear_ephemeron_edge(
27        &mut self,
28        _edge: EdgeId,
29        _expected_key: ManagedId,
30        _expected_value: ManagedId,
31    ) -> bool {
32        false
33    }
34}
35
36/// The only built-in policy: retain objects until explicit teardown, while
37/// refusing allocations beyond a fixed hard cap.
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct HardCappedRetainPolicy {
40    max_objects: usize,
41}
42
43impl HardCappedRetainPolicy {
44    /// Creates a retain policy with a non-zero object cap.
45    pub fn new(max_objects: usize) -> Result<Self, ArenaError> {
46        if max_objects == 0 {
47            return Err(ArenaError::InvalidCap);
48        }
49        Ok(Self { max_objects })
50    }
51
52    /// Returns the allocation cap.
53    pub const fn max_objects(self) -> usize {
54        self.max_objects
55    }
56}
57
58/// Fail-closed arena operation errors.
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum ArenaError {
61    /// A zero-sized arena was requested.
62    InvalidCap,
63    /// Allocation would exceed the hard object cap.
64    CapacityExceeded {
65        /// Configured maximum number of live objects.
66        cap: usize,
67    },
68    /// The allocation or root identity space is exhausted.
69    IdentityExhausted,
70    /// A handle no longer names a live object.
71    StaleHandle(ManagedId),
72    /// A root registration is unknown or does not match the handle.
73    StaleRoot(RootId),
74    /// A rooted object cannot be removed.
75    ObjectRooted(ManagedId),
76    /// Collection was planned against a different graph state.
77    MutationEpochChanged {
78        /// Epoch used to prepare the operation.
79        expected: u64,
80        /// Current arena epoch.
81        actual: u64,
82    },
83}
84
85impl fmt::Display for ArenaError {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            Self::InvalidCap => f.write_str("managed arena cap must be non-zero"),
89            Self::CapacityExceeded { cap } => write!(f, "managed arena hard cap {cap} reached"),
90            Self::IdentityExhausted => f.write_str("managed arena identity space exhausted"),
91            Self::StaleHandle(id) => write!(f, "stale managed handle {}", id.0),
92            Self::StaleRoot(id) => write!(f, "stale managed root {}", id.0),
93            Self::ObjectRooted(id) => write!(f, "managed object {} is rooted", id.0),
94            Self::MutationEpochChanged { expected, actual } => write!(
95                f,
96                "managed arena mutation epoch changed from {expected} to {actual}"
97            ),
98        }
99    }
100}
101
102impl Error for ArenaError {}
103
104/// An immutable, complete tracing view taken at a safepoint.
105pub struct TraceSnapshot<'a, T> {
106    roots: Vec<ManagedId>,
107    kept_alive: Vec<ManagedId>,
108    objects: &'a BTreeMap<ManagedId, T>,
109    mutation_epoch: u64,
110}
111
112impl<T: ManagedObject> TraceSnapshot<'_, T> {
113    /// Returns the arena mutation epoch captured by this snapshot.
114    pub const fn mutation_epoch(&self) -> u64 {
115        self.mutation_epoch
116    }
117    /// Enumerates roots in root-registration order.
118    pub fn roots(&self) -> impl ExactSizeIterator<Item = ManagedId> + '_ {
119        self.roots.iter().copied()
120    }
121
122    /// Enumerates successful weak dereferences kept alive for this epoch.
123    pub fn kept_alive(&self) -> impl ExactSizeIterator<Item = ManagedId> + '_ {
124        self.kept_alive.iter().copied()
125    }
126
127    /// Enumerates live objects in allocation order.
128    pub fn objects(&self) -> impl ExactSizeIterator<Item = ManagedId> + '_ {
129        self.objects.keys().copied()
130    }
131
132    /// Visits all edges for a live object.
133    pub fn visit_edges(
134        &self,
135        owner: ManagedId,
136        visitor: &mut dyn EdgeVisitor,
137    ) -> Result<(), ArenaError> {
138        self.objects
139            .get(&owner)
140            .ok_or(ArenaError::StaleHandle(owner))?
141            .trace_edges(visitor);
142        Ok(())
143    }
144}
145
146/// Deterministic evidence for one tracing safepoint.
147#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct SafepointReceipt {
149    /// Monotonic zero-based safepoint sequence.
150    pub sequence: u64,
151    /// Roots in root-registration order.
152    pub roots: Vec<ManagedId>,
153    /// Live objects in allocation order.
154    pub objects: Vec<ManagedId>,
155}
156
157/// Bounded, allocation-ordered audit evidence projected from one safepoint.
158///
159/// Labels are caller-owned metadata. They are deliberately absent from tracing
160/// and collection contracts, so changing them cannot affect reachability or
161/// reclamation policy.
162#[derive(Clone, Debug, Eq, PartialEq)]
163pub struct RoleProjectionReceipt<L> {
164    /// The base tracing receipt this optional evidence describes.
165    pub safepoint: SafepointReceipt,
166    /// Arena mutation epoch observed by the projection.
167    pub mutation_epoch: u64,
168    /// Owner identities and labels in managed allocation order.
169    pub roles: Vec<(ManagedId, L)>,
170}
171
172/// A failed bounded role-evidence projection.
173#[derive(Clone, Debug, Eq, PartialEq)]
174pub enum RoleProjectionError {
175    /// The requested projection would exceed its explicit row limit.
176    Limit {
177        /// Maximum admitted role rows.
178        limit: usize,
179        /// Number of live managed objects requiring rows.
180        required: usize,
181    },
182    /// The arena rejected the safepoint.
183    Arena(ArenaError),
184}
185
186impl fmt::Display for RoleProjectionError {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match self {
189            Self::Limit { limit, required } => {
190                write!(f, "role projection limit {limit} requires {required}")
191            }
192            Self::Arena(error) => error.fmt(f),
193        }
194    }
195}
196
197impl Error for RoleProjectionError {}
198
199impl From<ArenaError> for RoleProjectionError {
200    fn from(error: ArenaError) -> Self {
201        Self::Arena(error)
202    }
203}
204
205/// Deterministic evidence returned by explicit arena teardown.
206#[derive(Clone, Debug, Eq, PartialEq)]
207pub struct TeardownReceipt {
208    /// Objects removed in allocation order.
209    pub objects: Vec<ManagedId>,
210    /// Root registrations removed in registration order.
211    pub roots: Vec<RootId>,
212}
213
214/// Atomic collector mutation evidence.
215pub struct CollectionMutationReceipt {
216    /// Weak entries cleared as owner and edge identities.
217    pub cleared_weak: Vec<(ManagedId, EdgeId)>,
218    /// Ephemeron entries cleared as owner and edge identities.
219    pub cleared_ephemerons: Vec<(ManagedId, EdgeId)>,
220    /// Objects removed in allocation order.
221    pub swept: Vec<ManagedId>,
222}