diff options
author | 2022-05-10 21:36:01 +0000 | |
---|---|---|
committer | 2022-05-12 02:02:34 +0000 | |
commit | 9f23704d66eb3454c42d38cb970b44fb5bd3b0f3 (patch) | |
tree | 6d1f3ab43dc4047ff4ee10d6481fe5ad94d70e06 | |
parent | cad672801b2ec3014409f2391021ff076bd28b9c (diff) |
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
-rw-r--r-- | TEST_MAPPING | 12 | ||||
-rw-r--r-- | test/2042-reference-processing/Android.bp | 40 | ||||
-rw-r--r-- | test/2042-reference-processing/expected-stderr.txt | 0 | ||||
-rw-r--r-- | test/2042-reference-processing/expected-stdout.txt | 2 | ||||
-rw-r--r-- | test/2042-reference-processing/info.txt | 6 | ||||
-rw-r--r-- | test/2042-reference-processing/src/Main.java | 311 | ||||
-rw-r--r-- | test/2043-reference-pauses/Android.bp | 40 | ||||
-rw-r--r-- | test/2043-reference-pauses/expected-stderr.txt | 0 | ||||
-rw-r--r-- | test/2043-reference-pauses/expected-stdout.txt | 2 | ||||
-rw-r--r-- | test/2043-reference-pauses/info.txt | 5 | ||||
-rw-r--r-- | test/2043-reference-pauses/src/Main.java | 300 | ||||
-rw-r--r-- | test/knownfailures.json | 7 |
12 files changed, 724 insertions, 1 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index afdced2e7c..91a19d2d08 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 0000000000..a73b8d0bd0 --- /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 0000000000..e69de29bb2 --- /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 0000000000..8b3e832833 --- /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 0000000000..e2d7a99436 --- /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 0000000000..ed670523ea --- /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 0000000000..a84aea25e8 --- /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 0000000000..e69de29bb2 --- /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 0000000000..8b3e832833 --- /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 0000000000..f76fa328cc --- /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 0000000000..dc64d9abf3 --- /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 3e485f6daf..083ff8002e 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."] }, { |