Skip to content

ReferenceProcessor does not trace transitively discovered SoftReference instances #1125

@wks

Description

@wks

After retaining and scanning SoftReference, it generates ProcessEdgesWork work packets to trace the children of retained SoftReference instances. During this time, more SoftReference instances will be discovered, but they will not be traced. That will cause those transitively discovered SoftReference to contain un-updated reference to the children in the from-space, or crash MarkCompact due to those object not having forwarding pointers.

This bug was originally discovered when running CI tests for the PR removing ObjectReference::NULL when running fop in DaCapo 2006 using MarkCompact. See: https://github.com/mmtk/mmtk-openjdk/actions/runs/8750247972/job/24016142656?pr=265

The program

import java.lang.ref.*;

class StrongNode {
    public WeakNode next;
    public StrongNode(WeakNode next) {
        this.next = next;
    }
}

class WeakNode extends SoftReference<StrongNode> {
    public WeakNode(StrongNode next) {
        super(next);
    }
}

public class TwoLevelDeadWeak {
    public static WeakNode mkWeak(Object object) {
        StrongNode strong1 = new StrongNode(null);
        WeakNode weak1 = new WeakNode(strong1);
        StrongNode strong2 = new StrongNode(weak1);
        WeakNode weak2 = new WeakNode(strong2);
        return weak2;
    }

    public static void main(String[] args) {
        Object obj = new Object();

        System.out.println("Making objects");
        WeakNode rooted = mkWeak(obj);
        System.out.println("Triggering GC...");
        System.gc();

        System.out.format("Rooted: %s%n", rooted);
        StrongNode myStrong = rooted.get().next.get();
        System.out.format("myStrong: %s%n", myStrong);

        System.out.println("Done.");
    }
}

How to crash SemiSpace?

Run it with MMTK_NO_REFERENCE_TYPE=true, preferrably with stack trace and logs enabled.

export RUST_LOG=info,mmtk::util::reference_processor=trace
export RUST_BACKTRACE=1
MMTK_THREADS=1 MMTK_NO_REFERENCE_TYPES=false MMTK_PLAN=SemiSpace /path/to/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java -XX:+UseThirdPartyHeap -cp /path/to/the/program TwoLevelDeadWeak

It will print:

