/* * Copyright (c) 2015, 2022, 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.
*/
/** * 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") publicvoid 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") publicvoid 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);
}
/** * 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") publicvoid 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
*/ staticvoid 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());
/** * 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);
/** * 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) { protectedvoid performCleanup() {
s1.release(); thrownew RuntimeException("Exception thrown to cleaner thread");
}
};
/** * 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).
*/ staticclass CleanableCase {
privatevolatile Reference<?> ref; privatevolatile Cleaner.Cleanable cleanup; privatefinal Semaphore semaphore; privatefinalboolean throwsEx; privatefinalint[] events; // Sequence of calls to clean, clear, etc. privatevolatileint eventNdx;
public Cleaner.Cleanable getCleanable() { return cleanup;
}
publicvoid doClean() { try {
addEvent(EV_CLEAN);
cleanup.clean();
} catch (RuntimeException ex) { if (!throwsEx) { // unless it is known this case throws an exception, rethrow throw ex;
}
}
}
/** * 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.
*/ publicsynchronizedint 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;
}
privatesynchronizedint indexOfEvent(int e) { for (int i = 0; i < eventNdx; i++) { if (events[i] == e) { return i;
}
} return eventNdx;
}
/** * Verify that casting a Cleanup to a Reference is not allowed to * get the referent or clear the reference.
*/
@Test(dataProvider = "cleanerSuppliers")
@SuppressWarnings("rawtypes") publicvoid 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
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.