1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import android.system.SystemCleaner; 18 import dalvik.system.VMRuntime; 19 import java.lang.ref.Cleaner; 20 import java.util.concurrent.CountDownLatch; 21 22 import static java.util.concurrent.TimeUnit.MINUTES; 23 24 /** 25 * Test SystemCleaner with a bad cleaning action. 26 * 27 * This test is inherently very slightly flaky. It assumes that the system will schedule the 28 * finalizer daemon and finalizer watchdog daemon soon and often enough to reach the timeout and 29 * throw the fatal exception before we time out. Since we build in a 20 second buffer, failures 30 * should be very rare. 31 */ 32 public class Main { 33 static volatile Thread throwingThread; main(String[] args)34 public static void main(String[] args) throws Exception { 35 Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 36 @Override 37 public void uncaughtException(Thread t, Throwable e) { 38 if (t != throwingThread) { 39 System.out.println("Exception from wrong thread"); 40 } 41 if (!(e instanceof AssertionError)) { 42 System.out.println("Unexpected uncaught exception"); 43 } 44 System.out.println("Handling uncaught exception"); 45 System.exit(2); 46 } 47 }); 48 49 CountDownLatch cleanerWait1 = new CountDownLatch(1); 50 registerBadCleaner(Cleaner.create(), cleanerWait1); 51 gcRepeatedly(); 52 53 // Now wait for the finalizer to start running. Give it a minute. 54 cleanerWait1.await(1, MINUTES); 55 56 // Give the cleaner Runnable a chance to finish running. That should do nothing, 57 // since exceptions from standard Cleaners are ignored. 58 snooze(10000); 59 60 System.out.println("Good: Survived first exception"); 61 // Now repeat with SystemCleaner. 62 CountDownLatch cleanerWait2 = new CountDownLatch(1); 63 registerBadCleaner(SystemCleaner.cleaner(), cleanerWait2); 64 gcRepeatedly(); 65 cleanerWait2.await(1, MINUTES); 66 67 // Now fall asleep. The timeout is large enough that we expect the 68 // FinalizerDaemon to have been killed by the exception. 69 // The timeout is also large enough to cover the extra 5 seconds we wait 70 // to dump on termination, plus potentially substantial gcstress overhead. 71 // Note: the timeout is here (instead of an infinite sleep) to protect the test 72 // environment (e.g., in case this is run without a timeout wrapper). 73 snooze(20_000); 74 75 // We should not get here. 76 System.out.println("Bad: Unexpectedly survived second exception"); 77 System.exit(0); 78 } 79 gcRepeatedly()80 private static void gcRepeatedly() { 81 // Should have at least two iterations to trigger finalization, but just to make sure run 82 // some more. 83 for (int i = 0; i < 5; i++) { 84 Runtime.getRuntime().gc(); 85 } 86 } 87 registerBadCleaner(Cleaner cleaner, CountDownLatch cleanerWait)88 private static void registerBadCleaner(Cleaner cleaner, CountDownLatch cleanerWait) { 89 Object obj = new Object(); 90 cleaner.register(obj, () -> throwingCleanup(cleanerWait)); 91 System.out.println("About to null reference."); 92 obj = null; // Not that this would make a difference, could be eliminated earlier. 93 } 94 snooze(int ms)95 public static void snooze(int ms) { 96 try { 97 Thread.sleep(ms); 98 } catch (InterruptedException ie) { 99 System.out.println("Unexpected interrupt"); 100 } 101 } 102 throwingCleanup(CountDownLatch cleanerWait)103 private static void throwingCleanup(CountDownLatch cleanerWait) { 104 throwingThread = Thread.currentThread(); 105 cleanerWait.countDown(); 106 System.out.println("Cleaner started and sleeping briefly..."); 107 snooze(100); 108 throw new AssertionError("Process-killing exception"); 109 } 110 } 111