Revert^2 "Add reference processor tests""
This reverts commit bcf7ef1253691af61dab4062a358a2b903704d45.
Reason for revert: Land corrected version
PS1 is identical to aosp/1952438 .
PS3:
Moves timeUnreachable into a nested function to avoid accidentally
retained references.
Disables "debuggable" runs of 2042-reference-processing, which
sometimes take too long, since "debuggable" currrently disables use
of AOT code. Further, test runs often combine it with "poison" which
currently disables the fast interpreter.
Bug: 190867430
Bug: 189738006
Bug: 211784084
Test: Passes on host, Treehugger
Change-Id: I21d29158d0bc4c738fbf4c3f7c7ca881ae8b874b
diff --git a/TEST_MAPPING b/TEST_MAPPING
index afdced2..91a19d2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -401,6 +401,12 @@
"name": "art-run-test-2042-checker-dce-always-throw[com.google.android.art.apex]"
},
{
+ "name": "art-run-test-2042-reference-processing[com.google.android.art.apex]"
+ },
+ {
+ "name": "art-run-test-2043-reference-pauses[com.google.android.art.apex]"
+ },
+ {
"name": "art-run-test-2231-checker-heap-poisoning[com.google.android.art.apex]"
},
{
@@ -1699,6 +1705,12 @@
"name": "art-run-test-2042-checker-dce-always-throw"
},
{
+ "name": "art-run-test-2042-reference-processing"
+ },
+ {
+ "name": "art-run-test-2043-reference-pauses"
+ },
+ {
"name": "art-run-test-2231-checker-heap-poisoning"
},
{
diff --git a/test/2042-reference-processing/Android.bp b/test/2042-reference-processing/Android.bp
new file mode 100644
index 0000000..a73b8d0
--- /dev/null
+++ b/test/2042-reference-processing/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2042-reference-processing`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2042-reference-processing",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2042-reference-processing-expected-stdout",
+ ":art-run-test-2042-reference-processing-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2042-reference-processing-expected-stdout",
+ out: ["art-run-test-2042-reference-processing-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2042-reference-processing-expected-stderr",
+ out: ["art-run-test-2042-reference-processing-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2042-reference-processing/expected-stderr.txt b/test/2042-reference-processing/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2042-reference-processing/expected-stderr.txt
diff --git a/test/2042-reference-processing/expected-stdout.txt b/test/2042-reference-processing/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2042-reference-processing/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2042-reference-processing/info.txt b/test/2042-reference-processing/info.txt
new file mode 100644
index 0000000..e2d7a99
--- /dev/null
+++ b/test/2042-reference-processing/info.txt
@@ -0,0 +1,6 @@
+A test for reference processing correctness.
+
+The emphasis here is on fundamental properties. In particular, references to
+unreachable referents should be enqueued, and this should ensure that uncleared
+References don't point to objects for which References were enqueued. We also
+check various other ordering properties for java.lang.ref.References.
diff --git a/test/2042-reference-processing/src/Main.java b/test/2042-reference-processing/src/Main.java
new file mode 100644
index 0000000..ed67052
--- /dev/null
+++ b/test/2042-reference-processing/src/Main.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * Test that objects get finalized and their references cleared in the right order.
+ *
+ * We maintain a list of nominally MAX_LIVE_OBJS numbered finalizable objects.
+ * We then alternately drop the last 50, and add 50 more. When we see an object finalized
+ * or its reference cleared, we make sure that the preceding objects in its group of 50
+ * have also had their references cleared. We also perform a number of other more
+ * straightforward checks, such as ensuring that all references are eventually cleared,
+ * and all objects are finalized.
+ */
+public class Main {
+ // TODO(b/216481630) Enable CHECK_PHANTOM_REFS. This currently occasionally reports a few
+ // PhantomReferences as not enqueued. If this report is correct, this needs to be tracked
+ // down and fixed.
+ static final boolean CHECK_PHANTOM_REFS = false;
+
+ static final int MAX_LIVE_OBJS = 150;
+ static final int DROP_OBJS = 50; // Number of linked objects dropped in each batch.
+ static final int MIN_LIVE_OBJS = MAX_LIVE_OBJS - DROP_OBJS;
+ static final int TOTAL_OBJS = 200_000; // Allocate this many finalizable objects in total.
+ static final boolean REPORT_DROPS = false;
+ static volatile boolean pleaseStop;
+
+ AtomicInteger totalFinalized = new AtomicInteger(0);
+ Object phantomRefsLock = new Object();
+ int maxDropped = 0;
+ int liveObjects = 0;
+
+ // Number of next finalizable object to be allocated.
+ int nextAllocated = 0;
+
+ // List of finalizable objects in descending order. We add to the front and drop
+ // from the rear.
+ FinalizableObject listHead;
+
+ // A possibly incomplete list of FinalizableObject indices that were finalized, but
+ // have yet to be checked for consistency with reference processing.
+ ArrayBlockingQueue<Integer> finalized = new ArrayBlockingQueue<>(20_000);
+
+ // Maps from object number to Reference; Cleared references are deleted when queues are
+ // processed.
+ TreeMap<Integer, MyWeakReference> weakRefs = new TreeMap<>();
+ HashMap<Integer, MyPhantomReference> phantomRefs = new HashMap<>();
+
+ class FinalizableObject {
+ int n;
+ FinalizableObject next;
+ FinalizableObject(int num, FinalizableObject nextObj) {
+ n = num;
+ next = nextObj;
+ }
+ protected void finalize() {
+ if (!inPhantomRefs(n)) {
+ System.out.println("PhantomRef enqueued before finalizer ran");
+ }
+ totalFinalized.incrementAndGet();
+ if (!finalized.offer(n) && REPORT_DROPS) {
+ System.out.println("Dropped finalization of " + n);
+ }
+ }
+ }
+ ReferenceQueue<FinalizableObject> refQueue = new ReferenceQueue<>();
+ class MyWeakReference extends WeakReference<FinalizableObject> {
+ int n;
+ MyWeakReference(FinalizableObject obj) {
+ super(obj, refQueue);
+ n = obj.n;
+ }
+ };
+ class MyPhantomReference extends PhantomReference<FinalizableObject> {
+ int n;
+ MyPhantomReference(FinalizableObject obj) {
+ super(obj, refQueue);
+ n = obj.n;
+ }
+ }
+ boolean inPhantomRefs(int n) {
+ synchronized(phantomRefsLock) {
+ MyPhantomReference ref = phantomRefs.get(n);
+ if (ref == null) {
+ return false;
+ }
+ if (ref.n != n) {
+ System.out.println("phantomRef retrieval failed");
+ }
+ return true;
+ }
+ }
+
+ void CheckOKToClearWeak(int num) {
+ if (num > maxDropped) {
+ System.out.println("WeakRef to live object " + num + " was cleared/enqueued.");
+ }
+ int batchEnd = (num / DROP_OBJS + 1) * DROP_OBJS;
+ for (MyWeakReference wr : weakRefs.subMap(num + 1, batchEnd).values()) {
+ if (wr.n <= num || wr.n / DROP_OBJS != num / DROP_OBJS) {
+ throw new AssertionError("MyWeakReference logic error!");
+ }
+ // wr referent was dropped in same batch and precedes it in list.
+ if (wr.get() != null) {
+ // This violates the WeakReference spec, and can result in strong references
+ // to objects that have been cleaned.
+ System.out.println("WeakReference to " + wr.n
+ + " was erroneously cleared after " + num);
+ }
+ }
+ }
+
+ void CheckOKToClearPhantom(int num) {
+ if (num > maxDropped) {
+ System.out.println("PhantomRef to live object " + num + " was enqueued.");
+ }
+ MyWeakReference wr = weakRefs.get(num);
+ if (wr != null && wr.get() != null) {
+ System.out.println("PhantomRef cleared before WeakRef for " + num);
+ }
+ }
+
+ void emptyAndCheckQueues() {
+ // Check recently finalized objects for consistency with cleared references.
+ while (true) {
+ Integer num = finalized.poll();
+ if (num == null) {
+ break;
+ }
+ MyWeakReference wr = weakRefs.get(num);
+ if (wr != null) {
+ if (wr.n != num) {
+ System.out.println("Finalization logic error!");
+ }
+ if (wr.get() != null) {
+ System.out.println("Finalizing object with uncleared reference");
+ }
+ }
+ CheckOKToClearWeak(num);
+ }
+ // Check recently enqueued references for consistency.
+ while (true) {
+ Reference<FinalizableObject> ref = (Reference<FinalizableObject>) refQueue.poll();
+ if (ref == null) {
+ break;
+ }
+ if (ref instanceof MyWeakReference) {
+ MyWeakReference wr = (MyWeakReference) ref;
+ if (wr.get() != null) {
+ System.out.println("WeakRef " + wr.n + " enqueued but not cleared");
+ }
+ CheckOKToClearWeak(wr.n);
+ if (weakRefs.remove(Integer.valueOf(wr.n)) != ref) {
+ System.out.println("Missing WeakReference: " + wr.n);
+ }
+ } else if (ref instanceof MyPhantomReference) {
+ MyPhantomReference pr = (MyPhantomReference) ref;
+ CheckOKToClearPhantom(pr.n);
+ if (phantomRefs.remove(Integer.valueOf(pr.n)) != ref) {
+ System.out.println("Missing PhantomReference: " + pr.n);
+ }
+ } else {
+ System.out.println("Found unrecognized reference in queue");
+ }
+ }
+ }
+
+
+ /**
+ * Add n objects to the head of the list. These will be assigned the next n consecutive
+ * numbers after the current head of the list.
+ */
+ void addObjects(int n) {
+ for (int i = 0; i < n; ++i) {
+ int me = nextAllocated++;
+ listHead = new FinalizableObject(me, listHead);
+ weakRefs.put(me, new MyWeakReference(listHead));
+ synchronized(phantomRefsLock) {
+ phantomRefs.put(me, new MyPhantomReference(listHead));
+ }
+ }
+ liveObjects += n;
+ }
+
+ /**
+ * Drop n finalizable objects from the tail of the list. These are the lowest-numbered objects
+ * in the list.
+ */
+ void dropObjects(int n) {
+ FinalizableObject list = listHead;
+ FinalizableObject last = null;
+ if (n > liveObjects) {
+ System.out.println("Removing too many elements");
+ }
+ if (liveObjects == n) {
+ maxDropped = list.n;
+ listHead = null;
+ } else {
+ final int skip = liveObjects - n;
+ for (int i = 0; i < skip; ++i) {
+ last = list;
+ list = list.next;
+ }
+ int expected = nextAllocated - skip - 1;
+ if (list.n != expected) {
+ System.out.println("dropObjects found " + list.n + " but expected " + expected);
+ }
+ maxDropped = expected;
+ last.next = null;
+ }
+ liveObjects -= n;
+ }
+
+ void testLoop() {
+ System.out.println("Starting");
+ addObjects(MIN_LIVE_OBJS);
+ final int ITERS = (TOTAL_OBJS - MIN_LIVE_OBJS) / DROP_OBJS;
+ for (int i = 0; i < ITERS; ++i) {
+ addObjects(DROP_OBJS);
+ if (liveObjects != MAX_LIVE_OBJS) {
+ System.out.println("Unexpected live object count");
+ }
+ dropObjects(DROP_OBJS);
+ emptyAndCheckQueues();
+ }
+ dropObjects(MIN_LIVE_OBJS);
+ if (liveObjects != 0 || listHead != null) {
+ System.out.println("Unexpected live objecs at end");
+ }
+ if (maxDropped != TOTAL_OBJS - 1) {
+ System.out.println("Unexpected dropped object count: " + maxDropped);
+ }
+ for (int i = 0; i < 2; ++i) {
+ Runtime.getRuntime().gc();
+ System.runFinalization();
+ emptyAndCheckQueues();
+ }
+ if (!weakRefs.isEmpty()) {
+ System.out.println("Weak Reference map nonempty size = " + weakRefs.size());
+ }
+ if (CHECK_PHANTOM_REFS && !phantomRefs.isEmpty()) {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpected interrupt");
+ }
+ if (!phantomRefs.isEmpty()) {
+ System.out.println("Phantom Reference map nonempty size = " + phantomRefs.size());
+ System.out.print("First elements:");
+ int i = 0;
+ for (MyPhantomReference pr : phantomRefs.values()) {
+ System.out.print(" " + pr.n);
+ if (++i > 10) {
+ break;
+ }
+ }
+ System.out.println("");
+ }
+ }
+ if (totalFinalized.get() != TOTAL_OBJS) {
+ System.out.println("Finalized only " + totalFinalized + " objects");
+ }
+ }
+
+ static Runnable causeGCs = new Runnable() {
+ public void run() {
+ // Allocate a lot.
+ BigInteger counter = BigInteger.ZERO;
+ while (!pleaseStop) {
+ counter = counter.add(BigInteger.TEN);
+ }
+ // Look at counter to reduce chance of optimizing out the allocation.
+ if (counter.longValue() % 10 != 0) {
+ System.out.println("Bad causeGCs counter value: " + counter);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ Main theTest = new Main();
+ Thread gcThread = new Thread(causeGCs);
+ gcThread.setDaemon(true); // Terminate if main thread dies.
+ gcThread.start();
+ theTest.testLoop();
+ pleaseStop = true;
+ gcThread.join();
+ System.out.println("Finished");
+ }
+}
diff --git a/test/2043-reference-pauses/Android.bp b/test/2043-reference-pauses/Android.bp
new file mode 100644
index 0000000..a84aea2
--- /dev/null
+++ b/test/2043-reference-pauses/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2043-reference-pauses`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2043-reference-pauses",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2043-reference-pauses-expected-stdout",
+ ":art-run-test-2043-reference-pauses-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2043-reference-pauses-expected-stdout",
+ out: ["art-run-test-2043-reference-pauses-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2043-reference-pauses-expected-stderr",
+ out: ["art-run-test-2043-reference-pauses-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2043-reference-pauses/expected-stderr.txt b/test/2043-reference-pauses/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stderr.txt
diff --git a/test/2043-reference-pauses/expected-stdout.txt b/test/2043-reference-pauses/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2043-reference-pauses/info.txt b/test/2043-reference-pauses/info.txt
new file mode 100644
index 0000000..f76fa32
--- /dev/null
+++ b/test/2043-reference-pauses/info.txt
@@ -0,0 +1,5 @@
+Tests WeakReference processing and retention of objects needed by finalizers.
+
+Can be used as Reference.get() blocking benchmark by setting PRINT_TIMES to
+true. This will print maximum observed latencies for Reference.get() when
+significant memory is only reachable from SoftReferences and Finalizers.
diff --git a/test/2043-reference-pauses/src/Main.java b/test/2043-reference-pauses/src/Main.java
new file mode 100644
index 0000000..dc64d9a
--- /dev/null
+++ b/test/2043-reference-pauses/src/Main.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/**
+ * Basic test of WeakReferences with large amounts of memory that's only reachable through
+ * finalizers. Also makes sure that finalizer-reachable data is not collected.
+ * Can easily be modified to time Reference.get() blocking.
+ */
+public class Main {
+ static final boolean PRINT_TIMES = false; // true will cause benchmark failure.
+ // Data structures repeatedly allocated in background to trigger GC.
+ // Size of finalizer reachable trees.
+ static final int TREE_HEIGHT = 15; // Trees contain 2^TREE_HEIGHT -1 allocated objects.
+ // Number of finalizable tree-owning objects that exist at one point.
+ static final int N_RESURRECTING_OBJECTS = 10;
+ // Number of short-lived, not finalizer-reachable, objects allocated between trees.
+ static final int N_PLAIN_OBJECTS = 20_000;
+ // Number of SoftReferences to CBTs we allocate.
+ static final int N_SOFTREFS = 10;
+
+ static final boolean BACKGROUND_GC_THREAD = true;
+ static final int NBATCHES = 10;
+ static final int NREFS = PRINT_TIMES ? 1_000_000 : 300_000; // Multiple of NBATCHES.
+ static final int REFS_PER_BATCH = NREFS / NBATCHES;
+
+ static volatile boolean pleaseStop = false;
+
+ // Large array of WeakReferences filled and accessed by tests below.
+ ArrayList<WeakReference<Integer>> weakRefs = new ArrayList<>(NREFS);
+
+ /**
+ * Complete binary tree data structure. make(n) takes O(2^n) space.
+ */
+ static class CBT {
+ CBT left;
+ CBT right;
+ CBT(CBT l, CBT r) {
+ left = l;
+ right = r;
+ }
+ static CBT make(int n) {
+ if (n == 0) {
+ return null;
+ }
+ return new CBT(make(n - 1), make(n - 1));
+ }
+ /**
+ * Check that path described by bit-vector path has the correct length.
+ */
+ void check(int n, int path) {
+ CBT current = this;
+ for (int i = 0; i < n; i++, path = path >>> 1) {
+ // Unexpectedly short paths result in NPE.
+ if ((path & 1) == 0) {
+ current = current.left;
+ } else {
+ current = current.right;
+ }
+ }
+ if (current != null) {
+ System.out.println("Complete binary tree path too long");
+ }
+ }
+ }
+
+
+ /**
+ * A finalizable object that refers to O(2^TREE_HEIGHT) otherwise unreachable memory.
+ * When finalized, it creates a new identical object, making sure that one always stays
+ * around.
+ */
+ static class ResurrectingObject {
+ CBT stuff;
+ ResurrectingObject() {
+ stuff = CBT.make(TREE_HEIGHT);
+ }
+ static ResurrectingObject a[] = new ResurrectingObject[2];
+ static int i = 0;
+ static synchronized void allocOne() {
+ a[(++i) % 2] = new ResurrectingObject();
+ // Check the previous one to make it hard to optimize anything out.
+ if (i > 1) {
+ a[(i + 1) % 2].stuff.check(TREE_HEIGHT, i /* weirdly interpreted as path */);
+ }
+ }
+ protected void finalize() {
+ stuff.check(TREE_HEIGHT, 42 /* Some path descriptor */);
+ // Allocate a new one to replace this one.
+ allocOne();
+ }
+ }
+
+ void fillWeakRefs() {
+ for (int i = 0; i < NREFS; ++i) {
+ weakRefs.add(null);
+ }
+ }
+
+ /*
+ * Return maximum observed time in nanos to dereference a WeakReference to an unreachable
+ * object. weakRefs is presumed to be pre-filled to have the correct size.
+ */
+ long timeUnreachableInner() {
+ long maxNanos = 0;
+ // Fill weakRefs with WeakReferences to unreachable integers, a batch at a time.
+ // Then time and test .get() calls on carefully sampled array entries, some of which
+ // will have been cleared.
+ for (int i = 0; i < NBATCHES; ++i) {
+ for (int j = 0; j < REFS_PER_BATCH; ++j) {
+ weakRefs.set(i * REFS_PER_BATCH + j,
+ new WeakReference(new Integer(i * REFS_PER_BATCH + j)));
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpected exception");
+ }
+ // Iterate over the filled-in section of weakRefs, but look only at a subset of the
+ // elements, making sure the subsets for different top-level iterations are disjoint.
+ // Otherwise the get() calls here will extend the lifetimes of the referents, and we
+ // may never see any cleared WeakReferences.
+ for (int j = (i + 1) * REFS_PER_BATCH - i - 1; j >= 0; j -= NBATCHES) {
+ WeakReference<Integer> wr = weakRefs.get(j);
+ if (wr != null) {
+ long startNanos = System.nanoTime();
+ Integer referent = wr.get();
+ long totalNanos = System.nanoTime() - startNanos;
+ if (referent == null) {
+ // Optimization to reduce max space use and scanning time.
+ weakRefs.set(j, null);
+ }
+ maxNanos = Math.max(maxNanos, totalNanos);
+ if (referent != null && referent.intValue() != j) {
+ System.out.println("Unexpected referent; expected " + j + " got "
+ + referent.intValue());
+ }
+ }
+ }
+ }
+ return maxNanos;
+ }
+
+ /*
+ * Wrapper for the above that also checks that references were reclaimed.
+ * We do this separately to make sure any stack references from the core of the
+ * test are gone. Empirically, we otherwise sometimes see the zeroth WeakReference
+ * not reclaimed.
+ */
+ long timeUnreachable() {
+ long maxNanos = timeUnreachableInner();
+ Runtime.getRuntime().gc();
+ System.runFinalization(); // Presumed to wait for reference clearing.
+ for (int i = 0; i < NREFS; ++i) {
+ if (weakRefs.get(i) != null && weakRefs.get(i).get() != null) {
+ System.out.println("WeakReference to " + i + " wasn't cleared");
+ }
+ }
+ return maxNanos;
+ }
+
+ /**
+ * Return maximum observed time in nanos to dereference a WeakReference to a reachable
+ * object. Overwrites weakRefs, which is presumed to have NREFS entries already.
+ */
+ long timeReachable() {
+ long maxNanos = 0;
+ // Similar to the above, but we use WeakReferences to otherwise reachable objects,
+ // which should thus not get cleared.
+ Integer[] strongRefs = new Integer[NREFS];
+ for (int i = 0; i < NBATCHES; ++i) {
+ for (int j = i * REFS_PER_BATCH; j < (i + 1) * NREFS / NBATCHES; ++j) {
+ Integer newObj = new Integer(j);
+ strongRefs[j] = newObj;
+ weakRefs.set(j, new WeakReference(newObj));
+ }
+ for (int j = (i + 1) * REFS_PER_BATCH - 1; j >= 0; --j) {
+ WeakReference<Integer> wr = weakRefs.get(j);
+ long startNanos = System.nanoTime();
+ Integer referent = wr.get();
+ long totalNanos = System.nanoTime() - startNanos;
+ maxNanos = Math.max(maxNanos, totalNanos);
+ if (referent == null) {
+ System.out.println("Unexpectedly cleared referent at " + j);
+ } else if (referent.intValue() != j) {
+ System.out.println("Unexpected reachable referent; expected " + j + " got "
+ + referent.intValue());
+ }
+ }
+ }
+ Reference.reachabilityFence(strongRefs);
+ return maxNanos;
+ }
+
+ void runTest() {
+ System.out.println("Starting");
+ fillWeakRefs();
+ long unreachableNanos = timeUnreachable();
+ if (PRINT_TIMES) {
+ System.out.println("Finished timeUnrechable()");
+ }
+ long reachableNanos = timeReachable();
+ String unreachableMillis =
+ String. format("%,.3f", ((double) unreachableNanos) / 1_000_000);
+ String reachableMillis =
+ String. format("%,.3f", ((double) reachableNanos) / 1_000_000);
+ if (PRINT_TIMES) {
+ System.out.println(
+ "Max time for WeakReference.get (unreachable): " + unreachableMillis);
+ System.out.println(
+ "Max time for WeakReference.get (reachable): " + reachableMillis);
+ }
+ // Only report extremely egregious pauses to avoid spurious failures.
+ if (unreachableNanos > 10_000_000_000L) {
+ System.out.println("WeakReference.get (unreachable) time unreasonably long");
+ }
+ if (reachableNanos > 10_000_000_000L) {
+ System.out.println("WeakReference.get (reachable) time unreasonably long");
+ }
+ }
+
+ /**
+ * Allocate and GC a lot, while keeping significant amounts of finalizer and
+ * SoftReference-reachable memory around.
+ */
+ static Runnable allocFinalizable = new Runnable() {
+ public void run() {
+ // Allocate and drop some finalizable objects that take a long time
+ // to mark. Designed to be hard to optimize away. Each of these objects will
+ // build a new one in its finalizer before really going away.
+ ArrayList<SoftReference<CBT>> softRefs = new ArrayList<>(N_SOFTREFS);
+ for (int i = 0; i < N_SOFTREFS; ++i) {
+ // These should not normally get reclaimed, since we shouldn't run out of
+ // memory. They do increase tracing time.
+ softRefs.add(new SoftReference(CBT.make(TREE_HEIGHT)));
+ }
+ for (int i = 0; i < N_RESURRECTING_OBJECTS; ++i) {
+ ResurrectingObject.allocOne();
+ }
+ BigInteger counter = BigInteger.ZERO;
+ for (int i = 1; !pleaseStop; ++i) {
+ // Allocate a lot of short-lived objects, using BigIntegers to minimize the chance
+ // of the allocation getting optimized out. This makes things slightly more
+ // realistic, since not all objects will be finalizer reachable.
+ for (int j = 0; j < N_PLAIN_OBJECTS / 2; ++j) {
+ counter = counter.add(BigInteger.TEN);
+ }
+ // Look at counter to reduce chance of optimizing out the allocation.
+ if (counter.longValue() % 10 != 0) {
+ System.out.println("Bad allocFinalizable counter value: " + counter);
+ }
+ // Explicitly collect here, mostly to prevent heap growth. Otherwise we get
+ // ahead of the GC and eventually block on it.
+ Runtime.getRuntime().gc();
+ if (PRINT_TIMES && i % 100 == 0) {
+ System.out.println("Collected " + i + " times");
+ }
+ }
+ // To be safe, access softRefs.
+ final CBT sample = softRefs.get(N_SOFTREFS / 2).get();
+ if (sample != null) {
+ sample.check(TREE_HEIGHT, 47 /* some path descriptor */);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ Main theTest = new Main();
+ Thread allocThread = null;
+ if (BACKGROUND_GC_THREAD) {
+ allocThread = new Thread(allocFinalizable);
+ allocThread.setDaemon(true); // Terminate if main thread dies.
+ allocThread.start();
+ }
+ theTest.runTest();
+ if (BACKGROUND_GC_THREAD) {
+ pleaseStop = true;
+ allocThread.join();
+ }
+ System.out.println("Finished");
+ }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 3e485f6..083ff80 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1358,8 +1358,13 @@
"description": "Interpreting BigInteger.add() is too slow (timeouts)"
},
{
- "tests": ["2029-contended-monitors"],
+ "tests": ["2029-contended-monitors", "2043-reference-pauses"],
"variant": "interpreter | interp-ac | gcstress | trace",
+ "description": ["Slow tests. Prone to timeouts."]
+ },
+ {
+ "tests": ["2042-reference-processing"],
+ "variant": "interpreter | interp-ac | gcstress | trace | debuggable",
"description": ["Slow test. Prone to timeouts."]
},
{