sim_lib_lang_python/
managed.rs1use sim_lib_gc_tracing::{CollectionError, CollectionLimits, CollectionReceipt, collect};
2use sim_lib_mutation::{
3 EdgeId, EdgeVisitor, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedId,
4 ManagedObject,
5};
6
7#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9pub enum PythonManagedKind {
10 #[default]
12 Instance,
13 Closure,
15 Frame,
17 Exception,
19 Container,
21}
22
23#[derive(Clone, Debug, Default)]
25pub struct PythonManagedObject {
26 pub kind: PythonManagedKind,
28 pub edges: Vec<ManagedId>,
30}
31impl ManagedObject for PythonManagedObject {
32 fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
33 for (i, target) in self.edges.iter().copied().enumerate() {
34 visitor.strong(EdgeId(i as u32), target);
35 }
36 }
37 fn clear_weak_edge(&mut self, _: EdgeId, _: ManagedId) -> bool {
38 false
39 }
40 fn clear_ephemeron_edge(&mut self, _: EdgeId, _: ManagedId, _: ManagedId) -> bool {
41 false
42 }
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub enum PythonHeapPolicy {
48 Tracing(CollectionLimits),
50 Retain,
52}
53
54pub struct PythonHeap {
56 arena: ManagedArena<PythonManagedObject>,
57 policy: PythonHeapPolicy,
58}
59impl PythonHeap {
60 pub fn standard(
62 cap: usize,
63 limits: CollectionLimits,
64 ) -> Result<Self, sim_lib_mutation::ArenaError> {
65 Ok(Self {
66 arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
67 policy: PythonHeapPolicy::Tracing(limits),
68 })
69 }
70 pub fn retaining(cap: usize) -> Result<Self, sim_lib_mutation::ArenaError> {
72 Ok(Self {
73 arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
74 policy: PythonHeapPolicy::Retain,
75 })
76 }
77 pub fn allocate(
79 &mut self,
80 value: PythonManagedObject,
81 ) -> Result<ManagedHandle, sim_lib_mutation::ArenaError> {
82 self.arena.allocate(value)
83 }
84 pub fn connect(
86 &mut self,
87 from: ManagedHandle,
88 to: ManagedHandle,
89 ) -> Result<(), sim_lib_mutation::ArenaError> {
90 self.arena.get_mut(from)?.edges.push(to.id());
91 Ok(())
92 }
93 pub fn live_len(&self) -> usize {
95 self.arena.len()
96 }
97 pub const fn policy(&self) -> PythonHeapPolicy {
99 self.policy
100 }
101 pub const fn cycle_leak_gap(&self) -> Option<&'static str> {
103 match self.policy {
104 PythonHeapPolicy::Retain => {
105 Some("unreachable strong cycles are retained until heap teardown")
106 }
107 PythonHeapPolicy::Tracing(_) => None,
108 }
109 }
110 pub fn collect(&mut self) -> Result<Option<CollectionReceipt>, CollectionError> {
112 match self.policy {
113 PythonHeapPolicy::Tracing(limits) => collect(&mut self.arena, limits).map(Some),
114 PythonHeapPolicy::Retain => Ok(None),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 fn limits() -> CollectionLimits {
124 CollectionLimits {
125 objects: 8,
126 edges: 8,
127 stack: 8,
128 work: 32,
129 clears: 8,
130 finalizers: 0,
131 }
132 }
133
134 #[test]
135 fn tracing_is_standard_and_retention_is_explicit() {
136 let standard = PythonHeap::standard(8, limits()).unwrap();
137 assert!(matches!(standard.policy(), PythonHeapPolicy::Tracing(_)));
138 assert_eq!(standard.cycle_leak_gap(), None);
139 let retaining = PythonHeap::retaining(8).unwrap();
140 assert_eq!(retaining.policy(), PythonHeapPolicy::Retain);
141 assert!(retaining.cycle_leak_gap().unwrap().contains("cycles"));
142 }
143
144 #[test]
145 fn unreachable_python_objects_are_reclaimed_by_shared_collector() {
146 let mut heap = PythonHeap::standard(8, limits()).unwrap();
147 heap.allocate(PythonManagedObject::default()).unwrap();
148 let receipt = heap.collect().unwrap().unwrap();
149 assert_eq!(receipt.swept.len(), 1);
150 }
151
152 #[test]
153 fn heterogeneous_language_cycle_is_reclaimed_without_observable_mutation() {
154 let mut heap = PythonHeap::standard(8, limits()).unwrap();
155 let kinds = [
156 PythonManagedKind::Instance,
157 PythonManagedKind::Closure,
158 PythonManagedKind::Frame,
159 PythonManagedKind::Exception,
160 PythonManagedKind::Container,
161 ];
162 let handles: Vec<_> = kinds
163 .into_iter()
164 .map(|kind| {
165 heap.allocate(PythonManagedObject {
166 kind,
167 edges: vec![],
168 })
169 .unwrap()
170 })
171 .collect();
172 for pair in handles.windows(2) {
173 heap.connect(pair[0], pair[1]).unwrap();
174 }
175 heap.connect(handles[4], handles[0]).unwrap();
176 let visible_result = 42;
177 let receipt = heap.collect().unwrap().unwrap();
178 assert_eq!(receipt.swept.len(), 5);
179 assert_eq!(heap.live_len(), 0);
180 assert_eq!(visible_result, 42);
181 }
182}