Skip to main content

sim_lib_lang_python/
managed.rs

1use sim_lib_mutation::{
2    ArenaError, EdgeId, EphemeronMutationError, ManagedHandle, ManagedNode,
3    StrongEdgeMutationError, WeakEdgeMutationError,
4};
5
6/// Open Python role label carried by the shared managed node.
7#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
8pub enum PythonManagedKind {
9    /// An instance or class object.
10    #[default]
11    Instance,
12    /// A closure environment.
13    Closure,
14    /// A suspended or executing frame.
15    Frame,
16    /// An exception, traceback, or exception group.
17    Exception,
18    /// A mutable container.
19    Container,
20}
21
22/// Compatibility name for Python's role-bearing shared managed node.
23pub type PythonManagedObject = ManagedNode<PythonManagedKind>;
24
25/// Compatibility name for the shared managed heap instantiated for Python.
26pub type PythonHeap = sim_lib_gc_tracing::ManagedHeap<PythonManagedObject>;
27
28/// Compatibility name for the shared heap policy.
29pub type PythonHeapPolicy = sim_lib_gc_tracing::ManagedHeapPolicy;
30
31/// Python-named graph operations over the shared heap and node.
32pub trait PythonHeapExt {
33    /// Adds a checked strong edge and returns its stable edge identity.
34    fn connect(
35        &mut self,
36        from: ManagedHandle,
37        to: ManagedHandle,
38    ) -> Result<EdgeId, PythonManagedMutationError>;
39
40    /// Adds a checked weak edge and returns its stable edge identity.
41    fn connect_weak(
42        &mut self,
43        from: ManagedHandle,
44        to: ManagedHandle,
45    ) -> Result<EdgeId, PythonManagedMutationError>;
46
47    /// Adds a checked ephemeron and returns its stable edge identity.
48    fn connect_ephemeron(
49        &mut self,
50        from: ManagedHandle,
51        key: ManagedHandle,
52        value: ManagedHandle,
53    ) -> Result<EdgeId, PythonManagedMutationError>;
54}
55
56/// A checked Python managed-graph mutation failure.
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub enum PythonManagedMutationError {
59    /// The owning allocation handle is stale.
60    Arena(ArenaError),
61    /// A strong edge could not be admitted.
62    Strong(StrongEdgeMutationError),
63    /// A weak edge could not be admitted.
64    Weak(WeakEdgeMutationError),
65    /// An ephemeron could not be admitted.
66    Ephemeron(EphemeronMutationError),
67}
68
69impl From<ArenaError> for PythonManagedMutationError {
70    fn from(value: ArenaError) -> Self {
71        Self::Arena(value)
72    }
73}
74
75impl PythonHeapExt for PythonHeap {
76    fn connect(
77        &mut self,
78        from: ManagedHandle,
79        to: ManagedHandle,
80    ) -> Result<EdgeId, PythonManagedMutationError> {
81        self.get_mut(from)?
82            .insert_strong(to.id())
83            .map_err(PythonManagedMutationError::Strong)
84    }
85
86    fn connect_weak(
87        &mut self,
88        from: ManagedHandle,
89        to: ManagedHandle,
90    ) -> Result<EdgeId, PythonManagedMutationError> {
91        self.get_mut(from)?
92            .insert_weak(to.id())
93            .map_err(PythonManagedMutationError::Weak)
94    }
95
96    fn connect_ephemeron(
97        &mut self,
98        from: ManagedHandle,
99        key: ManagedHandle,
100        value: ManagedHandle,
101    ) -> Result<EdgeId, PythonManagedMutationError> {
102        self.get_mut(from)?
103            .insert_ephemeron(key.id(), value.id())
104            .map_err(PythonManagedMutationError::Ephemeron)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use sim_lib_gc_tracing::CollectionLimits;
112
113    fn limits() -> CollectionLimits {
114        CollectionLimits {
115            objects: 8,
116            edges: 16,
117            stack: 8,
118            work: 64,
119            clears: 8,
120            finalizers: 0,
121        }
122    }
123
124    #[test]
125    fn shared_node_preserves_heterogeneous_cycle_behavior() {
126        let mut heap = PythonHeap::tracing(8, limits()).unwrap();
127        let kinds = [
128            PythonManagedKind::Instance,
129            PythonManagedKind::Closure,
130            PythonManagedKind::Frame,
131            PythonManagedKind::Exception,
132            PythonManagedKind::Container,
133        ];
134        let handles: Vec<_> = kinds
135            .into_iter()
136            .map(|kind| heap.allocate(PythonManagedObject::new(kind)).unwrap())
137            .collect();
138        for pair in handles.windows(2) {
139            heap.connect(pair[0], pair[1]).unwrap();
140        }
141        heap.connect(handles[4], handles[0]).unwrap();
142        let visible_result = 42;
143        assert_eq!(
144            heap.collect().unwrap().unwrap().swept,
145            handles.iter().map(|handle| handle.id()).collect::<Vec<_>>()
146        );
147        assert_eq!(heap.live_len(), 0);
148        assert_eq!(visible_result, 42);
149    }
150
151    #[test]
152    fn shared_heap_preserves_exact_retention_gap() {
153        let mut heap = PythonHeap::retaining(2).unwrap();
154        heap.allocate(PythonManagedObject::new(PythonManagedKind::Instance))
155            .unwrap();
156        assert_eq!(
157            heap.cycle_leak_gap(),
158            Some("unreachable strong cycles are retained until heap teardown")
159        );
160        assert_eq!(heap.collect().unwrap(), None);
161        assert_eq!(heap.live_len(), 1);
162    }
163
164    #[test]
165    fn python_weak_and_ephemeron_edges_clear_on_shared_collector() {
166        let mut heap = PythonHeap::tracing(8, limits()).unwrap();
167        let owner = heap
168            .allocate(PythonManagedObject::new(PythonManagedKind::Container))
169            .unwrap();
170        let weak_target = heap
171            .allocate(PythonManagedObject::new(PythonManagedKind::Instance))
172            .unwrap();
173        let key = heap
174            .allocate(PythonManagedObject::new(PythonManagedKind::Instance))
175            .unwrap();
176        let value = heap
177            .allocate(PythonManagedObject::new(PythonManagedKind::Instance))
178            .unwrap();
179        let weak = heap.connect_weak(owner, weak_target).unwrap();
180        let ephemeron = heap.connect_ephemeron(owner, key, value).unwrap();
181        let root = heap.root(owner).unwrap();
182
183        let receipt = heap.collect().unwrap().unwrap();
184        assert_eq!(receipt.swept, [weak_target.id(), key.id(), value.id()]);
185        assert_eq!(receipt.cleared_weak, [(owner.id(), weak)]);
186        assert_eq!(receipt.cleared_ephemerons, [(owner.id(), ephemeron)]);
187        assert!(heap.get(owner).unwrap().edge_snapshot().is_empty());
188        heap.release_root(root).unwrap();
189    }
190}