1 /* 2 * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package test.java.lang.ref; 24 25 import android.system.SystemCleaner; 26 import dalvik.system.VMRuntime; 27 28 import java.lang.ref.Cleaner; 29 import java.lang.ref.Reference; 30 import java.lang.ref.PhantomReference; 31 import java.lang.ref.ReferenceQueue; 32 import java.util.Objects; 33 import java.util.concurrent.Executors; 34 import java.util.concurrent.Semaphore; 35 import java.util.concurrent.TimeUnit; 36 import java.util.function.Consumer; 37 import java.util.function.Supplier; 38 39 import jdk.internal.ref.PhantomCleanable; 40 import jdk.internal.ref.CleanerFactory; 41 42 import org.testng.Assert; 43 44 import org.testng.annotations.DataProvider; 45 import org.testng.annotations.Test; 46 47 /* 48 * @test 49 * @library /lib/testlibrary /test/lib 50 * @build jdk.test.whitebox.WhiteBox 51 * jdk.test.lib.Utils 52 * jdk.test.lib.Asserts 53 * jdk.test.lib.JDKToolFinder 54 * jdk.test.lib.JDKToolLauncher 55 * jdk.test.lib.Platform 56 * jdk.test.lib.process.* 57 * @modules java.base/jdk.internal.misc 58 * java.base/jdk.internal.ref 59 * java.management 60 * @compile CleanerTest.java 61 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox 62 * @run testng/othervm 63 * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. 64 * -verbose:gc CleanerTest 65 */ 66 67 @Test 68 public class CleanerTest { 69 // A common CleaningService used by the test for notifications 70 static final Cleaner COMMON = CleanerFactory.cleaner(); 71 72 // Android-removed: Remove sun.hotspot.WhiteBox usage 73 // Access to WhiteBox utilities 74 // static final WhiteBox whitebox = WhiteBox.getWhiteBox(); 75 76 @DataProvider(name = "cleanerSuppliers") factories()77 public Object[][] factories() { 78 Supplier<Cleaner> supplier1 = () -> Cleaner.create(); 79 // Android-removed: virtual threads are not available on Android. 80 // Supplier<Cleaner> supplier2 = () -> Cleaner.create(Thread.ofVirtual().factory()); 81 // Android-added: creating with a ThreadFactory for test coverage sake. 82 Supplier<Cleaner> supplier2 = () -> Cleaner.create(Executors.defaultThreadFactory()); 83 return new Object[][] { { supplier1 }, { supplier2 } }; 84 } 85 86 /** 87 * Test that sequences of the various actions on a Reference 88 * and on the Cleanable instance have the desired result. 89 * The test cases are generated for each of phantom, weak and soft 90 * references. 91 * The sequence of actions includes all permutations to an initial 92 * list of actions including clearing the ref and resulting garbage 93 * collection actions on the reference and explicitly performing 94 * the cleaning action. 95 */ 96 @Test(dataProvider = "cleanerSuppliers") 97 @SuppressWarnings("unchecked") testCleanableActions(Supplier<Cleaner> supplier)98 public void testCleanableActions(Supplier<Cleaner> supplier) { 99 Cleaner cleaner = supplier.get(); 100 101 // Individually 102 generateCases(cleaner, c -> c.clearRef()); 103 generateCases(cleaner, c -> c.doClean()); 104 105 // Pairs 106 generateCases(cleaner, c -> c.doClean(), c -> c.clearRef()); 107 108 CleanableCase s = setupPhantom(COMMON, cleaner); 109 cleaner = null; 110 checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned"); 111 } 112 113 // Android-added: Test (trivially) SystemCleaner by repeating above. 114 /** 115 * Check that SystemCleaner.cleaner() also behaves as above. 116 */ 117 @Test 118 @SuppressWarnings("unchecked") testSystemCleanerActions(Supplier<Cleaner> supplier)119 public void testSystemCleanerActions(Supplier<Cleaner> supplier) { 120 Cleaner cleaner = supplier.get(); 121 122 // Individually 123 generateCases(cleaner, c -> c.clearRef()); 124 generateCases(cleaner, c -> c.doClean()); 125 126 // Pairs 127 generateCases(cleaner, c -> c.doClean(), c -> c.clearRef()); 128 } 129 130 /** 131 * Test the jdk.internal.misc APIs with sequences of the various actions 132 * on a Reference and on the Cleanable instance have the desired result. 133 * The test cases are generated for each of phantom, weak and soft 134 * references. 135 * The sequence of actions includes all permutations to an initial 136 * list of actions including clearing the ref and resulting garbage 137 * collection actions on the reference, explicitly performing 138 * the cleanup and explicitly clearing the cleaning action. 139 */ 140 @Test(dataProvider = "cleanerSuppliers") 141 @SuppressWarnings("unchecked") testRefSubtypes(Supplier<Cleaner> supplier)142 public void testRefSubtypes(Supplier<Cleaner> supplier) { 143 Cleaner cleaner = supplier.get(); 144 145 // Individually 146 generateCasesInternal(cleaner, c -> c.clearRef()); 147 generateCasesInternal(cleaner, c -> c.doClean()); 148 generateCasesInternal(cleaner, c -> c.doClear()); 149 150 // Pairs 151 generateCasesInternal(cleaner, 152 c -> c.doClear(), c -> c.doClean()); 153 154 // Triplets 155 generateCasesInternal(cleaner, 156 c -> c.doClear(), c -> c.doClean(), c -> c.clearRef()); 157 158 generateExceptionCasesInternal(cleaner); 159 160 CleanableCase s = setupPhantom(COMMON, cleaner); 161 cleaner = null; 162 checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned"); 163 } 164 165 /** 166 * Generate tests using the runnables for each of phantom, weak, 167 * and soft references. 168 * @param cleaner the cleaner 169 * @param runnables the sequence of actions on the test case 170 */ 171 @SuppressWarnings("unchecked") generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables)172 void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) { 173 generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables); 174 } 175 176 @SuppressWarnings("unchecked") generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables)177 void generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables) { 178 generateCases(() -> setupPhantomSubclass(cleaner, null), 179 runnables.length, runnables); 180 } 181 182 @SuppressWarnings("unchecked") generateExceptionCasesInternal(Cleaner cleaner)183 void generateExceptionCasesInternal(Cleaner cleaner) { 184 generateCases(() -> setupPhantomSubclassException(cleaner, null), 185 1, c -> c.clearRef()); 186 } 187 188 /** 189 * Generate all permutations of the sequence of runnables 190 * and test each one. 191 * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges. 192 * @param generator the supplier of a CleanableCase 193 * @param n the first index to interchange 194 * @param runnables the sequence of actions 195 */ 196 @SuppressWarnings("unchecked") generateCases(Supplier<CleanableCase> generator, int n, Consumer<CleanableCase> ... runnables)197 void generateCases(Supplier<CleanableCase> generator, int n, 198 Consumer<CleanableCase> ... runnables) { 199 if (n == 1) { 200 CleanableCase test = generator.get(); 201 try { 202 verifyGetRef(test); 203 204 // Apply the sequence of actions on the Ref 205 for (Consumer<CleanableCase> c : runnables) { 206 c.accept(test); 207 } 208 verify(test); 209 } catch (Exception e) { 210 Assert.fail(test.toString(), e); 211 } 212 } else { 213 for (int i = 0; i < n - 1; i += 1) { 214 generateCases(generator, n - 1, runnables); 215 Consumer<CleanableCase> t = runnables[n - 1]; 216 int ndx = ((n & 1) == 0) ? i : 0; 217 runnables[n - 1] = runnables[ndx]; 218 runnables[ndx] = t; 219 } 220 generateCases(generator, n - 1, runnables); 221 } 222 } 223 224 /** 225 * Verify the test case. 226 * Any actions directly on the Reference or Cleanable have been executed. 227 * The CleanableCase under test is given a chance to do the cleanup 228 * by forcing a GC. 229 * The result is compared with the expected result computed 230 * from the sequence of operations on the Cleanable. 231 * The Cleanable itself should have been cleanedup. 232 * 233 * @param test A CleanableCase containing the references 234 */ verify(CleanableCase test)235 void verify(CleanableCase test) { 236 System.out.println(test); 237 int r = test.expectedResult(); 238 239 CleanableCase cc = setupPhantom(COMMON, test.getCleanable()); 240 test.clearCleanable(); // release this hard reference 241 242 checkCleaned(test.getSemaphore(), 243 r == CleanableCase.EV_CLEAN, 244 "Cleanable was cleaned"); 245 checkCleaned(cc.getSemaphore(), true, 246 "The reference to the Cleanable was freed"); 247 } 248 249 /** 250 * Verify that the reference.get works (or not) as expected. 251 * It handles the cases where UnsupportedOperationException is expected. 252 * 253 * @param test the CleanableCase 254 */ verifyGetRef(CleanableCase test)255 void verifyGetRef(CleanableCase test) { 256 Reference<?> r = (Reference) test.getCleanable(); 257 try { 258 Object o = r.get(); 259 Reference<?> expectedRef = test.getRef(); 260 Assert.assertEquals(expectedRef.get(), o, 261 "Object reference incorrect"); 262 if (r.getClass().getName().endsWith("CleanableRef")) { 263 Assert.fail("should not be able to get referent"); 264 } 265 } catch (UnsupportedOperationException uoe) { 266 if (r.getClass().getName().endsWith("CleanableRef")) { 267 // Expected exception 268 } else { 269 Assert.fail("Unexpected exception from subclassed cleanable: " + 270 uoe.getMessage() + ", class: " + r.getClass()); 271 } 272 } 273 } 274 275 // BEGIN Android-changed: gc() more emphatically. 276 /** 277 * Test that releasing the reference to the Cleaner service allows it to be 278 * be freed. 279 */ 280 @Test(dataProvider = "cleanerSuppliers") testCleanerTermination(Supplier<Cleaner> supplier)281 public void testCleanerTermination(Supplier<Cleaner> supplier) { 282 ReferenceQueue<Object> queue = new ReferenceQueue<>(); 283 Cleaner service = supplier.get(); 284 285 PhantomReference<Object> ref = new PhantomReference<>(service, queue); 286 gcEmphatically(); 287 // Clear the Reference to the cleaning service and force a gc. 288 service = null; 289 gcEmphatically(); 290 try { 291 Reference<?> r = queue.remove(1000L); 292 Assert.assertNotNull(r, "queue.remove timeout,"); 293 Assert.assertEquals(r, ref, "Wrong Reference dequeued"); 294 } catch (InterruptedException ie) { 295 System.out.printf("queue.remove Interrupted%n"); 296 } 297 } 298 gcEmphatically()299 private static void gcEmphatically() { 300 int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion(); 301 if (targetSdkVersion <= 34) { 302 Runtime.getRuntime().gc(); 303 } else { 304 System.gc(); 305 } 306 } 307 // END Android-changed: gc() more emphatically. 308 309 310 /** 311 * Check a semaphore having been released by cleanup handler. 312 * Force a number of GC cycles to give the GC a chance to process 313 * the Reference and for the cleanup action to be run. 314 * Use a larger number of cycles to wait for an expected cleaning to occur. 315 * 316 * @param semaphore a Semaphore 317 * @param expectCleaned true if cleaning the function should have been run, otherwise not run 318 * @param msg a message describing the cleaning function expected to be run or not run 319 */ checkCleaned(Semaphore semaphore, boolean expectCleaned, String msg)320 static void checkCleaned(Semaphore semaphore, boolean expectCleaned, String msg) { 321 long max_cycles = expectCleaned ? 10 : 3; 322 long cycle = 0; 323 for (; cycle < max_cycles; cycle++) { 324 // Force GC 325 // Android-removed: Remove sun.hotspot.WhiteBox usage 326 // whitebox.fullGC(); 327 gcEmphatically(); 328 329 try { 330 // Android-changed: inline Utils.adjustTimeout call 331 // It is "Math.round(arg * Utils.TIMEOUT_FACTOR), where TIMEOUT_FACTOR defaults to 1.0" 332 // if (semaphore.tryAcquire(Utils.adjustTimeout(200L), TimeUnit.MILLISECONDS)) { 333 if (semaphore.tryAcquire(200L, TimeUnit.MILLISECONDS)) { 334 System.out.printf(" Cleanable cleaned in cycle: %d%n", cycle); 335 if (!expectCleaned) 336 Assert.fail("Should not have been run: " + msg); 337 338 return; 339 } 340 } catch (InterruptedException ie) { 341 // retry in outer loop 342 } 343 } 344 // Object has not been cleaned 345 if (expectCleaned) 346 Assert.fail("Should have been run: " + msg); 347 } 348 349 /** 350 * Create a CleanableCase for a PhantomReference. 351 * @param cleaner the cleaner to use 352 * @param obj an object or null to create a new Object 353 * @return a new CleanableCase preset with the object, cleanup, and semaphore 354 */ setupPhantom(Cleaner cleaner, Object obj)355 static CleanableCase setupPhantom(Cleaner cleaner, Object obj) { 356 if (obj == null) { 357 obj = new Object(); 358 } 359 Semaphore s1 = new Semaphore(0); 360 Cleaner.Cleanable c1 = cleaner.register(obj, () -> s1.release()); 361 362 return new CleanableCase(new PhantomReference<>(obj, null), c1, s1); 363 } 364 365 /** 366 * Create a CleanableCase for a PhantomReference. 367 * @param cleaner the cleaner to use 368 * @param obj an object or null to create a new Object 369 * @return a new CleanableCase preset with the object, cleanup, and semaphore 370 */ setupPhantomSubclass(Cleaner cleaner, Object obj)371 static CleanableCase setupPhantomSubclass(Cleaner cleaner, Object obj) { 372 if (obj == null) { 373 obj = new Object(); 374 } 375 Semaphore s1 = new Semaphore(0); 376 377 Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) { 378 protected void performCleanup() { 379 s1.release(); 380 } 381 }; 382 383 return new CleanableCase(new PhantomReference<>(obj, null), c1, s1); 384 } 385 386 /** 387 * Create a CleanableCase for a PhantomReference. 388 * @param cleaner the cleaner to use 389 * @param obj an object or null to create a new Object 390 * @return a new CleanableCase preset with the object, cleanup, and semaphore 391 */ setupPhantomSubclassException(Cleaner cleaner, Object obj)392 static CleanableCase setupPhantomSubclassException(Cleaner cleaner, Object obj) { 393 if (obj == null) { 394 obj = new Object(); 395 } 396 Semaphore s1 = new Semaphore(0); 397 398 Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) { 399 protected void performCleanup() { 400 s1.release(); 401 throw new RuntimeException("Exception thrown to cleaner thread"); 402 } 403 }; 404 405 return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true); 406 } 407 408 /** 409 * CleanableCase encapsulates the objects used for a test. 410 * The reference to the object is not held directly, 411 * but in a Reference object that can be cleared. 412 * The semaphore is used to count whether the cleanup occurred. 413 * It can be awaited on to determine that the cleanup has occurred. 414 * It can be checked for non-zero to determine if it was 415 * invoked or if it was invoked twice (a bug). 416 */ 417 static class CleanableCase { 418 419 private volatile Reference<?> ref; 420 private volatile Cleaner.Cleanable cleanup; 421 private final Semaphore semaphore; 422 private final boolean throwsEx; 423 private final int[] events; // Sequence of calls to clean, clear, etc. 424 private volatile int eventNdx; 425 426 public static int EV_UNKNOWN = 0; 427 public static int EV_CLEAR = 1; 428 public static int EV_CLEAN = 2; 429 public static int EV_UNREF = 3; 430 public static int EV_CLEAR_CLEANUP = 4; 431 432 CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup, Semaphore semaphore)433 CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup, 434 Semaphore semaphore) { 435 this.ref = ref; 436 this.cleanup = cleanup; 437 this.semaphore = semaphore; 438 this.throwsEx = false; 439 this.events = new int[4]; 440 this.eventNdx = 0; 441 } CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup, Semaphore semaphore, boolean throwsEx)442 CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup, 443 Semaphore semaphore, 444 boolean throwsEx) { 445 this.ref = ref; 446 this.cleanup = cleanup; 447 this.semaphore = semaphore; 448 this.throwsEx = throwsEx; 449 this.events = new int[4]; 450 this.eventNdx = 0; 451 } 452 getRef()453 public Reference<?> getRef() { 454 return ref; 455 } 456 clearRef()457 public void clearRef() { 458 addEvent(EV_UNREF); 459 ref.clear(); 460 } 461 getCleanable()462 public Cleaner.Cleanable getCleanable() { 463 return cleanup; 464 } 465 doClean()466 public void doClean() { 467 try { 468 addEvent(EV_CLEAN); 469 cleanup.clean(); 470 } catch (RuntimeException ex) { 471 if (!throwsEx) { 472 // unless it is known this case throws an exception, rethrow 473 throw ex; 474 } 475 } 476 } 477 doClear()478 public void doClear() { 479 addEvent(EV_CLEAR); 480 ((Reference)cleanup).clear(); 481 } 482 clearCleanable()483 public void clearCleanable() { 484 addEvent(EV_CLEAR_CLEANUP); 485 cleanup = null; 486 } 487 getSemaphore()488 public Semaphore getSemaphore() { 489 return semaphore; 490 } 491 isCleaned()492 public boolean isCleaned() { 493 return semaphore.availablePermits() != 0; 494 } 495 addEvent(int e)496 private synchronized void addEvent(int e) { 497 events[eventNdx++] = e; 498 } 499 500 /** 501 * Computed the expected result from the sequence of events. 502 * If EV_CLEAR appears before anything else, it is cleared. 503 * If EV_CLEAN appears before EV_UNREF, then it is cleaned. 504 * Anything else is Unknown. 505 * @return EV_CLEAR if the cleanup should occur; 506 * EV_CLEAN if the cleanup should occur; 507 * EV_UNKNOWN if it is unknown. 508 */ expectedResult()509 public synchronized int expectedResult() { 510 // Test if EV_CLEAR appears before anything else 511 int clearNdx = indexOfEvent(EV_CLEAR); 512 int cleanNdx = indexOfEvent(EV_CLEAN); 513 int unrefNdx = indexOfEvent(EV_UNREF); 514 if (clearNdx < cleanNdx) { 515 return EV_CLEAR; 516 } 517 if (cleanNdx < clearNdx || cleanNdx < unrefNdx) { 518 return EV_CLEAN; 519 } 520 if (unrefNdx < eventNdx) { 521 return EV_CLEAN; 522 } 523 524 return EV_UNKNOWN; 525 } 526 indexOfEvent(int e)527 private synchronized int indexOfEvent(int e) { 528 for (int i = 0; i < eventNdx; i++) { 529 if (events[i] == e) { 530 return i; 531 } 532 } 533 return eventNdx; 534 } 535 536 private static final String[] names = 537 {"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"}; 538 eventName(int event)539 public String eventName(int event) { 540 return names[event]; 541 } 542 eventsString()543 public synchronized String eventsString() { 544 StringBuilder sb = new StringBuilder(); 545 sb.append('['); 546 for (int i = 0; i < eventNdx; i++) { 547 if (i > 0) { 548 sb.append(", "); 549 } 550 sb.append(eventName(events[i])); 551 } 552 sb.append(']'); 553 sb.append(", throwEx: "); 554 sb.append(throwsEx); 555 return sb.toString(); 556 } 557 toString()558 public String toString() { 559 return String.format("Case: %s, expect: %s, events: %s", 560 getRef().getClass().getName(), 561 eventName(expectedResult()), eventsString()); 562 } 563 } 564 565 /** 566 * Verify that casting a Cleanup to a Reference is not allowed to 567 * get the referent or clear the reference. 568 */ 569 @Test(dataProvider = "cleanerSuppliers") 570 @SuppressWarnings("rawtypes") testReferentNotAvailable(Supplier<Cleaner> supplier)571 public void testReferentNotAvailable(Supplier<Cleaner> supplier) { 572 Cleaner cleaner = supplier.get(); 573 Semaphore s1 = new Semaphore(0); 574 575 Object obj = new String("a new string"); 576 Cleaner.Cleanable c = cleaner.register(obj, () -> s1.release()); 577 Reference r = (Reference) c; 578 try { 579 Object o = r.get(); 580 System.out.printf("r: %s%n", Objects.toString(o)); 581 Assert.fail("should not be able to get the referent from Cleanable"); 582 } catch (UnsupportedOperationException uoe) { 583 // expected 584 } 585 586 try { 587 r.clear(); 588 Assert.fail("should not be able to clear the referent from Cleanable"); 589 } catch (UnsupportedOperationException uoe) { 590 // expected 591 } 592 593 obj = null; 594 checkCleaned(s1, true, "reference was cleaned"); 595 cleaner = null; 596 } 597 598 /** 599 * Test the Cleaner from the CleanerFactory. 600 */ 601 @Test testCleanerFactory()602 public void testCleanerFactory() { 603 Cleaner cleaner = CleanerFactory.cleaner(); 604 605 Object obj = new Object(); 606 CleanableCase s = setupPhantom(cleaner, obj); 607 obj = null; 608 checkCleaned(s.getSemaphore(), true, 609 "Object cleaned using internal CleanerFactory.cleaner()"); 610 } 611 } 612