sim_lib_lang_python/
managed.rs1use sim_lib_mutation::{
2 ArenaError, EdgeId, EphemeronMutationError, ManagedHandle, ManagedNode,
3 StrongEdgeMutationError, WeakEdgeMutationError,
4};
5
6#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
8pub enum PythonManagedKind {
9 #[default]
11 Instance,
12 Closure,
14 Frame,
16 Exception,
18 Container,
20}
21
22pub type PythonManagedObject = ManagedNode<PythonManagedKind>;
24
25pub type PythonHeap = sim_lib_gc_tracing::ManagedHeap<PythonManagedObject>;
27
28pub type PythonHeapPolicy = sim_lib_gc_tracing::ManagedHeapPolicy;
30
31pub trait PythonHeapExt {
33 fn connect(
35 &mut self,
36 from: ManagedHandle,
37 to: ManagedHandle,
38 ) -> Result<EdgeId, PythonManagedMutationError>;
39
40 fn connect_weak(
42 &mut self,
43 from: ManagedHandle,
44 to: ManagedHandle,
45 ) -> Result<EdgeId, PythonManagedMutationError>;
46
47 fn connect_ephemeron(
49 &mut self,
50 from: ManagedHandle,
51 key: ManagedHandle,
52 value: ManagedHandle,
53 ) -> Result<EdgeId, PythonManagedMutationError>;
54}
55
56#[derive(Clone, Debug, Eq, PartialEq)]
58pub enum PythonManagedMutationError {
59 Arena(ArenaError),
61 Strong(StrongEdgeMutationError),
63 Weak(WeakEdgeMutationError),
65 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}