Add bad cleaner action registered in the SystemCleaner test.

Ensures that finalizer watchdog aborts the VM when a cleaner
action takes too much time.

Bug: 242311818
Test: art/test.py -b --host -r -t 2261
Change-Id: I1fe2c40ccccf4a11da69de886a41273ae84eac17
diff --git a/test/2261-badcleaner-in-systemcleaner/Android.bp b/test/2261-badcleaner-in-systemcleaner/Android.bp
new file mode 100644
index 0000000..4643c77
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2261-badcleaner-in-systemcleaner`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2261-badcleaner-in-systemcleaner",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src-art/**/*.java"],
+    data: [
+        ":art-run-test-2261-badcleaner-in-systemcleaner-expected-stdout",
+        ":art-run-test-2261-badcleaner-in-systemcleaner-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2261-badcleaner-in-systemcleaner-expected-stdout",
+    out: ["art-run-test-2261-badcleaner-in-systemcleaner-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2261-badcleaner-in-systemcleaner-expected-stderr",
+    out: ["art-run-test-2261-badcleaner-in-systemcleaner-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2261-badcleaner-in-systemcleaner/expected-stderr.txt b/test/2261-badcleaner-in-systemcleaner/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/expected-stderr.txt
diff --git a/test/2261-badcleaner-in-systemcleaner/expected-stdout.txt b/test/2261-badcleaner-in-systemcleaner/expected-stdout.txt
new file mode 100644
index 0000000..db36097
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/expected-stdout.txt
@@ -0,0 +1,4 @@
+About to null reference.
+Cleaner started and sleeping briefly...
+Cleaner done snoozing.
+Cleaner sleeping forever now.
diff --git a/test/2261-badcleaner-in-systemcleaner/info.txt b/test/2261-badcleaner-in-systemcleaner/info.txt
new file mode 100644
index 0000000..9916ff1
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/info.txt
@@ -0,0 +1,3 @@
+Cleanup actions registered at android.system.SystemCleaner are run within FinalizerDaemon thread
+and the same time out rules are applied. Essentially this is SystemCleaner version of
+030-bad-finalizer.
diff --git a/test/2261-badcleaner-in-systemcleaner/run.py b/test/2261-badcleaner-in-systemcleaner/run.py
new file mode 100644
index 0000000..3151272
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f", expected_exit_code=2)
diff --git a/test/2261-badcleaner-in-systemcleaner/src-art/Main.java b/test/2261-badcleaner-in-systemcleaner/src-art/Main.java
new file mode 100644
index 0000000..a14d0d5
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/src-art/Main.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.system.SystemCleaner;
+import dalvik.system.VMRuntime;
+import java.util.concurrent.CountDownLatch;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+/**
+ * Test SystemCleaner with a bad cleaning action.
+ *
+ * This test is inherently very slightly flaky. It assumes that the system will schedule the
+ * finalizer daemon and finalizer watchdog daemon soon and often enough to reach the timeout and
+ * throw the fatal exception before we time out. Since we build in a 100 second buffer, failures
+ * should be very rare.
+ */
+public class Main {
+    public static void main(String[] args) throws Exception {
+        CountDownLatch cleanerWait = new CountDownLatch(1);
+
+        registerBadCleaner(cleanerWait);
+
+        // Should have at least two iterations to trigger finalization, but just to make sure run
+        // some more.
+        for (int i = 0; i < 5; i++) {
+            Runtime.getRuntime().gc();
+        }
+
+        // Now wait for the finalizer to start running. Give it a minute.
+        cleanerWait.await(1, MINUTES);
+
+        // Now fall asleep with a timeout. The timeout is large enough that we expect the
+        // finalizer daemon to have killed the process before the deadline elapses.
+        // The timeout is also large enough to cover the extra 5 seconds we wait
+        // to dump threads, plus potentially substantial gcstress overhead.
+        // Note: the timeout is here (instead of an infinite sleep) to protect the test
+        //       environment (e.g., in case this is run without a timeout wrapper).
+        final long timeout = 100 * 1000 + VMRuntime.getRuntime().getFinalizerTimeoutMs();
+        long remainingWait = timeout;
+        final long waitStart = System.currentTimeMillis();
+        while (remainingWait > 0) {
+            synchronized (args) {  // Just use an already existing object for simplicity...
+                try {
+                    args.wait(remainingWait);
+                } catch (Exception e) {
+                    System.out.println("UNEXPECTED EXCEPTION");
+                }
+            }
+            remainingWait = timeout - (System.currentTimeMillis() - waitStart);
+        }
+
+        // We should not get here.
+        System.out.println("UNREACHABLE");
+        System.exit(0);
+    }
+
+    private static void registerBadCleaner(CountDownLatch cleanerWait) {
+        Object obj = new Object();
+        SystemCleaner.cleaner().register(obj, () -> neverEndingCleanup(cleanerWait));
+
+        System.out.println("About to null reference.");
+        obj = null;  // Not that this would make a difference, could be eliminated earlier.
+    }
+
+    public static void snooze(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException ie) {
+            System.out.println("Unexpected interrupt");
+        }
+    }
+
+    private static void neverEndingCleanup(CountDownLatch cleanerWait) {
+        cleanerWait.countDown();
+
+        System.out.println("Cleaner started and sleeping briefly...");
+
+        long start, end;
+        start = System.nanoTime();
+        snooze(2000);
+        end = System.nanoTime();
+        System.out.println("Cleaner done snoozing.");
+
+        System.out.println("Cleaner sleeping forever now.");
+        while (true) {
+            snooze(10000);
+        }
+    }
+}
diff --git a/test/2261-badcleaner-in-systemcleaner/test-metadata.json b/test/2261-badcleaner-in-systemcleaner/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index cb9c890..5a90e51 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1219,7 +1219,8 @@
                   "2238-checker-polymorphic-recursive-inlining",
                   "2240-tracing-non-invokable-method",
                   "2246-trace-stream",
-                  "2254-class-value-before-and-after-u"],
+                  "2254-class-value-before-and-after-u",
+                  "2261-badcleaner-in-systemcleaner"],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]
     },