Use runFinalizationWithTimeout for native allocations

Prevents deadlocks by not waiting longer than 250ms for finalizers
to complete.

(cherry picked from commit 3b532d744034b43ed329a3198f15846d80fec3f0)

Bug: 21544853
Change-Id: I57b2f7ae8b74185922eb3c15ba0ab71a4d2348aa
diff --git a/runtime/base/time_utils.h b/runtime/base/time_utils.h
index f58c22a..55d2764 100644
--- a/runtime/base/time_utils.h
+++ b/runtime/base/time_utils.h
@@ -68,8 +68,8 @@
 }
 
 // Converts the given number of milliseconds to nanoseconds
-static constexpr inline uint64_t MsToNs(uint64_t ns) {
-  return ns * 1000 * 1000;
+static constexpr inline uint64_t MsToNs(uint64_t ms) {
+  return ms * 1000 * 1000;
 }
 
 #if defined(__APPLE__)
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index d344d81..aeab7d8 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -110,6 +110,9 @@
     sizeof(mirror::HeapReference<mirror::Object>);
 static constexpr size_t kDefaultAllocationStackSize = 8 * MB /
     sizeof(mirror::HeapReference<mirror::Object>);
+// System.runFinalization can deadlock with native allocations, to deal with this, we have a
+// timeout on how long we wait for finalizers to run. b/21544853
+static constexpr uint64_t kNativeAllocationFinalizeTimeout = MsToNs(250u);
 
 Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max_free,
            double target_utilization, double foreground_heap_growth_multiplier,
@@ -3538,22 +3541,16 @@
   return concurrent_gc_pending_.LoadRelaxed();
 }
 