...
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor] Process reference: 0x400aef68
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor]  ~> 0x400e0410
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor]  => 0x40473250
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor]  ~> 0x404853d0
[2024-04-19T12:08:53Z DEBUG mmtk::util::reference_processor] SOFT reference table from 151 to 100 (0 enqueued)
[2024-04-19T12:08:53Z DEBUG mmtk::util::reference_processor] Ending ReferenceProcessor.scan(SOFT)
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor] Add soft candidate: 0x404e8480
[2024-04-19T12:08:53Z DEBUG mmtk::util::reference_processor] Starting ReferenceProcessor.scan(WEAK)
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor] WEAK Reference table is {0x40477d30, 0x400b14f8, ...
...
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor] Process reference: 0x4046f870
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor]  ~> 0x0
[2024-04-19T12:08:53Z TRACE mmtk::util::reference_processor]  (cleared referent) 
[2024-04-19T12:08:53Z DEBUG mmtk::util::reference_processor] PHANTOM reference table from 42 to 3 (10 enqueued)
[2024-04-19T12:08:53Z DEBUG mmtk::util::reference_processor] Ending ReferenceProcessor.scan(PHANTOM)
thread '<unnamed>' panicked at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:252:21:
Referent 0x401ebf18 (of reference 0x404e8480) is not in any space
stack backtrace:
   0: rust_begin_unwind
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/panicking.rs:647:5
   1: core::panicking::panic_fmt
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/panicking.rs:72:14
   2: mmtk::util::reference_processor::ReferenceProcessor::enqueue::{{closure}}
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:252:21
   3: core::iter::traits::iterator::Iterator::for_each::call::{{closure}}
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/traits/iterator.rs:855:29
   4: <hashbrown::map::Keys<K,V> as core::iter::traits::iterator::Iterator>::fold::{{closure}}
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4865:45
   5: <hashbrown::map::Iter<K,V> as core::iter::traits::iterator::Iterator>::fold::{{closure}}
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4749:13
   6: hashbrown::raw::RawIterRange<T>::fold_impl
             at /rust/deps/hashbrown-0.14.3/src/raw/mod.rs:3885:23
   7: <hashbrown::raw::RawIter<T> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/raw/mod.rs:4156:18
   8: <hashbrown::map::Iter<K,V> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4747:20
   9: <hashbrown::map::Keys<K,V> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4865:9
  10: <hashbrown::set::Iter<K> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/set.rs:1705:9
  11: <std::collections::hash::set::Iter<K> as core::iter::traits::iterator::Iterator>::fold
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/collections/hash/set.rs:1513:19
  12: core::iter::traits::iterator::Iterator::for_each
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/traits/iterator.rs:858:9
  13: mmtk::util::reference_processor::ReferenceProcessor::enqueue
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:247:13
  14: mmtk::util::reference_processor::ReferenceProcessors::enqueue_refs
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:60:9
  15: <mmtk::util::reference_processor::RefEnqueue<VM> as mmtk::scheduler::work::GCWork<VM>>::do_work
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:559:9
  16: mmtk::scheduler::work::GCWork::do_work_with_stat
             at /home/wks/projects/mmtk-github/mmtk-core/src/scheduler/work.rs:45:9
  17: mmtk::scheduler::worker::GCWorker<VM>::run
             at /home/wks/projects/mmtk-github/mmtk-core/src/scheduler/worker.rs:244:13
  18: mmtk::memory_manager::start_worker
             at /home/wks/projects/mmtk-github/mmtk-core/src/memory_manager.rs:470:5
  19: start_worker
             at /home/wks/projects/mmtk-github/mmtk-openjdk/mmtk/src/api.rs:214:9
  20: _ZN19MMTkCollectorThread3runEv
             at /home/wks/projects/mmtk-github/openjdk/../mmtk-openjdk/openjdk/mmtkCollectorThread.cpp:36:15
  21: _ZN6Thread8call_runEv
             at /home/wks/projects/mmtk-github/openjdk/src/hotspot/share/runtime/thread.cpp:402:12
  22: thread_native_entry
             at /home/wks/projects/mmtk-github/openjdk/src/hotspot/os/linux/os_linux.cpp:826:19
  23: <unknown>
  24: <unknown>
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fatal runtime error: failed to initiate panic, error 5
fish: Job 1, 'MMTK_THREADS=1 MMTK_NO_REFERENC…' terminated by signal SIGABRT (Abort)

How to crash MarkCompact

We first patch mmtk-core so that MarkCompactSpace::trace_forward_object asserts the object has forwarding pointer. If an object is reachable during the SecondRoot stage or the RefForwarding stage, it must be live, and its forwarding pointer must have been computed during the CalculateForwarding stage.

diff --git a/src/policy/markcompactspace.rs b/src/policy/markcompactspace.rs
index e6d332919..533d7fa8c 100644
--- a/src/policy/markcompactspace.rs
+++ b/src/policy/markcompactspace.rs
@@ -251,7 +251,9 @@ impl<VM: VMBinding> MarkCompactSpace<VM> {
             queue.enqueue(object);
         }
 
