Conversation
7cabb2f to
503ee7d
Compare
| unsafe { gc_box.finalize_fn()(node) }; | ||
| unsafe { gc_box.drop_fn()(node) }; |
There was a problem hiding this comment.
This is incorrect, and possibly unsound. You cannot finalize objects then drop them immediately after, because their finalize implementation could revive those objects to make them reachable again. If you immediately drop them after finalizing, you would have a reference to unallocated memory.
There was a problem hiding this comment.
GC usually happens in phases to ensure soundness:
mark -> finalize -> mark again -> drop
503ee7d to
b236f85
Compare
|
Thanks for the review — fixed. I rebased onto latest What changed in
So if finalization revives any root, teardown now avoids immediate drop/free of those values. Validation rerun locally:
|
|
|
||
| // Phase 2: if finalization revived any roots, do not drop/free now. | ||
| // Dropping immediately after revival would be unsound. | ||
| let revived_rooted = roots |
There was a problem hiding this comment.
This is still invalid because this doesn't run a marking phase, so the check for rootedness check is just returning roots again. See https://github.com/boa-dev/boa/blob/a49348586d90b790936dc6ab651da6aff3f60e67/core/gc/src/lib.rs#L226-L277 for a correct implementation, and note how mark_heap needs to run twice.
|
Thanks for the pointer — agreed the previous rootedness re-check was not enough. I updated teardown to follow the phase order from your reference: mark -> finalize -> mark again -> drop/free. In
I also added a regression test ( |
| // arena3 uses`free_slot` calls to reclaim memory. | ||
| // arena2 uses a bitmap (`mark_dropped`) and reclaims automatically | ||
| fn sweep_all_queues(&self) { | ||
| fn sweep_all_queues(&self) -> bool { |
There was a problem hiding this comment.
I just realized this only runs when the collector itself gets dropped, so it doesn't require a two phase mark & drop, it just requires that we run all finalizers first (since finalizers could reference other objects in the heap), then all drops after that.
26703be to
5ca8967
Compare
|
Thanks for the clarification, that makes sense. I updated the latest commit to keep collector-drop teardown simple: it now runs finalizers for all tracked values first, then does drop/free for all tracked values. I removed the extra mark/reachability logic from this path and also resolved the I reran the full local checks ( |
|
LGTM. Pinging @nekevss to do the final review |
| if map.is_alive() { | ||
| true | ||
| } else { | ||
| unsafe { |
| let _ = rust_alloc::boxed::Box::from_raw(map_ptr.as_ptr()); | ||
| } | ||
| false | ||
| } |
There was a problem hiding this comment.
suggestion: maybe clean up this section a bit
| } | |
| let is_map_alive = map.is_alive(); | |
| if !is_map_alive { | |
| unsafe { | |
| let _ = rust_alloc::boxed::Box::from_raw(map_ptr.as_ptr()); | |
| } | |
| } | |
| is_map_alive |
| true | ||
| } else { | ||
| unsafe { | ||
| let _ = rust_alloc::boxed::Box::from_raw(map_ptr.as_ptr()); |
There was a problem hiding this comment.
nit: can you please add a note that we reclaim the weak_map with rust_alloc, because it's allocated with rust_alloc and not alloc.
Ideally we eventually have that value better tied to alloc, and not rust_alloc, but in the meantime, we should really note it better, because it's going to be easy to lose track and get confused if we don't
| } | ||
|
|
||
| #[test] | ||
| fn collector_drop_runs_destructors_for_live_gc_values() { |
There was a problem hiding this comment.
praise: nice tests! Thanks for adding them
This PR tightens mark-sweep teardown/finalization semantics with minimal scope.
What changed
FinalizeFn) soGcBox<T>finalizers can be dispatched correctly from erased pointers.sweep_all_queues) to runfinalize_fnbeforedrop_fnfor:root_queuepending_root_queueephemeron_queuepending_ephemeron_queueTrace::run_finalizer(...).collector_drop_runs_finalizers_for_live_gc_valuescollector_drop_runs_ephemeron_value_finalizers_for_live_valuesWhy
Previous teardown behavior reliably dropped live tracked allocations, but finalizer execution for erased GC values was incomplete/inconsistent. This patch keeps the PR focused to mark-sweep internals and guarantees finalizers are run during collector teardown where safe.
Scope
Validation
cargo fmt --all -- --checkcargo test --workspacecargo clippy --workspace --all-features --all-targetscargo clippy --workspace --no-default-features --all-targetscargo doc -v --document-private-items --all-features