Handle recursive call into GrResourceCache::purgeAsNeeded

Review URL: http://codereview.appspot.com/4850042/



git-svn-id: http://skia.googlecode.com/svn/trunk@2046 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/src/GrResourceCache.cpp b/gpu/src/GrResourceCache.cpp
index e73cb9a..2f5dfaf 100644
--- a/gpu/src/GrResourceCache.cpp
+++ b/gpu/src/GrResourceCache.cpp
@@ -38,11 +38,13 @@
         fMaxCount(maxCount),
         fMaxBytes(maxBytes) {
     fEntryCount          = 0;
+    fUnlockedEntryCount  = 0;
     fEntryBytes          = 0;
     fClientDetachedCount = 0;
     fClientDetachedBytes = 0;
 
     fHead = fTail = NULL;
+    fPurging = false;
 }
 
 GrResourceCache::~GrResourceCache() {
@@ -86,6 +88,9 @@
     } else {
         fTail = prev;
     }
+    if (!entry->isLocked()) {
+        --fUnlockedEntryCount;
+    }
 
     // update our stats
     if (clientDetach) {
@@ -108,6 +113,9 @@
     if (NULL == fTail) {
         fTail = entry;
     }
+    if (!entry->isLocked()) {
+        ++fUnlockedEntryCount;
+    }
 
     // update our stats
     if (clientReattach) {
@@ -153,19 +161,29 @@
     GrResourceEntry* entry = fCache.find(key);
     if (entry) {
         this->internalDetach(entry, false);
-        this->attachToHead(entry, false);
         // mark the entry as "busy" so it doesn't get purged
+        // do this between detach and attach for locked count tracking
         entry->lock();
+        this->attachToHead(entry, false);
     }
     return entry;
 }
 
 GrResourceEntry* GrResourceCache::createAndLock(const GrResourceKey& key,
                                               GrResource* resource) {
+    // we don't expect to create new resources during a purge. In theory
+    // this could cause purgeAsNeeded() into an infinite loop (e.g.
+    // each resource destroyed creates and locks 2 resources and
+    // unlocks 1 thereby causing a new purge).
+    GrAssert(!fPurging);
     GrAutoResourceCacheValidate atcv(this);
 
     GrResourceEntry* entry = new GrResourceEntry(key, resource);
 
+    // mark the entry as "busy" so it doesn't get purged
+    // do this before attach for locked count tracking
+    entry->lock();
+
     this->attachToHead(entry, false);
     fCache.insert(key, entry);
 
@@ -174,8 +192,6 @@
              entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
 #endif
 
-    // mark the entry as "busy" so it doesn't get purged
-    entry->lock();
     this->purgeAsNeeded();
     return entry;
 }
@@ -199,34 +215,55 @@
     GrAssert(fCache.find(entry->key()));
 
     entry->unlock();
+    if (!entry->isLocked()) {
+        ++fUnlockedEntryCount;
+    }
     this->purgeAsNeeded();
 }
 
+/**
+ * Destroying a resource may potentially trigger the unlock of additional 
+ * resources which in turn will trigger a nested purge. We block the nested
+ * purge using the fPurging variable. However, the initial purge will keep
+ * looping until either all resources in the cache are unlocked or we've met
+ * the budget. There is an assertion in createAndLock to check against a
+ * resource's destructor inserting new resources into the cache. If these
+ * new resources were unlocked before purgeAsNeeded completed it could
+ * potentially make purgeAsNeeded loop infinitely.
+ */
 void GrResourceCache::purgeAsNeeded() {
-    GrAutoResourceCacheValidate atcv(this);
+    if (!fPurging) {
+        fPurging = true;
+        bool withinBudget = false;
+        do {
+            GrAutoResourceCacheValidate atcv(this);
+            GrResourceEntry* entry = fTail;
+            while (entry && fUnlockedEntryCount) {
+                if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
+                    withinBudget = true;
+                    break;
+                }
 
-    GrResourceEntry* entry = fTail;
-    while (entry) {
-        if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
-            break;
-        }
+                GrResourceEntry* prev = entry->fPrev;
+                if (!entry->isLocked()) {
+                    // remove from our cache
+                    fCache.remove(entry->fKey, entry);
 
-        GrResourceEntry* prev = entry->fPrev;
-        if (!entry->isLocked()) {
-            // remove from our cache
-            fCache.remove(entry->fKey, entry);
+                    // remove from our llist
+                    this->internalDetach(entry, false);
 
-            // remove from our llist
-            this->internalDetach(entry, false);
-
-#if GR_DUMP_TEXTURE_UPLOAD
-            GrPrintf("--- ~resource from cache %p [%d %d]\n", entry->resource(),
-                     entry->resource()->width(),
-                     entry->resource()->height());
-#endif
-            delete entry;
-        }
-        entry = prev;
+        #if GR_DUMP_TEXTURE_UPLOAD
+                    GrPrintf("--- ~resource from cache %p [%d %d]\n",
+                             entry->resource(),
+                             entry->resource()->width(),
+                             entry->resource()->height());
+        #endif
+                    delete entry;
+                }
+                entry = prev;
+            }
+        } while (!withinBudget && fUnlockedEntryCount);
+        fPurging = false;
     }
 }
 
@@ -247,6 +284,7 @@
     fHead = fTail = NULL;
     fEntryCount = 0;
     fEntryBytes = 0;
+    fUnlockedEntryCount = 0;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -282,16 +320,21 @@
 
     GrResourceEntry* entry = fHead;
     int count = 0;
+    int unlockCount = 0;
     size_t bytes = 0;
     while (entry) {
         entry->validate();
         GrAssert(fCache.find(entry->key()));
         count += 1;
         bytes += entry->resource()->sizeInBytes();
+        if (!entry->isLocked()) {
+            unlockCount += 1;
+        }
         entry = entry->fNext;
     }
     GrAssert(count == fEntryCount - fClientDetachedCount);
     GrAssert(bytes == fEntryBytes  - fClientDetachedBytes);
+    GrAssert(unlockCount == fUnlockedEntryCount);
 
     count = 0;
     for (entry = fTail; entry; entry = entry->fPrev) {