Add 2040-huge-native-alloc test

Check that if we allocate more than a gigabyte of native memory and then
repeatedly notify the collector of native allocation, we temporarily
block the allocation for a reasonable amount of time to allow the GC to
catch up.

We previously had some coverage of this, but lost it when BigInteger
moved away from using native allocation.

Test: Ran locally on host; TreeHugger.
Bug: 186592536
Change-Id: Id7d120e00630e26bcdf396a80069c9cca7c00804
diff --git a/test/2040-huge-native-alloc/Android.bp b/test/2040-huge-native-alloc/Android.bp
new file mode 100644
index 0000000..8a5501d
--- /dev/null
+++ b/test/2040-huge-native-alloc/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2040-huge-native-alloc`.
+
+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-2040-huge-native-alloc",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2040-huge-native-alloc-expected-stdout",
+        ":art-run-test-2040-huge-native-alloc-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2040-huge-native-alloc-expected-stdout",
+    out: ["art-run-test-2040-huge-native-alloc-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2040-huge-native-alloc-expected-stderr",
+    out: ["art-run-test-2040-huge-native-alloc-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2040-huge-native-alloc/expected-stderr.txt b/test/2040-huge-native-alloc/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2040-huge-native-alloc/expected-stderr.txt
diff --git a/test/2040-huge-native-alloc/expected-stdout.txt b/test/2040-huge-native-alloc/expected-stdout.txt
new file mode 100644
index 0000000..f2fc51c
--- /dev/null
+++ b/test/2040-huge-native-alloc/expected-stdout.txt
@@ -0,0 +1,3 @@
+JNI_OnLoad called
+Main Started
+Main Finished
diff --git a/test/2040-huge-native-alloc/huge_native_buf.cc b/test/2040-huge-native-alloc/huge_native_buf.cc
new file mode 100644
index 0000000..06186c9
--- /dev/null
+++ b/test/2040-huge-native-alloc/huge_native_buf.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "base/utils.h"
+#include "jni.h"
+#include <stddef.h>
+
+namespace art {
+
+static constexpr size_t HUGE_SIZE = 10'000'000;
+
+extern "C" JNIEXPORT jobject JNICALL Java_Main_getHugeNativeBuffer(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  char* buffer = new char[HUGE_SIZE];
+  return env->NewDirectByteBuffer(buffer, HUGE_SIZE);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_deleteHugeNativeBuffer(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject jbuffer) {
+  delete [] static_cast<char*>(env->GetDirectBufferAddress(jbuffer));
+}
+
+}  // namespace art
+
+
diff --git a/test/2040-huge-native-alloc/info.txt b/test/2040-huge-native-alloc/info.txt
new file mode 100644
index 0000000..41c5ef6
--- /dev/null
+++ b/test/2040-huge-native-alloc/info.txt
@@ -0,0 +1,2 @@
+Check that we properly trigger world stop collections after a lot of native
+allocation.
diff --git a/test/2040-huge-native-alloc/src/Main.java b/test/2040-huge-native-alloc/src/Main.java
new file mode 100644
index 0000000..b865347
--- /dev/null
+++ b/test/2040-huge-native-alloc/src/Main.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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 dalvik.system.VMRuntime;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+
+public class Main {
+
+  static final int HOW_MANY_HUGE = 110;  // > 1GB to trigger blocking in default config.
+  int allocated = 0;
+  int deallocated = 0;
+  static Object lock = new Object();
+  WeakReference<BufferHolder>[] references = new WeakReference[HOW_MANY_HUGE];
+
+  class BufferHolder {
+    private ByteBuffer buffer;
+    BufferHolder() {
+      ++allocated;
+      buffer = getHugeNativeBuffer();
+    }
+    protected void finalize() {
+      synchronized(lock) {
+        ++deallocated;
+      }
+      deleteHugeNativeBuffer(buffer);
+      buffer = null;
+    }
+  }
+
+  // Repeatedly inform the GC of native allocations. Return the time (in nsecs) this takes.
+  private static long timeNotifications() {
+    VMRuntime vmr = VMRuntime.getRuntime();
+    long startNanos = System.nanoTime();
+    for (int i = 0; i < 200; ++i) {
+      vmr.notifyNativeAllocation();
+    }
+    return System.nanoTime() - startNanos;
+  }
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+    System.out.println("Main Started");
+    new Main().run();
+    System.out.println("Main Finished");
+  }
+
+  void run() {
+    timeNotifications();  // warm up.
+    long referenceTime1 = timeNotifications();
+    long referenceTime2 = timeNotifications();
+    long referenceTime = Math.min(referenceTime1, referenceTime2);
+
+    // Allocate half a GB of native memory without informing the GC.
+    for (int i = 0; i < HOW_MANY_HUGE; ++i) {
+      new BufferHolder();
+    }
+
+    // One of the notifications should block for GC to catch up.
+    long actualTime = timeNotifications();
+
+    if (actualTime > 500_000_000) {
+      System.out.println("Notifications ran too slowly; excessive blocking? msec = "
+          + (actualTime / 1_000_000));
+    } else if (actualTime < 3 * referenceTime + 2_000_000) {
+      System.out.println("Notifications ran too quickly; no blocking GC? msec = "
+          + (actualTime / 1_000_000));
+    }
+
+    // Let finalizers run.
+    try {
+      Thread.sleep(3000);
+    } catch (InterruptedException e) {
+      System.out.println("Unexpected interrupt");
+    }
+
+    if (deallocated > allocated || deallocated < allocated - 5 /* slop for register references */) {
+      System.out.println("Unexpected number of deallocated objects:");
+      System.out.println("Allocated = " + allocated + " deallocated = " + deallocated);
+    }
+  }
+
+  private static native ByteBuffer getHugeNativeBuffer();
+  private static native void deleteHugeNativeBuffer(ByteBuffer buf);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 1a14eff..e375ad9 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -726,6 +726,7 @@
         "2033-shutdown-mechanics/native_shutdown.cc",
         "2036-jni-filechannel/jni_filechannel.cc",
         "2037-thread-name-inherit/thread_name_inherit.cc",
+        "2040-huge-native-alloc/huge_native_buf.cc",
         "common/runtime_state.cc",
         "common/stack_inspect.cc",
     ],
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 6034b94..b2480eb 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1190,7 +1190,8 @@
                   "2033-shutdown-mechanics",
                   "2035-structural-native-method",
                   "2036-structural-subclass-shadow",
-                  "2038-hiddenapi-jvmti-ext"],
+                  "2038-hiddenapi-jvmti-ext",
+                  "2040-huge-native-alloc"],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]
     },