1 /*
   2  * Copyright (c) 2015, 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 
  24 import java.lang.ref.Cleaner;
  25 import java.lang.ref.Reference;
  26 import java.lang.ref.PhantomReference;
  27 import java.lang.ref.ReferenceQueue;
  28 import java.lang.ref.SoftReference;
  29 import java.lang.ref.WeakReference;
  30 import java.util.Vector;
  31 import java.util.concurrent.Semaphore;
  32 import java.util.function.Consumer;
  33 import java.util.function.Supplier;
  34 
  35 import org.testng.Assert;
  36 import org.testng.TestNG;
  37 import org.testng.annotations.Test;
  38 
  39 /*
  40  * @test
  41  * @library /lib/testlibrary
  42  * @run testng/othervm -Xmx16m CleanerTest
  43  */
  44 
  45 @Test
  46 public class CleanerTest {
  47     // A common CleaningService used by the test for notifications
  48     static final Cleaner COMMON = Cleaner.create();
  49 
  50     /**
  51      * Test that sequences of the various actions on a Reference
  52      * and on the Cleanable instance have the desired result.
  53      * The test cases are generated for each of phantom, weak and soft
  54      * references.
  55      * The sequence of actions includes all permutations to an initial
  56      * list of actions including clearing the ref and resulting garbage
  57      * collection actions on the reference, explicitly performing
  58      * the cleanup and explicitly clearing the cleaning function.
  59      */
  60     @Test
  61     @SuppressWarnings("unchecked")
  62     void test1() {
  63         Cleaner cleaner = Cleaner.create();
  64 
  65         // Individually
  66         generateCases(cleaner, c -> c.clearRef());
  67         generateCases(cleaner, c -> c.doClean());
  68         generateCases(cleaner, c -> c.doClear());
  69 
  70         // Pairs
  71         generateCases(cleaner, c -> c.doClear(), c -> c.doClean());
  72 
  73         // Triplets
  74         generateCases(cleaner, c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
  75 
  76         CleanableCase s = setupPhantom(COMMON, cleaner);
  77         cleaner = null;
  78         Assert.assertTrue(checkCleaned(s.getSemaphore()), "Cleaner cleanup should have occurred");
  79 
  80     }
  81 
  82     /**
  83      * Generate tests using the runnables for each of phantom, weak,
  84      * and soft references.
  85      * @param cleaner  the cleaner
  86      * @param runnables the sequence of actions on the test case
  87      */
  88     @SuppressWarnings("unchecked")
  89     void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
  90         generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables);
  91         generateCases(() -> setupWeak(cleaner, null), runnables.length, runnables);
  92         generateCases(() -> setupSoft(cleaner, null), runnables.length, runnables);
  93     }
  94 
  95     /**
  96      * Generate all permutations of the sequence of runnables
  97      * and test each one.
  98      * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges.
  99      * @param generator the supplier of a CleanableCase
 100      * @param n the first index to interchange
 101      * @param runnables the sequence of actions
 102      */
 103     @SuppressWarnings("unchecked")
 104     void generateCases(Supplier<CleanableCase> generator, int n, Consumer<CleanableCase> ... runnables) {
 105         System.out.printf("Heap n: %d%n", n);
 106         if (n == 1) {
 107             CleanableCase test = generator.get();
 108             // Apply the sequence of actions on the Ref
 109             for (Consumer<CleanableCase> c : runnables) {
 110                 c.accept(test);
 111             }
 112             verify(test);
 113         } else {
 114             for (int i = 0; i < n - 1; i += 1) {
 115                 generateCases(generator, n - 1, runnables);
 116                 Consumer<CleanableCase> t = runnables[n - 1];
 117                 int ndx = ((n & 1) == 0) ? i : 0;
 118                 runnables[n - 1] = runnables[ndx];
 119                 runnables[ndx] = t;
 120             }
 121             generateCases(generator, n - 1, runnables);
 122         }
 123     }
 124 
 125     /**
 126      * Verify the test case.
 127      * Any actions directly on the Reference or Cleanable have been executed.
 128      * The CleanableCase under test is given a chance to do the cleanup
 129      * by forcing a GC.
 130      * The result is compared with the expecte result computed
 131      * from the sequence of operations on the Cleanable.
 132      * The Cleanable itself should have been cleanedup.
 133      *
 134      * @param test A CleanableCase containing the references
 135      */
 136     void verify(CleanableCase test) {
 137         int r = test.expectedResult();
 138         System.out.printf("Case: %s, expect: %s, events: %s%n",
 139                 test.getRef().getClass().getName(), test.eventName(r), test.eventsString());
 140 
 141         CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
 142         test.clearCleanable();        // release this hard reference
 143 
 144         boolean result = checkCleaned(test.getSemaphore());
 145         if (result) {
 146             Assert.assertEquals(r, CleanableCase.EV_CLEAN, "expected cleaning to be done");
 147         } else {
 148             Assert.assertNotEquals(r, CleanableCase.EV_CLEAN, "unexpected cleaning");
 149         }
 150         Assert.assertTrue(checkCleaned(cc.getSemaphore()), "The reference to the Cleaner should have been freed");
 151     }
 152 
 153     /**
 154      * Test that releasing the reference to the Cleaner service allows it to be
 155      * be freed.
 156      */
 157     @Test
 158     void test2() {
 159         ReferenceQueue<Object> queue = new ReferenceQueue<>();
 160         Cleaner service = Cleaner.create();
 161 
 162         PhantomReference<Object> ref = new PhantomReference<>(service, queue);
 163         System.gc();
 164         // Clear the Reference to the cleaning service and force a gc.
 165         service = null;
 166         System.gc();
 167         try {
 168             Reference<?> r = queue.remove();
 169             Assert.assertEquals(r, ref, "Wrong Reference dequeued");
 170         } catch (InterruptedException ie) {
 171             System.out.printf("queue.remove Interrupted%n");
 172         }
 173 
 174     }
 175 
 176     /**
 177      * Check a set of semaphores having been released by cleanup handlers.
 178      * Force a number of GC cycles to give the GC a chance to process
 179      * all the References and for the cleanup functions to be run.
 180      *
 181      * @param semaphore a varargs list of Semaphores
 182      * @return true if all of the semaphores have at least 1 permit,
 183      *      false otherwise.
 184      */
 185     static boolean checkCleaned(Semaphore... semaphore) {
 186         long[] cycles = new long[semaphore.length];
 187         long total = 0;
 188         for (int cycle = 0; cycle < 20; cycle++) {
 189             for (int i = 0; i < semaphore.length; i++) {
 190                 long count = semaphore[i].availablePermits();
 191                 if (count > 0 && cycles[i] == 0) {
 192                     System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycle);
 193                     cycles[i] = cycle;
 194                     total += 1;
 195                 }
 196             }
 197 
 198             if (total == semaphore.length) {
 199                 System.out.printf(" All cleanups done in cycle: %d, total: %d%n", cycle, total);
 200                 for (int i = 0; i < semaphore.length; i++) {
 201                     long count = semaphore[i].availablePermits();
 202                     Assert.assertEquals(count, 1, "Cleanable invoked more than once, semaphore " + i);
 203                 }
 204                 return true;          // all references freed
 205             }
 206             // Force GC
 207             memoryPressure();
 208             System.gc();
 209         }
 210         // Not all objects have been cleaned
 211 
 212         for (int i = 0; i < semaphore.length; i++) {
 213             if (cycles[i] != 0) {
 214                 System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycles[i]);
 215             } else {
 216                 System.out.printf(" Cleanable[%d] not cleaned%n", i);
 217             }
 218         }
 219 
 220         return false;   // Failing result
 221     }
 222 
 223     /**
 224      * Create a CleanableCase for a PhantomReference.
 225      * @param cleaner the cleaner to use
 226      * @param obj an object or null to create a new Object
 227      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 228      */
 229     static CleanableCase setupPhantom(Cleaner cleaner, Object obj) {
 230         if (obj == null) {
 231             obj = new Object();
 232         }
 233         Semaphore s1 = new Semaphore(0);
 234         Cleaner.Cleanable c1 = cleaner.phantomCleanable(obj, () -> s1.release());
 235 
 236         return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
 237     }
 238 
 239     /**
 240      * Create a CleanableCase for a WeakReference.
 241      * @param cleaner the cleaner to use
 242      * @param obj an object or null to create a new Object
 243      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 244      */
 245     static CleanableCase setupWeak(Cleaner cleaner, Object obj) {
 246         if (obj == null) {
 247             obj = new Object();
 248         }
 249         Semaphore s1 = new Semaphore(0);
 250         Cleaner.Cleanable c1 = cleaner.weakCleanable(obj, () -> s1.release());
 251 
 252         return new CleanableCase(new WeakReference<>(obj, null), c1, s1);
 253     }
 254 
 255     /**
 256      * Create a CleanableCase for a SoftReference.
 257      * @param cleaner the cleaner to use
 258      * @param obj an object or null to create a new Object
 259      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 260      */
 261     static CleanableCase setupSoft(Cleaner cleaner, Object obj) {
 262         if (obj == null) {
 263             obj = new Object();
 264         }
 265         Semaphore s1 = new Semaphore(0);
 266         Cleaner.Cleanable c1 = cleaner.softCleanable(obj, () -> s1.release());
 267 
 268         return new CleanableCase(new SoftReference<>(obj, null), c1, s1);
 269     }
 270 
 271     /**
 272      * MemoryPressure allocates memory to force a gc and to clear SoftReferences.
 273      */
 274     static void memoryPressure() {
 275         Vector<Object> root = new Vector<>();
 276         try {
 277             long free = 0;
 278             while ((free = Runtime.getRuntime().freeMemory()) > 1_000_000) {
 279                 long[] extra = new long[1_000_000];
 280                 root.addElement(extra);
 281             }
 282         } catch (OutOfMemoryError mem) {
 283             // ignore
 284             root = null;
 285         }
 286     }
 287 
 288     /**
 289      * CleanableCase encapsulates the objects used for a test.
 290      * The reference to the object is not held directly,
 291      * but in a Reference object that can be cleared.
 292      * The semaphore is used to count whether the cleanup occurred.
 293      * It can be awaited on to determine that the cleanup has occurred.
 294      * It can be checked for non-zero to determine if it was
 295      * invoked or if itwas invoked twice (a bug).
 296      */
 297     static class CleanableCase {
 298 
 299         private Reference<Object> ref;
 300         private Cleaner.Cleanable cleanup;
 301         private Semaphore semaphore;
 302         private int[] events;   // Sequence of calls to clean, clear, etc.
 303         private int eventNdx;
 304         public static int EV_UNKNOWN = 0;
 305         public static int EV_CLEAR = 1;
 306         public static int EV_CLEAN = 2;
 307         public static int EV_UNREF = 3;
 308         public static int EV_CLEAR_CLEANUP = 4;
 309 
 310 
 311         CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup, Semaphore semaphore) {
 312             this.ref = ref;
 313             this.cleanup = cleanup;
 314             this.semaphore = semaphore;
 315             this.events = new int[4];
 316             this.eventNdx = 0;
 317         }
 318 
 319         public Object getRef() {
 320             return ref;
 321         }
 322 
 323         public void clearRef() {
 324             addEvent(EV_UNREF);
 325             ref.clear();
 326         }
 327 
 328         public Cleaner.Cleanable getCleanable() {
 329             return cleanup;
 330         }
 331 
 332         public void doClean() {
 333             addEvent(EV_CLEAN);
 334             cleanup.clean();
 335         }
 336 
 337         public void doClear() {
 338             addEvent(EV_CLEAR);
 339             cleanup.clear();
 340         }
 341 
 342         public void clearCleanable() {
 343             addEvent(EV_CLEAR_CLEANUP);
 344             cleanup = null;
 345         }
 346 
 347         public Semaphore getSemaphore() {
 348             return semaphore;
 349         }
 350 
 351         public boolean isCleaned() {
 352             return semaphore.availablePermits() != 0;
 353         }
 354 
 355         private synchronized void addEvent(int e) {
 356             events[eventNdx++] = e;
 357         }
 358 
 359         /**
 360          * Computed the expected result from the sequence of events.
 361          * If EV_CLEAR appears before anything else, it is cleared.
 362          * If EV_CLEAN appears before EV_UNREF, then it is cleaned.
 363          * Anything else is Unknown.
 364          * @return EV_CLEAR if the cleanup should occur;
 365          *         EV_CLEAN if the cleanup should occur;
 366          *         EV_UNKNOWN if it is unknown.
 367          */
 368         public synchronized int expectedResult() {
 369             // Test if EV_CLEAR appears before anything else
 370             int clearNdx = indexOfEvent(EV_CLEAR);
 371             int cleanNdx = indexOfEvent(EV_CLEAN);
 372             int unrefNdx = indexOfEvent(EV_UNREF);
 373             if (clearNdx < cleanNdx) {
 374                 return EV_CLEAR;
 375             }
 376             if (cleanNdx < clearNdx || cleanNdx < unrefNdx) {
 377                 return EV_CLEAN;
 378             }
 379             if (unrefNdx < eventNdx) {
 380                 return EV_CLEAN;
 381             }
 382 
 383             return EV_UNKNOWN;
 384         }
 385 
 386         private synchronized  int indexOfEvent(int e) {
 387             for (int i = 0; i < eventNdx; i++) {
 388                 if (events[i] == e) {
 389                     return i;
 390                 }
 391             }
 392             return eventNdx;
 393         }
 394 
 395         private static final String[] names =
 396                 {"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"};
 397 
 398         public String eventName(int event) {
 399             return names[event];
 400         }
 401 
 402         public synchronized String eventsString() {
 403             StringBuilder sb = new StringBuilder();
 404             for (int i = 0; i < eventNdx; i++) {
 405                 sb.append(eventName(events[i]));
 406                 sb.append(' ');
 407             }
 408             return sb.toString();
 409         }
 410     }
 411 
 412 }