aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlmaz Mingaleev <mingaleev@google.com>2024-04-02 09:56:45 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-04-02 09:56:45 +0000
commitac9e0d0f68ab8834069cd48b1c8d9474fae6743c (patch)
treeae39a71c65f8f835e241e84fdbae2a7180a199a0
parent2c7effc59d0f326e753ff195cef49924ae8e3064 (diff)
parentb124a12cf175b4c65434c52a6369802d7ffa48bd (diff)
downloadlibcore-expected_upstream.tar.gz
Merge "Import CleanerTest from jdk-21.0.2-ga" into expected_upstreamexpected_upstream
-rw-r--r--EXPECTED_UPSTREAM1
-rw-r--r--ojluni/src/test/java/lang/ref/CleanerTest.java571
2 files changed, 572 insertions, 0 deletions
diff --git a/EXPECTED_UPSTREAM b/EXPECTED_UPSTREAM
index 5ce6c8f7cf0..3528a78ccbf 100644
--- a/EXPECTED_UPSTREAM
+++ b/EXPECTED_UPSTREAM
@@ -2026,6 +2026,7 @@ ojluni/src/test/java/lang/invoke/VarHandles/accessibility/pkg/C.java,jdk17u/jdk-
ojluni/src/test/java/lang/invoke/VarHandles/accessibility/pkg/subpkg/B_extends_A.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/lang/invoke/VarHandles/accessibility/pkg/subpkg/B_extends_A.java
ojluni/src/test/java/lang/invoke/VarHandles/accessibility/pkg/subpkg/C.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/lang/invoke/VarHandles/accessibility/pkg/subpkg/C.java
ojluni/src/test/java/lang/invoke/VarHandles/generate-vh-tests.sh,jdk11u/jdk-11.0.13-ga,test/jdk/java/lang/invoke/VarHandles/generate-vh-tests.sh
+ojluni/src/test/java/lang/ref/CleanerTest.java,jdk21u/jdk-21.0.2-ga,test/jdk/java/lang/ref/CleanerTest.java
ojluni/src/test/java/lang/reflect/records/CheckEqualityIsBasedOnFields.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/lang/reflect/records/CheckEqualityIsBasedOnFields.java
ojluni/src/test/java/lang/reflect/records/RecordReflectionTest.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/lang/reflect/records/RecordReflectionTest.java
ojluni/src/test/java/math/BigDecimal/AddTests.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/math/BigDecimal/AddTests.java
diff --git a/ojluni/src/test/java/lang/ref/CleanerTest.java b/ojluni/src/test/java/lang/ref/CleanerTest.java
new file mode 100644
index 00000000000..e4119338252
--- /dev/null
+++ b/ojluni/src/test/java/lang/ref/CleanerTest.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Objects;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import jdk.internal.ref.PhantomCleanable;
+import jdk.internal.ref.CleanerFactory;
+
+import jdk.test.whitebox.WhiteBox;
+
+import jdk.test.lib.Utils;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @library /lib/testlibrary /test/lib
+ * @build jdk.test.whitebox.WhiteBox
+ * jdk.test.lib.Utils
+ * jdk.test.lib.Asserts
+ * jdk.test.lib.JDKToolFinder
+ * jdk.test.lib.JDKToolLauncher
+ * jdk.test.lib.Platform
+ * jdk.test.lib.process.*
+ * @modules java.base/jdk.internal.misc
+ * java.base/jdk.internal.ref
+ * java.management
+ * @compile CleanerTest.java
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run testng/othervm
+ * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:.
+ * -verbose:gc CleanerTest
+ */
+
+@Test
+public class CleanerTest {
+ // A common CleaningService used by the test for notifications
+ static final Cleaner COMMON = CleanerFactory.cleaner();
+
+ // Access to WhiteBox utilities
+ static final WhiteBox whitebox = WhiteBox.getWhiteBox();
+
+ @DataProvider(name = "cleanerSuppliers")
+ public Object[][] factories() {
+ Supplier<Cleaner> supplier1 = () -> Cleaner.create();
+ Supplier<Cleaner> supplier2 = () -> Cleaner.create(Thread.ofVirtual().factory());
+ return new Object[][] { { supplier1 }, { supplier2 } };
+ }
+
+ /**
+ * Test that sequences of the various actions on a Reference
+ * and on the Cleanable instance have the desired result.
+ * The test cases are generated for each of phantom, weak and soft
+ * references.
+ * The sequence of actions includes all permutations to an initial
+ * list of actions including clearing the ref and resulting garbage
+ * collection actions on the reference and explicitly performing
+ * the cleaning action.
+ */
+ @Test(dataProvider = "cleanerSuppliers")
+ @SuppressWarnings("unchecked")
+ public void testCleanableActions(Supplier<Cleaner> supplier) {
+ Cleaner cleaner = supplier.get();
+
+ // Individually
+ generateCases(cleaner, c -> c.clearRef());
+ generateCases(cleaner, c -> c.doClean());
+
+ // Pairs
+ generateCases(cleaner, c -> c.doClean(), c -> c.clearRef());
+
+ CleanableCase s = setupPhantom(COMMON, cleaner);
+ cleaner = null;
+ checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned");
+ }
+
+ /**
+ * Test the jdk.internal.misc APIs with sequences of the various actions
+ * on a Reference and on the Cleanable instance have the desired result.
+ * The test cases are generated for each of phantom, weak and soft
+ * references.
+ * The sequence of actions includes all permutations to an initial
+ * list of actions including clearing the ref and resulting garbage
+ * collection actions on the reference, explicitly performing
+ * the cleanup and explicitly clearing the cleaning action.
+ */
+ @Test(dataProvider = "cleanerSuppliers")
+ @SuppressWarnings("unchecked")
+ public void testRefSubtypes(Supplier<Cleaner> supplier) {
+ Cleaner cleaner = supplier.get();
+
+ // Individually
+ generateCasesInternal(cleaner, c -> c.clearRef());
+ generateCasesInternal(cleaner, c -> c.doClean());
+ generateCasesInternal(cleaner, c -> c.doClear());
+
+ // Pairs
+ generateCasesInternal(cleaner,
+ c -> c.doClear(), c -> c.doClean());
+
+ // Triplets
+ generateCasesInternal(cleaner,
+ c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
+
+ generateExceptionCasesInternal(cleaner);
+
+ CleanableCase s = setupPhantom(COMMON, cleaner);
+ cleaner = null;
+ checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned");
+ }
+
+ /**
+ * Generate tests using the runnables for each of phantom, weak,
+ * and soft references.
+ * @param cleaner the cleaner
+ * @param runnables the sequence of actions on the test case
+ */
+ @SuppressWarnings("unchecked")
+ void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
+ generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables);
+ }
+
+ @SuppressWarnings("unchecked")
+ void generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
+ generateCases(() -> setupPhantomSubclass(cleaner, null),
+ runnables.length, runnables);
+ }
+
+ @SuppressWarnings("unchecked")
+ void generateExceptionCasesInternal(Cleaner cleaner) {
+ generateCases(() -> setupPhantomSubclassException(cleaner, null),
+ 1, c -> c.clearRef());
+ }
+
+ /**
+ * Generate all permutations of the sequence of runnables
+ * and test each one.
+ * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges.
+ * @param generator the supplier of a CleanableCase
+ * @param n the first index to interchange
+ * @param runnables the sequence of actions
+ */
+ @SuppressWarnings("unchecked")
+ void generateCases(Supplier<CleanableCase> generator, int n,
+ Consumer<CleanableCase> ... runnables) {
+ if (n == 1) {
+ CleanableCase test = generator.get();
+ try {
+ verifyGetRef(test);
+
+ // Apply the sequence of actions on the Ref
+ for (Consumer<CleanableCase> c : runnables) {
+ c.accept(test);
+ }
+ verify(test);
+ } catch (Exception e) {
+ Assert.fail(test.toString(), e);
+ }
+ } else {
+ for (int i = 0; i < n - 1; i += 1) {
+ generateCases(generator, n - 1, runnables);
+ Consumer<CleanableCase> t = runnables[n - 1];
+ int ndx = ((n & 1) == 0) ? i : 0;
+ runnables[n - 1] = runnables[ndx];
+ runnables[ndx] = t;
+ }
+ generateCases(generator, n - 1, runnables);
+ }
+ }
+
+ /**
+ * Verify the test case.
+ * Any actions directly on the Reference or Cleanable have been executed.
+ * The CleanableCase under test is given a chance to do the cleanup
+ * by forcing a GC.
+ * The result is compared with the expected result computed
+ * from the sequence of operations on the Cleanable.
+ * The Cleanable itself should have been cleanedup.
+ *
+ * @param test A CleanableCase containing the references
+ */
+ void verify(CleanableCase test) {
+ System.out.println(test);
+ int r = test.expectedResult();
+
+ CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
+ test.clearCleanable(); // release this hard reference
+
+ checkCleaned(test.getSemaphore(),
+ r == CleanableCase.EV_CLEAN,
+ "Cleanable was cleaned");
+ checkCleaned(cc.getSemaphore(), true,
+ "The reference to the Cleanable was freed");
+ }
+
+ /**
+ * Verify that the reference.get works (or not) as expected.
+ * It handles the cases where UnsupportedOperationException is expected.
+ *
+ * @param test the CleanableCase
+ */
+ void verifyGetRef(CleanableCase test) {
+ Reference<?> r = (Reference) test.getCleanable();
+ try {
+ Object o = r.get();
+ Reference<?> expectedRef = test.getRef();
+ Assert.assertEquals(expectedRef.get(), o,
+ "Object reference incorrect");
+ if (r.getClass().getName().endsWith("CleanableRef")) {
+ Assert.fail("should not be able to get referent");
+ }
+ } catch (UnsupportedOperationException uoe) {
+ if (r.getClass().getName().endsWith("CleanableRef")) {
+ // Expected exception
+ } else {
+ Assert.fail("Unexpected exception from subclassed cleanable: " +
+ uoe.getMessage() + ", class: " + r.getClass());
+ }
+ }
+ }
+
+ /**
+ * Test that releasing the reference to the Cleaner service allows it to be
+ * be freed.
+ */
+ @Test(dataProvider = "cleanerSuppliers")
+ public void testCleanerTermination(Supplier<Cleaner> supplier) {
+ ReferenceQueue<Object> queue = new ReferenceQueue<>();
+ Cleaner service = supplier.get();
+
+ PhantomReference<Object> ref = new PhantomReference<>(service, queue);
+ System.gc();
+ // Clear the Reference to the cleaning service and force a gc.
+ service = null;
+ System.gc();
+ try {
+ Reference<?> r = queue.remove(1000L);
+ Assert.assertNotNull(r, "queue.remove timeout,");
+ Assert.assertEquals(r, ref, "Wrong Reference dequeued");
+ } catch (InterruptedException ie) {
+ System.out.printf("queue.remove Interrupted%n");
+ }
+ }
+
+ /**
+ * Check a semaphore having been released by cleanup handler.
+ * Force a number of GC cycles to give the GC a chance to process
+ * the Reference and for the cleanup action to be run.
+ * Use a larger number of cycles to wait for an expected cleaning to occur.
+ *
+ * @param semaphore a Semaphore
+ * @param expectCleaned true if cleaning the function should have been run, otherwise not run
+ * @param msg a message describing the cleaning function expected to be run or not run
+ */
+ static void checkCleaned(Semaphore semaphore, boolean expectCleaned, String msg) {
+ long max_cycles = expectCleaned ? 10 : 3;
+ long cycle = 0;
+ for (; cycle < max_cycles; cycle++) {
+ // Force GC
+ whitebox.fullGC();
+
+ try {
+ if (semaphore.tryAcquire(Utils.adjustTimeout(200L), TimeUnit.MILLISECONDS)) {
+ System.out.printf(" Cleanable cleaned in cycle: %d%n", cycle);
+ if (!expectCleaned)
+ Assert.fail("Should not have been run: " + msg);
+ return;
+ }
+ } catch (InterruptedException ie) {
+ // retry in outer loop
+ }
+ }
+ // Object has not been cleaned
+ if (expectCleaned)
+ Assert.fail("Should have been run: " + msg);
+ }
+
+ /**
+ * Create a CleanableCase for a PhantomReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupPhantom(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+ Cleaner.Cleanable c1 = cleaner.register(obj, () -> s1.release());
+
+ return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+ }
+
+ /**
+ * Create a CleanableCase for a PhantomReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupPhantomSubclass(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ }
+ };
+
+ return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+ }
+
+ /**
+ * Create a CleanableCase for a PhantomReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupPhantomSubclassException(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ throw new RuntimeException("Exception thrown to cleaner thread");
+ }
+ };
+
+ return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true);
+ }
+
+ /**
+ * CleanableCase encapsulates the objects used for a test.
+ * The reference to the object is not held directly,
+ * but in a Reference object that can be cleared.
+ * The semaphore is used to count whether the cleanup occurred.
+ * It can be awaited on to determine that the cleanup has occurred.
+ * It can be checked for non-zero to determine if it was
+ * invoked or if it was invoked twice (a bug).
+ */
+ static class CleanableCase {
+
+ private volatile Reference<?> ref;
+ private volatile Cleaner.Cleanable cleanup;
+ private final Semaphore semaphore;
+ private final boolean throwsEx;
+ private final int[] events; // Sequence of calls to clean, clear, etc.
+ private volatile int eventNdx;
+
+ public static int EV_UNKNOWN = 0;
+ public static int EV_CLEAR = 1;
+ public static int EV_CLEAN = 2;
+ public static int EV_UNREF = 3;
+ public static int EV_CLEAR_CLEANUP = 4;
+
+
+ CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+ Semaphore semaphore) {
+ this.ref = ref;
+ this.cleanup = cleanup;
+ this.semaphore = semaphore;
+ this.throwsEx = false;
+ this.events = new int[4];
+ this.eventNdx = 0;
+ }
+ CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+ Semaphore semaphore,
+ boolean throwsEx) {
+ this.ref = ref;
+ this.cleanup = cleanup;
+ this.semaphore = semaphore;
+ this.throwsEx = throwsEx;
+ this.events = new int[4];
+ this.eventNdx = 0;
+ }
+
+ public Reference<?> getRef() {
+ return ref;
+ }
+
+ public void clearRef() {
+ addEvent(EV_UNREF);
+ ref.clear();
+ }
+
+ public Cleaner.Cleanable getCleanable() {
+ return cleanup;
+ }
+
+ public void doClean() {
+ try {
+ addEvent(EV_CLEAN);
+ cleanup.clean();
+ } catch (RuntimeException ex) {
+ if (!throwsEx) {
+ // unless it is known this case throws an exception, rethrow
+ throw ex;
+ }
+ }
+ }
+
+ public void doClear() {
+ addEvent(EV_CLEAR);
+ ((Reference)cleanup).clear();
+ }
+
+ public void clearCleanable() {
+ addEvent(EV_CLEAR_CLEANUP);
+ cleanup = null;
+ }
+
+ public Semaphore getSemaphore() {
+ return semaphore;
+ }
+
+ public boolean isCleaned() {
+ return semaphore.availablePermits() != 0;
+ }
+
+ private synchronized void addEvent(int e) {
+ events[eventNdx++] = e;
+ }
+
+ /**
+ * Computed the expected result from the sequence of events.
+ * If EV_CLEAR appears before anything else, it is cleared.
+ * If EV_CLEAN appears before EV_UNREF, then it is cleaned.
+ * Anything else is Unknown.
+ * @return EV_CLEAR if the cleanup should occur;
+ * EV_CLEAN if the cleanup should occur;
+ * EV_UNKNOWN if it is unknown.
+ */
+ public synchronized int expectedResult() {
+ // Test if EV_CLEAR appears before anything else
+ int clearNdx = indexOfEvent(EV_CLEAR);
+ int cleanNdx = indexOfEvent(EV_CLEAN);
+ int unrefNdx = indexOfEvent(EV_UNREF);
+ if (clearNdx < cleanNdx) {
+ return EV_CLEAR;
+ }
+ if (cleanNdx < clearNdx || cleanNdx < unrefNdx) {
+ return EV_CLEAN;
+ }
+ if (unrefNdx < eventNdx) {
+ return EV_CLEAN;
+ }
+
+ return EV_UNKNOWN;
+ }
+
+ private synchronized int indexOfEvent(int e) {
+ for (int i = 0; i < eventNdx; i++) {
+ if (events[i] == e) {
+ return i;
+ }
+ }
+ return eventNdx;
+ }
+
+ private static final String[] names =
+ {"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"};
+
+ public String eventName(int event) {
+ return names[event];
+ }
+
+ public synchronized String eventsString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ for (int i = 0; i < eventNdx; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(eventName(events[i]));
+ }
+ sb.append(']');
+ sb.append(", throwEx: ");
+ sb.append(throwsEx);
+ return sb.toString();
+ }
+
+ public String toString() {
+ return String.format("Case: %s, expect: %s, events: %s",
+ getRef().getClass().getName(),
+ eventName(expectedResult()), eventsString());
+ }
+ }
+
+ /**
+ * Verify that casting a Cleanup to a Reference is not allowed to
+ * get the referent or clear the reference.
+ */
+ @Test(dataProvider = "cleanerSuppliers")
+ @SuppressWarnings("rawtypes")
+ public void testReferentNotAvailable(Supplier<Cleaner> supplier) {
+ Cleaner cleaner = supplier.get();
+ Semaphore s1 = new Semaphore(0);
+
+ Object obj = new String("a new string");
+ Cleaner.Cleanable c = cleaner.register(obj, () -> s1.release());
+ Reference r = (Reference) c;
+ try {
+ Object o = r.get();
+ System.out.printf("r: %s%n", Objects.toString(o));
+ Assert.fail("should not be able to get the referent from Cleanable");
+ } catch (UnsupportedOperationException uoe) {
+ // expected
+ }
+
+ try {
+ r.clear();
+ Assert.fail("should not be able to clear the referent from Cleanable");
+ } catch (UnsupportedOperationException uoe) {
+ // expected
+ }
+
+ obj = null;
+ checkCleaned(s1, true, "reference was cleaned");
+ cleaner = null;
+ }
+
+ /**
+ * Test the Cleaner from the CleanerFactory.
+ */
+ @Test
+ public void testCleanerFactory() {
+ Cleaner cleaner = CleanerFactory.cleaner();
+
+ Object obj = new Object();
+ CleanableCase s = setupPhantom(cleaner, obj);
+ obj = null;
+ checkCleaned(s.getSemaphore(), true,
+ "Object cleaned using internal CleanerFactory.cleaner()");
+ }
+}