-void Heap::RunFinalization(JNIEnv* env) {
-  // Can't do this in WellKnownClasses::Init since System is not properly set up at that point.
-  if (WellKnownClasses::java_lang_System_runFinalization == nullptr) {
-    CHECK(WellKnownClasses::java_lang_System != nullptr);
-    WellKnownClasses::java_lang_System_runFinalization =
-        CacheMethod(env, WellKnownClasses::java_lang_System, true, "runFinalization", "()V");
-    CHECK(WellKnownClasses::java_lang_System_runFinalization != nullptr);
-  }
-  env->CallStaticVoidMethod(WellKnownClasses::java_lang_System,
-                            WellKnownClasses::java_lang_System_runFinalization);
+void Heap::RunFinalization(JNIEnv* env, uint64_t timeout) {
+  env->CallStaticVoidMethod(WellKnownClasses::dalvik_system_VMRuntime,
+                            WellKnownClasses::dalvik_system_VMRuntime_runFinalization,
+                            static_cast<jlong>(timeout));
 }
 
 void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) {
   Thread* self = ThreadForEnv(env);
   if (native_need_to_run_finalization_) {
-    RunFinalization(env);
+    RunFinalization(env, kNativeAllocationFinalizeTimeout);
     UpdateMaxNativeFootprint();
     native_need_to_run_finalization_ = false;
   }
@@ -3569,7 +3566,7 @@
     if (new_native_bytes_allocated > growth_limit_) {
       if (WaitForGcToComplete(kGcCauseForNativeAlloc, self) != collector::kGcTypeNone) {
         // Just finished a GC, attempt to run finalizers.
-        RunFinalization(env);
+        RunFinalization(env, kNativeAllocationFinalizeTimeout);
         CHECK(!env->ExceptionCheck());
         // Native bytes allocated may be updated by finalization, refresh it.
         new_native_bytes_allocated = native_bytes_allocated_.LoadRelaxed();
@@ -3577,7 +3574,7 @@
       // If we still are over the watermark, attempt a GC for alloc and run finalizers.
       if (new_native_bytes_allocated > growth_limit_) {
         CollectGarbageInternal(gc_type, kGcCauseForNativeAlloc, false);
-        RunFinalization(env);
+        RunFinalization(env, kNativeAllocationFinalizeTimeout);
         native_need_to_run_finalization_ = false;
         CHECK(!env->ExceptionCheck());
       }
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index c72414a..81a9741 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -776,8 +776,8 @@
   bool IsValidContinuousSpaceObjectAddress(const mirror::Object* obj) const
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
-  // Run the finalizers.
-  void RunFinalization(JNIEnv* env);
+  // Run the finalizers. If timeout is non zero, then we use the VMRuntime version.
+  void RunFinalization(JNIEnv* env, uint64_t timeout);
 
   // Blocks the caller until the garbage collector becomes idle and returns the type of GC we
   // waited for.
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 3dbfe1b..e7857a0 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -34,6 +34,7 @@
 jclass WellKnownClasses::dalvik_system_DexPathList;
 jclass WellKnownClasses::dalvik_system_DexPathList__Element;
 jclass WellKnownClasses::dalvik_system_PathClassLoader;
+jclass WellKnownClasses::dalvik_system_VMRuntime;
 jclass WellKnownClasses::java_lang_BootClassLoader;
 jclass WellKnownClasses::java_lang_ClassLoader;
 jclass WellKnownClasses::java_lang_ClassNotFoundException;
@@ -63,6 +64,7 @@
 jclass WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer;
 
 jmethodID WellKnownClasses::com_android_dex_Dex_create;
+jmethodID WellKnownClasses::dalvik_system_VMRuntime_runFinalization;
 jmethodID WellKnownClasses::java_lang_Boolean_valueOf;
 jmethodID WellKnownClasses::java_lang_Byte_valueOf;
 jmethodID WellKnownClasses::java_lang_Character_valueOf;
@@ -209,6 +211,8 @@
   dalvik_system_DexPathList = CacheClass(env, "dalvik/system/DexPathList");
   dalvik_system_DexPathList__Element = CacheClass(env, "dalvik/system/DexPathList$Element");
   dalvik_system_PathClassLoader = CacheClass(env, "dalvik/system/PathClassLoader");
+  dalvik_system_VMRuntime = CacheClass(env, "dalvik/system/VMRuntime");
+
   java_lang_BootClassLoader = CacheClass(env, "java/lang/BootClassLoader");
   java_lang_ClassLoader = CacheClass(env, "java/lang/ClassLoader");
   java_lang_ClassNotFoundException = CacheClass(env, "java/lang/ClassNotFoundException");
@@ -238,6 +242,7 @@
   org_apache_harmony_dalvik_ddmc_Chunk = CacheClass(env, "org/apache/harmony/dalvik/ddmc/Chunk");
   org_apache_harmony_dalvik_ddmc_DdmServer = CacheClass(env, "org/apache/harmony/dalvik/ddmc/DdmServer");
 
+  dalvik_system_VMRuntime_runFinalization = CacheMethod(env, dalvik_system_VMRuntime, true, "runFinalization", "(J)V");
   com_android_dex_Dex_create = CacheMethod(env, com_android_dex_Dex, true, "create", "(Ljava/nio/ByteBuffer;)Lcom/android/dex/Dex;");
   java_lang_ClassNotFoundException_init = CacheMethod(env, java_lang_ClassNotFoundException, false, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
   java_lang_ClassLoader_loadClass = CacheMethod(env, java_lang_ClassLoader, false, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index d25d1c3..66b9abe 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -45,6 +45,7 @@
   static jclass dalvik_system_DexPathList;
   static jclass dalvik_system_DexPathList__Element;
   static jclass dalvik_system_PathClassLoader;
+  static jclass dalvik_system_VMRuntime;
   static jclass java_lang_BootClassLoader;
   static jclass java_lang_ClassLoader;
   static jclass java_lang_ClassNotFoundException;
@@ -74,6 +75,7 @@
   static jclass org_apache_harmony_dalvik_ddmc_DdmServer;
 
   static jmethodID com_android_dex_Dex_create;
+  static jmethodID dalvik_system_VMRuntime_runFinalization;
   static jmethodID java_lang_Boolean_valueOf;
   static jmethodID java_lang_Byte_valueOf;
   static jmethodID java_lang_Character_valueOf;
diff --git a/test/004-NativeAllocations/src/Main.java b/test/004-NativeAllocations/src/Main.java
index a99fe92..92f4e21 100644
--- a/test/004-NativeAllocations/src/Main.java
+++ b/test/004-NativeAllocations/src/Main.java
@@ -19,6 +19,8 @@
 
 public class Main {
     static Object nativeLock = new Object();
+    static Object deadlockLock = new Object();
+    static boolean aboutToDeadlockLock = false;
     static int nativeBytes = 0;
     static Object runtime;
     static Method register_native_allocation;
@@ -28,13 +30,15 @@
     static class NativeAllocation {
         private int bytes;
 
-        NativeAllocation(int bytes) throws Exception {
+        NativeAllocation(int bytes, boolean testingDeadlock) throws Exception {
             this.bytes = bytes;
             register_native_allocation.invoke(runtime, bytes);
             synchronized (nativeLock) {
-                nativeBytes += bytes;
-                if (nativeBytes > maxMem) {
-                    throw new OutOfMemoryError();
+                if (!testingDeadlock) {
+                    nativeBytes += bytes;
+                    if (nativeBytes > maxMem) {
+                        throw new OutOfMemoryError();
+                    }
                 }
             }
         }
@@ -44,6 +48,9 @@
                 nativeBytes -= bytes;
             }
             register_native_free.invoke(runtime, bytes);
+            aboutToDeadlockLock = true;
+            synchronized (deadlockLock) {
+            }
         }
     }
 
@@ -59,7 +66,20 @@
         int allocation_count = 256;
         NativeAllocation[] allocations = new NativeAllocation[count];
         for (int i = 0; i < allocation_count; ++i) {
-            allocations[i % count] = new NativeAllocation(size);
+            allocations[i % count] = new NativeAllocation(size, false);
+        }
+        // Test that we don't get a deadlock if we are holding nativeLock. If there is no timeout,
+        // then we will get a finalizer timeout exception.
+        aboutToDeadlockLock = false;
+        synchronized (deadlockLock) {
+            for (int i = 0; aboutToDeadlockLock != true; ++i) {
+                allocations[i % count] = new NativeAllocation(size, true);
+            }
+            // Do more allocations now that the finalizer thread is deadlocked so that we force
+            // finalization and timeout. 
+            for (int i = 0; i < 10; ++i) {
+                allocations[i % count] = new NativeAllocation(size, true);
+            }
         }
         System.out.println("Test complete");
     }