If a finalizer wedges, raise prio and retry.

The VM uses a watchdog mechanism to detect stuck finalizers.  It appears
that, in some cases, the watchdog may be firing because the HeapWorker
thread in a background process doesn't get any CPU time after a
remotely-induced GC finishes.  With this change, if the HeapWorker is
running at a reduced priority, we raise the priority and allow it to
try some more.

No attempt is made to put the thread priority back.  (The HeapWorker
thread doesn't do anything in an idle process, and what it does do is
geared toward freeing one kind of resource or another.)

For bug 2492196.

Change-Id: Ic734c2e2819b9d60d20b2961f2a75085d5879495
diff --git a/vm/Thread.c b/vm/Thread.c
index b2d5776..9375921 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -30,8 +30,6 @@
 #include <errno.h>
 #include <fcntl.h>
 
-#include <cutils/sched_policy.h>
-
 #if defined(HAVE_PRCTL)
 #include <sys/prctl.h>
 #endif
@@ -2490,9 +2488,6 @@
     //abort();
 }
 
-/* "change flags" values for next two functions */
-enum { kChangedPriority = 0x01, kChangedPolicy = 0x02 };
-
 /*
  * If the thread is running at below-normal priority, temporarily elevate
  * it to "normal".
@@ -2501,7 +2496,7 @@
  * indicating what was changed, storing the previous values in the
  * provided locations.
  */
-static int raiseThreadPriorityIfNeeded(Thread* thread, int* pSavedThreadPrio,
+int dvmRaiseThreadPriorityIfNeeded(Thread* thread, int* pSavedThreadPrio,
     SchedPolicy* pSavedThreadPolicy)
 {
     errno = 0;
@@ -2554,7 +2549,7 @@
 /*
  * Reset the priority values for the thread in question.
  */
-static void resetThreadPriority(Thread* thread, int changeFlags,
+void dvmResetThreadPriority(Thread* thread, int changeFlags,
     int savedThreadPrio, SchedPolicy savedThreadPolicy)
 {
     if ((changeFlags & kChangedPolicy) != 0) {
@@ -2632,7 +2627,7 @@
              */
             if (retryCount == 2) {
                 assert(thread->systemTid != 0);
-                priChangeFlags = raiseThreadPriorityIfNeeded(thread,
+                priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread,
                     &savedThreadPrio, &savedThreadPolicy);
             }
         }
@@ -2687,7 +2682,7 @@
         //dvmDumpThread(thread, false);   /* suspended, so dump is safe */
     }
     if (priChangeFlags != 0) {
-        resetThreadPriority(thread, priChangeFlags, savedThreadPrio,
+        dvmResetThreadPriority(thread, priChangeFlags, savedThreadPrio,
             savedThreadPolicy);
     }
 }
@@ -3158,6 +3153,27 @@
     return (Thread*) vmData;
 }
 
+/*
+ * Given a pthread handle, return the associated Thread*.
+ * Caller must NOT hold the thread list lock.
+ *
+ * Returns NULL if the thread was not found.
+ */
+Thread* dvmGetThreadByHandle(pthread_t handle)
+{
+    dvmLockThreadList(NULL);
+    Thread* thread = gDvm.threadList;
+    while (thread != NULL) {
+        if (thread->handle == handle)
+            break;
+
+        thread = thread->next;
+    }
+    dvmUnlockThreadList();
+
+    return thread;
+}
+
 
 /*
  * Conversion map for "nice" values.
diff --git a/vm/Thread.h b/vm/Thread.h
index 964c968..2d6ce5f 100644
--- a/vm/Thread.h
+++ b/vm/Thread.h
@@ -22,6 +22,9 @@
 
 #include "jni.h"
 
+#include <cutils/sched_policy.h>
+
+
 #if defined(CHECK_MUTEX) && !defined(__USE_UNIX98)
 /* glibc lacks this unless you #define __USE_UNIX98 */
 int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
@@ -434,6 +437,14 @@
 Thread* dvmGetThreadFromThreadObject(Object* vmThreadObj);
 
 /*
+ * Given a pthread handle, return the associated Thread*.
+ * Caller must NOT hold the thread list lock.
+ *
+ * Returns NULL if the thread was not found.
+ */
+Thread* dvmGetThreadByHandle(pthread_t handle);
+
+/*
  * Sleep in a thread.  Returns when the sleep timer returns or the thread
  * is interrupted.
  */
@@ -466,6 +477,25 @@
  */
 void dvmChangeThreadPriority(Thread* thread, int newPriority);
 
+/* "change flags" values for raise/reset thread priority calls */
+#define kChangedPriority    0x01
+#define kChangedPolicy      0x02
+
+/*
+ * If necessary, raise the thread's priority to nice=0 cgroup=fg.
+ *
+ * Returns bit flags indicating changes made (zero if nothing was done).
+ */
+int dvmRaiseThreadPriorityIfNeeded(Thread* thread, int* pSavedThreadPrio,
+    SchedPolicy* pSavedThreadPolicy);
+
+/*
+ * Drop the thread priority to what it was before an earlier call to
+ * dvmRaiseThreadPriorityIfNeeded().
+ */
+void dvmResetThreadPriority(Thread* thread, int changeFlags,
+    int savedThreadPrio, SchedPolicy savedThreadPolicy);
+
 /*
  * Debug: dump information about a single thread.
  */
diff --git a/vm/alloc/HeapWorker.c b/vm/alloc/HeapWorker.c
index 228be5a..e6e9209 100644
--- a/vm/alloc/HeapWorker.c
+++ b/vm/alloc/HeapWorker.c
@@ -145,8 +145,29 @@
              * watchdog and just reset the timer.
              */
             LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n");
-            heapWorkerInterpStartTime = now;        /* reset timer */
+            gDvm.gcHeap->heapWorkerInterpStartTime = now;   /* reset timer */
         } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) {
+            /*
+             * Before we give up entirely, see if maybe we're just not
+             * getting any CPU time because we're stuck in a background
+             * process group.  If we successfully move the thread into the
+             * foreground we'll just leave it there (it doesn't do anything
+             * if the process isn't GCing).
+             */
+            Thread* thread = dvmGetThreadByHandle(gDvm.heapWorkerHandle);
+            if (thread != NULL) {
+                int priChangeFlags, threadPrio;
+                SchedPolicy threadPolicy;
+                priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread,
+                        &threadPrio, &threadPolicy);
+                if (priChangeFlags != 0) {
+                    LOGI("HeapWorker watchdog expired, raising priority"
+                         " and retrying\n");
+                    gDvm.gcHeap->heapWorkerInterpStartTime = now;
+                    return;
+                }
+            }
+
             char* desc = dexProtoCopyMethodDescriptor(
                     &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
             LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n",