• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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