Allow allocation during a concurrent GC.

Previously, any thread performing a GC held the heap lock for the
entire GC.  If the GC performed was a concurrent GC, mutator threads
that allocate during the GC would be blocked until the GC completed.

With this change, if the GC performed is a concurrent GC, the heap
lock is released while the roots are being traced.  If a mutator
thread allocates an object from available storage, the allocation
proceeds.  If a mutator thread attempts to allocate an object larger
than available storage, the thread will block until the GC completes.

Change-Id: I91a04179c6f583f878b685405a6fdd16b9995017
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index 544230d..86f9207 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -285,8 +285,7 @@
  *
  * These aren't exact, and should not be treated as such.
  */
-static inline void
-countAllocation(Heap *heap, const void *ptr, bool isObj)
+static void countAllocation(Heap *heap, const void *ptr, bool isObj)
 {
     HeapSource *hs;
 
@@ -789,28 +788,41 @@
     HeapSource *hs = gHs;
     Heap *heap;
     void *ptr;
+    size_t allocated;
 
     HS_BOILERPLATE();
     heap = hs2heap(hs);
-
-    if (heap->bytesAllocated + n <= hs->softLimit) {
-        ptr = mspace_calloc(heap->msp, 1, n);
-        if (ptr != NULL) {
-            countAllocation(heap, ptr, true);
-            size_t allocated = heap->bytesAllocated - heap->prevBytesAllocated;
-            if (allocated > OCCUPANCY_THRESHOLD) {
-                if (hs->hasGcThread == true) {
-                    dvmSignalCond(&gHs->gcThreadCond);
-                }
-            }
-        }
-    } else {
-        /* This allocation would push us over the soft limit;
-         * act as if the heap is full.
+    if (heap->bytesAllocated + n > hs->softLimit) {
+        /*
+         * This allocation would push us over the soft limit; act as
+         * if the heap is full.
          */
         LOGV_HEAP("softLimit of %zd.%03zdMB hit for %zd-byte allocation\n",
-                FRACTIONAL_MB(hs->softLimit), n);
-        ptr = NULL;
+                  FRACTIONAL_MB(hs->softLimit), n);
+        return NULL;
+    }
+    ptr = mspace_calloc(heap->msp, 1, n);
+    if (ptr == NULL) {
+        return NULL;
+    }
+    countAllocation(heap, ptr, true);
+    /*
+     * Check to see if a concurrent GC should be initiated.
+     */
+    if (gDvm.gcHeap->gcRunning || !hs->hasGcThread) {
+        /*
+         * The garbage collector thread is already running or has yet
+         * to be started.  Do nothing.
+         */
+        return ptr;
+    }
+    allocated = heap->bytesAllocated - heap->prevBytesAllocated;
+    if (allocated > OCCUPANCY_THRESHOLD) {
+        /*
+         * We have exceeded the occupancy threshold.  Wake up the
+         * garbage collector.
+         */
+        dvmSignalCond(&gHs->gcThreadCond);
     }
     return ptr;
 }
@@ -1653,6 +1665,18 @@
         goto out;
     }
 
+    if (gDvm.gcHeap->gcRunning) {
+        /*
+         * The GC is concurrently tracing the heap.  Release the heap
+         * lock, wait for the GC to complete, and try again.
+         */
+        dvmWaitForConcurrentGcToComplete();
+        if (externalAlloc(hs, n, false)) {
+            ret = true;
+            goto out;
+        }
+    }
+
     /* The "allocation" failed.  Free up some space by doing
      * a full garbage collection.  This may grow the heap source
      * if the live set is sufficiently large.