Skip to main content

gc_lite/
partition.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright (c) 2025-2026 John Ray <996351336@qq.com>
3
4use std::{cell::Cell, ptr::NonNull};
5
6use crate::{GcHead, GcHeap, node::GcTriColor, node_link::GcNodeLink};
7
8/// Partition ID
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
10pub struct GcPartitionId(pub u16);
11
12impl GcPartitionId {
13    /// Special partition ID representing no partition (null value)
14    pub const NONE: Self = Self(0);
15
16    #[inline(always)]
17    pub const fn is_null(&self) -> bool {
18        self.0 == 0
19    }
20}
21
22impl std::fmt::Debug for GcPartitionId {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28#[derive(Debug)]
29pub struct GcPartition {
30    /// link of nodes in this partition
31    pub(crate) nodes: GcNodeLink,
32    /// gray nodes to be traced in this partition
33    pub(crate) gray_list: Vec<NonNull<GcHead>>,
34    /// Is in a marking cycle
35    marking: bool,
36
37    pub(crate) memory_used: usize,
38}
39
40impl GcPartition {
41    fn new() -> Self {
42        Self {
43            memory_used: 0,
44            nodes: GcNodeLink::default(),
45            gray_list: Vec::new(),
46            marking: false,
47        }
48    }
49
50    #[inline(always)]
51    pub fn memory_used(&self) -> usize {
52        self.memory_used
53    }
54
55    #[inline(always)]
56    pub const fn is_marking(&self) -> bool {
57        self.marking
58    }
59
60    #[inline(always)]
61    pub(crate) const fn set_marking(&mut self, marking: bool) {
62        self.marking = marking;
63    }
64
65    pub(crate) fn add_gray_node(&mut self, mut node: NonNull<GcHead>) {
66        debug_assert!(self.is_marking());
67
68        match unsafe { node.as_ref().color() } {
69            GcTriColor::White => unsafe {
70                node.as_mut().set_color(GcTriColor::Gray);
71            },
72            GcTriColor::Gray => {}
73            GcTriColor::Black => {
74                return;
75            }
76        }
77
78        if !self.gray_list.contains(&node) {
79            self.gray_list.push(node);
80        }
81    }
82}
83
84impl GcHeap {
85    /// Create a new partition.
86    pub fn create_partition(&mut self) -> GcPartitionId {
87        thread_local! {
88            static NEXT_PARTITION_ID: Cell<u16> = const { Cell::new(1) };
89        }
90
91        let id = NEXT_PARTITION_ID.with(|next_id| {
92            let mut serial = next_id.get();
93            if serial == 0 {
94                serial = 1;
95            }
96            let start = serial;
97
98            loop {
99                let candidate = GcPartitionId(serial);
100                let conflict = self.partitions.contains_key(&candidate);
101                if !conflict {
102                    let next = if serial == u16::MAX { 1 } else { serial + 1 };
103                    next_id.set(next);
104                    return candidate;
105                }
106
107                serial = if serial == u16::MAX { 1 } else { serial + 1 };
108                if serial == start {
109                    panic!("too many active partitions");
110                }
111            }
112        });
113
114        let partition = GcPartition::new();
115        self.partitions.insert(id, partition);
116
117        log::trace!("[new_scope] {id:?}");
118
119        id
120    }
121
122    /// Remove a partition, and dispose unused nodes.
123    /// For non-root partition, migrate xref nodes is optionally performed.
124    pub fn remove_partition(
125        &mut self,
126        partition_id: GcPartitionId,
127        on_dispose: impl Fn(&GcHeap, &GcHead),
128    ) -> usize {
129        if partition_id.is_null() {
130            return 0;
131        }
132
133        #[cfg(debug_assertions)]
134        {
135            self.dbg_dropping_root_partition = Some(partition_id);
136        }
137
138        log::trace!("[close_scope] {partition_id:?}");
139
140        let mut freed_bytes = 0;
141
142        if let Some(mut par) = self.partitions.remove(&partition_id) {
143            let link = std::mem::take(&mut par.nodes);
144            freed_bytes += self.dispose_all_nodes(link, &on_dispose);
145        }
146
147        log::trace!("[close_scope_done] {partition_id:?}");
148
149        #[cfg(debug_assertions)]
150        {
151            self.dbg_dropping_root_partition = None;
152        }
153
154        freed_bytes
155    }
156
157    /// Get partition information
158    #[inline(always)]
159    pub fn partition(&self, partition_id: GcPartitionId) -> Option<&GcPartition> {
160        self.partitions.get(&partition_id)
161    }
162
163    /// Get partition information
164    #[inline(always)]
165    pub fn partition_mut(&mut self, partition_id: GcPartitionId) -> Option<&mut GcPartition> {
166        self.partitions.get_mut(&partition_id)
167    }
168
169    /// Get all partition IDs
170    pub fn partition_ids(&self) -> Vec<GcPartitionId> {
171        self.partitions.keys().copied().collect()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    struct DummyType;
180    impl crate::trace::GcTrace for DummyType {
181        fn trace(&self, _: &mut crate::trace::GcTraceCtx) {}
182    }
183
184    crate::gc_type_register! {
185        DummyType, drop_pass = 0;
186    }
187
188    #[test]
189    fn test_partition_creation() {
190        let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
191        let id = heap.create_partition();
192
193        let partition = heap.partition(id).unwrap();
194        assert_eq!(partition.memory_used(), 0);
195
196        // Clean up partition
197        heap.remove_partition(id, |_, n| {
198            println!("dispose: {n:?}");
199        });
200        assert!(heap.partition(id).is_none());
201    }
202
203    #[test]
204    fn test_gc_threshold() {
205        let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
206        heap.set_memory_limit(1024);
207
208        assert_eq!(heap.gc_threshold(), 0);
209
210        heap.set_gc_threshold(512);
211        assert_eq!(heap.gc_threshold(), 512);
212
213        heap.set_gc_threshold(2048);
214        assert_eq!(heap.gc_threshold(), 819);
215
216        heap.set_gc_threshold(0);
217        assert_eq!(heap.gc_threshold(), 0);
218    }
219
220    #[test]
221    fn test_memory_limit() {
222        let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
223        let id = heap.create_partition();
224
225        assert_eq!(heap.memory_limit(), 0);
226
227        // Simulate some allocations in a single partition
228        heap.update_mem_use(id, 100);
229        assert_eq!(heap.memory_limit(), 0);
230
231        // Set limit larger than used memory
232        let applied = heap.set_memory_limit(512);
233        assert_eq!(applied, 512);
234        assert_eq!(heap.memory_limit(), 512);
235
236        // Set limit smaller than used memory should clamp to used
237        let applied_small = heap.set_memory_limit(80);
238        assert_eq!(applied_small, 100);
239        assert_eq!(heap.memory_limit(), 100);
240
241        // Set unlimited
242        let applied_zero = heap.set_memory_limit(0);
243        assert_eq!(applied_zero, 0);
244        assert_eq!(heap.memory_limit(), 0);
245    }
246
247    #[test]
248    fn test_is_ancestor_of() {
249        // 已删除 ancestor 相关 API,此测试不再适用,保留空壳确保编译通过
250    }
251
252    #[test]
253    fn test_common_parent() {
254        // 已删除 common_parent 相关 API,此测试不再适用,保留空壳确保编译通过
255    }
256
257    #[test]
258    fn test_update_mem_use() {
259        let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
260        let p1 = heap.create_partition();
261        let p2 = heap.create_partition();
262
263        heap.update_mem_use(p1, 100);
264        assert_eq!(heap.partition(p1).unwrap().memory_used(), 100);
265        assert_eq!(heap.partition(p2).unwrap().memory_used(), 0);
266
267        heap.update_mem_use(p2, 50);
268        assert_eq!(heap.partition(p1).unwrap().memory_used(), 100);
269        assert_eq!(heap.partition(p2).unwrap().memory_used(), 50);
270
271        heap.update_mem_use(p1, -20);
272        assert_eq!(heap.partition(p1).unwrap().memory_used(), 80);
273        assert_eq!(heap.partition(p2).unwrap().memory_used(), 50);
274
275        // Clean up
276        heap.remove_partition(p1, GcHeap::DUMMY_DISPOSE_CALLBACK);
277    }
278
279    #[test]
280    fn test_partition_id_serial_and_range() {
281        let id = GcPartitionId(10);
282        assert_eq!(id.0, 10);
283        assert!(!id.is_null());
284        assert!(GcPartitionId::NONE.is_null());
285    }
286}