From 700c20c14385aeac37eee1c3f84a6d13ea67c445 Mon Sep 17 00:00:00 2001 From: Flamki <9833ayush@gmail.com> Date: Sun, 12 Apr 2026 10:53:50 +0530 Subject: [PATCH] gc: add finalizer_safe parity API and behavior tests --- oscars/src/collectors/mark_sweep/mod.rs | 6 +++ oscars/src/collectors/mark_sweep/tests.rs | 54 +++++++++++++++++++ .../src/collectors/mark_sweep_arena2/mod.rs | 6 +++ .../src/collectors/mark_sweep_arena2/tests.rs | 54 +++++++++++++++++++ 4 files changed, 120 insertions(+) diff --git a/oscars/src/collectors/mark_sweep/mod.rs b/oscars/src/collectors/mark_sweep/mod.rs index 607641b..1090497 100644 --- a/oscars/src/collectors/mark_sweep/mod.rs +++ b/oscars/src/collectors/mark_sweep/mod.rs @@ -95,6 +95,12 @@ impl MarkSweepGarbageCollector { pub fn pools_len(&self) -> usize { self.allocator.borrow().pools_len() } + + /// Returns true when the collector is not inside an active collection + /// cycle, i.e. it is safe to run external finalizer-sensitive paths. + pub fn finalizer_safe(&self) -> bool { + !self.is_collecting.get() + } } impl Drop for MarkSweepGarbageCollector { diff --git a/oscars/src/collectors/mark_sweep/tests.rs b/oscars/src/collectors/mark_sweep/tests.rs index 454e5e6..95a5a83 100644 --- a/oscars/src/collectors/mark_sweep/tests.rs +++ b/oscars/src/collectors/mark_sweep/tests.rs @@ -855,6 +855,60 @@ mod gc_edge_cases { let _value = *flag.borrow(); } + #[test] + fn finalizer_safe_reflects_collecting_state() { + use core::sync::atomic::{AtomicU8, Ordering}; + + // 0 = not observed, 1 = true, 2 = false + static OBSERVED: AtomicU8 = AtomicU8::new(0); + + struct Probe { + collector: core::ptr::NonNull, + } + + impl Finalize for Probe {} + + unsafe impl Trace for Probe { + unsafe fn trace(&self, _color: crate::mark_sweep::TraceColor) { + let safe = unsafe { self.collector.as_ref().finalizer_safe() }; + OBSERVED.store(if safe { 1 } else { 2 }, Ordering::SeqCst); + } + + fn run_finalizer(&self) { + Finalize::finalize(self); + } + } + + let collector = &mut MarkSweepGarbageCollector::default() + .with_page_size(256) + .with_heap_threshold(512); + + assert!( + collector.finalizer_safe(), + "collector should report finalizer-safe while idle" + ); + + OBSERVED.store(0, Ordering::SeqCst); + let _root = Gc::new_in( + Probe { + collector: core::ptr::NonNull::from(&*collector), + }, + collector, + ); + + collector.collect(); + + assert_eq!( + OBSERVED.load(Ordering::SeqCst), + 2, + "trace-time observation should see finalizer_safe() == false during collection" + ); + assert!( + collector.finalizer_safe(), + "collector should return to finalizer-safe state after collection" + ); + } + // ---- Multiple collections on the same graph --------------------------- /// Run GC repeatedly while objects are still alive to verify that diff --git a/oscars/src/collectors/mark_sweep_arena2/mod.rs b/oscars/src/collectors/mark_sweep_arena2/mod.rs index 8fc1437..3784f51 100644 --- a/oscars/src/collectors/mark_sweep_arena2/mod.rs +++ b/oscars/src/collectors/mark_sweep_arena2/mod.rs @@ -84,6 +84,12 @@ impl MarkSweepGarbageCollector { pub fn arenas_len(&self) -> usize { self.allocator.borrow().arenas_len() } + + /// Returns true when the collector is not inside an active collection + /// cycle, i.e. it is safe to run external finalizer-sensitive paths. + pub fn finalizer_safe(&self) -> bool { + !self.is_collecting.get() + } } impl Drop for MarkSweepGarbageCollector { diff --git a/oscars/src/collectors/mark_sweep_arena2/tests.rs b/oscars/src/collectors/mark_sweep_arena2/tests.rs index e556a56..99016c5 100644 --- a/oscars/src/collectors/mark_sweep_arena2/tests.rs +++ b/oscars/src/collectors/mark_sweep_arena2/tests.rs @@ -901,6 +901,60 @@ mod gc_edge_cases { let _value = *flag.borrow(); } + #[test] + fn finalizer_safe_reflects_collecting_state() { + use core::sync::atomic::{AtomicU8, Ordering}; + + // 0 = not observed, 1 = true, 2 = false + static OBSERVED: AtomicU8 = AtomicU8::new(0); + + struct Probe { + collector: core::ptr::NonNull, + } + + impl Finalize for Probe {} + + unsafe impl Trace for Probe { + unsafe fn trace(&self, _color: TraceColor) { + let safe = unsafe { self.collector.as_ref().finalizer_safe() }; + OBSERVED.store(if safe { 1 } else { 2 }, Ordering::SeqCst); + } + + fn run_finalizer(&self) { + Finalize::finalize(self); + } + } + + let collector = &mut MarkSweepGarbageCollector::default() + .with_arena_size(256) + .with_heap_threshold(512); + + assert!( + collector.finalizer_safe(), + "collector should report finalizer-safe while idle" + ); + + OBSERVED.store(0, Ordering::SeqCst); + let _root = Gc::new_in( + Probe { + collector: core::ptr::NonNull::from(&*collector), + }, + collector, + ); + + collector.collect(); + + assert_eq!( + OBSERVED.load(Ordering::SeqCst), + 2, + "trace-time observation should see finalizer_safe() == false during collection" + ); + assert!( + collector.finalizer_safe(), + "collector should return to finalizer-safe state after collection" + ); + } + // ---- Multiple collections on the same graph --------------------------- /// Run GC repeatedly while objects are still alive to verify that