-        Self::get_header_forwarding_pointer(object)
+        let result = Self::get_header_forwarding_pointer(object);
+        assert!(!result.is_null(), "Object {object} does not have a forwarding pointer");
+        result
     }
 
     pub fn test_and_mark(object: ObjectReference) -> bool {

Then run the same program with the same env vars except using MarkCompact. It will crash with the following log:

...
[2024-04-19T12:15:46Z DEBUG mmtk::util::reference_processor] SOFT reference table from 101 to 100 (0 enqueued)
[2024-04-19T12:15:46Z DEBUG mmtk::util::reference_processor] Ending ReferenceProcessor.scan(SOFT)
[2024-04-19T12:15:46Z TRACE mmtk::util::reference_processor] Add soft candidate: 0x4066bf28
[2024-04-19T12:15:46Z DEBUG mmtk::util::reference_processor] Starting ReferenceProcessor.scan(WEAK)
...
[2024-04-19T12:15:46Z TRACE mmtk::util::reference_processor] Forwarding reference: 0x4066bf28 (size: 40)
thread '<unnamed>' panicked at /home/wks/projects/mmtk-github/mmtk-core/src/policy/markcompactspace.rs:255:9:
Object 0x4066bf10 does not have a forwarding pointer
stack backtrace:
   0: rust_begin_unwind
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/panicking.rs:647:5
   1: core::panicking::panic_fmt
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/panicking.rs:72:14
   2: mmtk::policy::markcompactspace::MarkCompactSpace<VM>::trace_forward_object
             at /home/wks/projects/mmtk-github/mmtk-core/src/policy/markcompactspace.rs:255:9
   3: <mmtk::policy::markcompactspace::MarkCompactSpace<VM> as mmtk::policy::gc_work::PolicyTraceObject<VM>>::trace_object
             at /home/wks/projects/mmtk-github/mmtk-core/src/policy/markcompactspace.rs:141:13
   4: <mmtk::plan::markcompact::global::MarkCompact<VM> as mmtk::plan::global::PlanTraceObject<VM>>::trace_object
             at /home/wks/projects/mmtk-github/mmtk-core/src/plan/markcompact/global.rs:30:21
   5: <mmtk::scheduler::gc_work::PlanProcessEdges<VM,P,_> as mmtk::scheduler::gc_work::ProcessEdgesWork>::trace_object
             at /home/wks/projects/mmtk-github/mmtk-core/src/scheduler/gc_work.rs:931:9
   6: mmtk::util::reference_processor::ReferenceProcessor::get_forwarded_referent
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:220:9
   7: mmtk::util::reference_processor::ReferenceProcessor::forward::forward_reference
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:301:36
   8: mmtk::util::reference_processor::ReferenceProcessor::forward::{{closure}}
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:325:25
   9: core::iter::adapters::map::map_fold::{{closure}}
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/adapters/map.rs:89:28
  10: <hashbrown::map::Keys<K,V> as core::iter::traits::iterator::Iterator>::fold::{{closure}}
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4865:45
  11: <hashbrown::map::Iter<K,V> as core::iter::traits::iterator::Iterator>::fold::{{closure}}
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4749:13
  12: hashbrown::raw::RawIterRange<T>::fold_impl
             at /rust/deps/hashbrown-0.14.3/src/raw/mod.rs:3885:23
  13: <hashbrown::raw::RawIter<T> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/raw/mod.rs:4156:18
  14: <hashbrown::map::Iter<K,V> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4747:20
  15: <hashbrown::map::Keys<K,V> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/map.rs:4865:9
  16: <hashbrown::set::Iter<K> as core::iter::traits::iterator::Iterator>::fold
             at /rust/deps/hashbrown-0.14.3/src/set.rs:1705:9
  17: <std::collections::hash::set::Iter<K> as core::iter::traits::iterator::Iterator>::fold
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/collections/hash/set.rs:1513:19
  18: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::fold
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/adapters/map.rs:129:9
  19: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::fold
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/adapters/map.rs:129:9
  20: core::iter::traits::iterator::Iterator::for_each
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/traits/iterator.rs:858:9
  21: <hashbrown::map::HashMap<K,V,S,A> as core::iter::traits::collect::Extend<(K,V)>>::extend
             at /rust/deps/hashbrown-0.14.3/src/map.rs:6511:9
  22: <hashbrown::set::HashSet<T,S,A> as core::iter::traits::collect::Extend<T>>::extend
             at /rust/deps/hashbrown-0.14.3/src/set.rs:1355:9
  23: <std::collections::hash::set::HashSet<T,S> as core::iter::traits::collect::Extend<T>>::extend
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/collections/hash/set.rs:1070:9
  24: <std::collections::hash::set::HashSet<T,S> as core::iter::traits::collect::FromIterator<T>>::from_iter
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/collections/hash/set.rs:1026:13
  25: core::iter::traits::iterator::Iterator::collect
             at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/iter/traits/iterator.rs:2054:9
  26: mmtk::util::reference_processor::ReferenceProcessor::forward
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:322:27
  27: mmtk::util::reference_processor::ReferenceProcessors::forward_refs
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:74:9
  28: <mmtk::util::reference_processor::RefForwarding<E> as mmtk::scheduler::work::GCWork<<E as mmtk::scheduler::gc_work::ProcessEdgesWork>::VM>>::do_work
             at /home/wks/projects/mmtk-github/mmtk-core/src/util/reference_processor.rs:545:9
  29: mmtk::scheduler::work::GCWork::do_work_with_stat
             at /home/wks/projects/mmtk-github/mmtk-core/src/scheduler/work.rs:45:9
  30: mmtk::scheduler::worker::GCWorker<VM>::run
             at /home/wks/projects/mmtk-github/mmtk-core/src/scheduler/worker.rs:244:13
  31: mmtk::memory_manager::start_worker
             at /home/wks/projects/mmtk-github/mmtk-core/src/memory_manager.rs:470:5
  32: start_worker
             at /home/wks/projects/mmtk-github/mmtk-openjdk/mmtk/src/api.rs:214:9
  33: _ZN19MMTkCollectorThread3runEv
             at /home/wks/projects/mmtk-github/openjdk/../mmtk-openjdk/openjdk/mmtkCollectorThread.cpp:36:15
  34: _ZN6Thread8call_runEv
             at /home/wks/projects/mmtk-github/openjdk/src/hotspot/share/runtime/thread.cpp:402:12
  35: thread_native_entry
             at /home/wks/projects/mmtk-github/openjdk/src/hotspot/os/linux/os_linux.cpp:826:19
  36: <unknown>
  37: <unknown>
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fatal runtime error: failed to initiate panic, error 5
fish: Job 1, 'MMTK_THREADS=1 MMTK_NO_REFERENC…' terminated by signal SIGABRT (Abort)

What went wrong?

This portion of the log reveals the cause:

[2024-04-19T12:15:46Z DEBUG mmtk::util::reference_processor] Ending ReferenceProcessor.scan(SOFT)
[2024-04-19T12:15:46Z TRACE mmtk::util::reference_processor] Add soft candidate: 0x4066bf28
[2024-04-19T12:15:46Z DEBUG mmtk::util::reference_processor] Starting ReferenceProcessor.scan(WEAK)

A new soft candidate is discovered after processing all soft references. The OpenJDK binding discovers soft/weak/phantom references gradually during tracing. The new soft candidate 0x4066bf28 was added. Itself was traced. But when it was scanned, it added itself to the reference processor using add_soft_candidate. However, it never gets processed, and its referent is not traced, either.

In SemiSpace, the referent pointer still pointed to the from-space. During the Release stage, it tried to find references to enqueue, and the debug assertion discovered that some SoftReference still pointed to the from space.

In MarkCompact, things were a bit complicated. During CalculateForwarding, the GC computeed the forwarded addresses of all live objects. However, since the referent of the newly discovered SoftReference was not marked, it was considered dead, and was not assigned a forwarding pointer. However, during SecondRoot, the GC computed another transitive closure and found the SoftReference to be live. It then attempted to forward the referent (which is dead), and found it did not have a forwarding pointer. The error would have silently slipped away without checking, but after I removed ObjectReference::NULL, it started to require an explicit NULL check against the forwarding pointer, and caught the bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions