Bug 958317 - HTTP cache v2: allow yield to more priority levels in IOThread, r=michal
authorHonza Bambas <honzab.moz@firemni.cz>
Thu, 27 Feb 2014 00:11:42 +0100
changeset 191184 a0b80ff2d63813ee13d9f25932c508dc26693d6e
parent 191183 a8cb3454d250df05a7fa57d1eb131cd3c455c547
child 191185 75c53d96a9c94a3c4f8b36f6f5245bf8d06443a4
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs958317
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 958317 - HTTP cache v2: allow yield to more priority levels in IOThread, r=michal
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheIOThread.cpp
netwerk/cache2/CacheIOThread.h
netwerk/cache2/CacheIndex.cpp
netwerk/cache2/CacheStorageService.cpp
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -37,19 +37,17 @@
 #endif
 
 
 namespace mozilla {
 namespace net {
 
 #define kOpenHandlesLimit      64
 #define kMetadataWriteDelay    5000
-#define kEvictionLoopLimit     40      // in milliseconds
 #define kRemoveTrashStartDelay 60000   // in milliseconds
-#define kRemoveTrashLoopLimit  40      // in milliseconds
 
 bool
 CacheFileHandle::DispatchRelease()
 {
   if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
     return false;
   }
 
@@ -2348,52 +2346,46 @@ CacheFileIOManager::OverLimitEvictionInt
 {
   LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
 
   nsresult rv;
 
   MOZ_ASSERT(mIOThread->IsCurrentThread());
 
   // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
-  // here and set ti to true again once we dispatch another event that will
+  // here and set it to true again once we dispatch another event that will
   // continue with the eviction. The reason why we do so is that we can fail
   // early anywhere in this method and the variable will contain a correct
   // value. Otherwise we would need to set it to false on every failing place.
   mOverLimitEvicting = false;
 
   if (mShuttingDown) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  TimeStamp start;
-
   while (true) {
     uint32_t cacheUsage;
     rv = CacheIndex::GetCacheSize(&cacheUsage);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
     if (cacheUsage <= cacheLimit) {
       LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size under "
            "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
       return NS_OK;
     }
 
     LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
          "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
 
-    if (start.IsNull()) {
-      start = TimeStamp::NowLoRes();
-    } else {
-      TimeDuration elapsed = TimeStamp::NowLoRes() - start;
-      if (elapsed.ToMilliseconds() >= kEvictionLoopLimit) {
-        LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
-             "after %u ms.", static_cast<uint32_t>(elapsed.ToMilliseconds())));
-        break;
-      }
+    if (CacheIOThread::YieldAndRerun()) {
+      LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
+           "for higher level events."));
+      mOverLimitEvicting = true;
+      return NS_OK;
     }
 
     SHA1Sum::Hash hash;
     uint32_t cnt;
     static uint32_t consecutiveFailures = 0;
     rv = CacheIndex::GetEntryForEviction(&hash, &cnt);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2438,24 +2430,17 @@ CacheFileIOManager::OverLimitEvictionInt
         // but we've reached a sane number of tries. It is likely that another
         // eviction will start soon. And as said earlier, this normally doesn't
         // happen at all.
         return NS_OK;
       }
     }
   }
 
-  nsCOMPtr<nsIRunnable> ev;
-  ev = NS_NewRunnableMethod(this,
-                            &CacheFileIOManager::OverLimitEvictionInternal);
-
-  rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mOverLimitEvicting = true;
+  NS_NOTREACHED("We should never get here");
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::TrashDirectory(nsIFile *aFile)
 {
 #ifdef PR_LOGGING
   nsAutoCString path;
@@ -2623,30 +2608,22 @@ CacheFileIOManager::RemoveTrashInternal(
     }
   }
 
   // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
   // here and set it again once we dispatch a continuation event. By doing so,
   // we don't have to drop the flag on any possible early return.
   mRemovingTrashDirs = false;
 
-  TimeStamp start;
-
   while (true) {
-    if (start.IsNull()) {
-      start = TimeStamp::NowLoRes();
-    } else {
-      static TimeDuration const kLimit = TimeDuration::FromMilliseconds(
-                                           kRemoveTrashLoopLimit);
-      TimeDuration elapsed = TimeStamp::NowLoRes() - start;
-      if (elapsed >= kLimit) {
-        LOG(("CacheFileIOManager::RemoveTrashInternal() - Breaking loop after "
-             "%u ms.", static_cast<uint32_t>(elapsed.ToMilliseconds())));
-        break;
-      }
+    if (CacheIOThread::YieldAndRerun()) {
+      LOG(("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
+           "higher level events."));
+      mRemovingTrashDirs = true;
+      return NS_OK;
     }
 
     // Find some trash directory
     if (!mTrashDir) {
       MOZ_ASSERT(!mTrashDirEnumerator);
 
       rv = FindTrashDirToRemove();
       if (rv == NS_ERROR_NOT_AVAILABLE) {
@@ -2704,24 +2681,17 @@ CacheFileIOManager::RemoveTrashInternal(
         LOG(("CacheFileIOManager::RemoveTrashInternal() - Found a directory in a trash "
             "directory! It will be removed recursively, but this can block IO "
             "thread for a while! [file=%s]", path.get()));
       }
       file->Remove(isDir);
     }
   }
 
-  nsCOMPtr<nsIRunnable> ev;
-  ev = NS_NewRunnableMethod(this,
-                            &CacheFileIOManager::RemoveTrashInternal);
-
-  rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mRemovingTrashDirs = true;
+  NS_NOTREACHED("We should never get here");
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::FindTrashDirToRemove()
 {
   LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
 
--- a/netwerk/cache2/CacheIOThread.cpp
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -9,29 +9,35 @@
 #include "nsISupportsImpl.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
 #include "mozilla/VisualEventTracer.h"
 
 namespace mozilla {
 namespace net {
 
+CacheIOThread* CacheIOThread::sSelf = nullptr;
+
 NS_IMPL_ISUPPORTS1(CacheIOThread, nsIThreadObserver)
 
 CacheIOThread::CacheIOThread()
 : mMonitor("CacheIOThread")
 , mThread(nullptr)
 , mLowestLevelWaiting(LAST_LEVEL)
+, mCurrentlyExecutingLevel(0)
 , mHasXPCOMEvents(false)
+, mRerunCurrentEvent(false)
 , mShutdown(false)
 {
+  sSelf = this;
 }
 
 CacheIOThread::~CacheIOThread()
 {
+  sSelf = nullptr;
 #ifdef DEBUG
   for (uint32_t level = 0; level < LAST_LEVEL; ++level) {
     MOZ_ASSERT(!mEventQueue[level].Length());
   }
 #endif
 }
 
 nsresult CacheIOThread::Init()
@@ -63,16 +69,37 @@ nsresult CacheIOThread::Dispatch(nsIRunn
   return NS_OK;
 }
 
 bool CacheIOThread::IsCurrentThread()
 {
   return mThread == PR_GetCurrentThread();
 }
 
+bool CacheIOThread::YieldInternal()
+{
+  if (!IsCurrentThread()) {
+    NS_WARNING("Trying to yield to priority events on non-cache2 I/O thread? "
+               "You probably do something wrong.");
+    return false;
+  }
+
+  if (mCurrentlyExecutingLevel == XPCOM_LEVEL) {
+    // Doesn't make any sense, since this handler is the one
+    // that would be executed as the next one.
+    return false;
+  }
+
+  if (!EventsPending(mCurrentlyExecutingLevel))
+    return false;
+
+  mRerunCurrentEvent = true;
+  return true;
+}
+
 nsresult CacheIOThread::Shutdown()
 {
   {
     MonitorAutoLock lock(mMonitor);
     mShutdown = true;
     mMonitor.NotifyAll();
   }
 
@@ -132,16 +159,18 @@ loopStart:
       mLowestLevelWaiting = LAST_LEVEL;
 
       // Process xpcom events first
       while (mHasXPCOMEvents) {
         eventtracer::AutoEventTracer tracer(this, eventtracer::eExec, eventtracer::eDone,
           "net::cache::io::level(xpcom)");
 
         mHasXPCOMEvents = false;
+        mCurrentlyExecutingLevel = XPCOM_LEVEL;
+
         MonitorAutoUnlock unlock(mMonitor);
 
         bool processedEvent;
         nsresult rv;
         do {
           rv = mXPCOMThread->ProcessNextEvent(false, &processedEvent);
         } while (NS_SUCCEEDED(rv) && processedEvent);
       }
@@ -199,30 +228,44 @@ void CacheIOThread::LoopOneLevel(uint32_
 {
   eventtracer::AutoEventTracer tracer(this, eventtracer::eExec, eventtracer::eDone,
     sLevelTraceName[aLevel]);
 
   nsTArray<nsRefPtr<nsIRunnable> > events;
   events.SwapElements(mEventQueue[aLevel]);
   uint32_t length = events.Length();
 
+  mCurrentlyExecutingLevel = aLevel;
+
   bool returnEvents = false;
   uint32_t index;
   {
     MonitorAutoUnlock unlock(mMonitor);
 
     for (index = 0; index < length; ++index) {
       if (EventsPending(aLevel)) {
         // Somebody scheduled a new event on a lower level, break and harry
         // to execute it!  Don't forget to return what we haven't exec.
         returnEvents = true;
         break;
       }
 
+      // Drop any previous flagging, only an event on the current level may set
+      // this flag.
+      mRerunCurrentEvent = false;
+
       events[index]->Run();
+
+      if (mRerunCurrentEvent) {
+        // The event handler yields to higher priority events and wants to rerun.
+        returnEvents = true;
+        break;
+      }
+
+      // Release outside the lock.
       events[index] = nullptr;
     }
   }
 
   if (returnEvents)
     mEventQueue[aLevel].InsertElementsAt(0, events.Elements() + index, length - index);
 }
 
--- a/netwerk/cache2/CacheIOThread.h
+++ b/netwerk/cache2/CacheIOThread.h
@@ -31,41 +31,66 @@ public:
     READ_PRIORITY,
     OPEN,
     READ,
     WRITE,
     MANAGEMENT,
     CLOSE,
     BUILD_OR_UPDATE_INDEX,
     EVICT,
-    LAST_LEVEL
+    LAST_LEVEL,
+
+    // This is actually executed as the first level, but we want this enum
+    // value merely as an indicator while other values are used as indexes
+    // to the queue array.  Hence put at end and not as the first.
+    XPCOM_LEVEL
   };
 
   nsresult Init();
   nsresult Dispatch(nsIRunnable* aRunnable, uint32_t aLevel);
   bool IsCurrentThread();
+
+  /**
+   * Callable only on this thread, checks if there is an event waiting in
+   * the event queue with a higher execution priority.  If so, the result
+   * is true and the current event handler should break it's work and return
+   * from Run() method immediately.  The event handler will be rerun again
+   * when all more priority events are processed.  Events pending after this
+   * handler (i.e. the one that called YieldAndRerun()) will not execute sooner
+   * then this handler is executed w/o a call to YieldAndRerun().
+   */
+  static bool YieldAndRerun()
+  {
+    return sSelf ? sSelf->YieldInternal() : false;
+  }
+
   nsresult Shutdown();
   already_AddRefed<nsIEventTarget> Target();
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   static void ThreadFunc(void* aClosure);
   void ThreadFunc();
   void LoopOneLevel(uint32_t aLevel);
   bool EventsPending(uint32_t aLastLevel = LAST_LEVEL);
+  bool YieldInternal();
+
+  static CacheIOThread* sSelf;
 
   mozilla::Monitor mMonitor;
   PRThread* mThread;
   nsCOMPtr<nsIThread> mXPCOMThread;
   uint32_t mLowestLevelWaiting;
+  uint32_t mCurrentlyExecutingLevel;
   nsTArray<nsRefPtr<nsIRunnable> > mEventQueue[LAST_LEVEL];
 
   bool mHasXPCOMEvents;
+  bool mRerunCurrentEvent;
   bool mShutdown;
 };
 
 } // net
 } // mozilla
 
 #endif
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -20,18 +20,16 @@
 
 
 #define kMinUnwrittenChanges   300
 #define kMinDumpInterval       20000 // in milliseconds
 #define kMaxBufSize            16384
 #define kIndexVersion          0x00000001
 #define kBuildIndexStartDelay  10000 // in milliseconds
 #define kUpdateIndexStartDelay 10000 // in milliseconds
-#define kBuildIndexLoopLimit   40    // in milliseconds
-#define kUpdateIndexLoopLimit  40    // in milliseconds
 
 const char kIndexName[]     = "index";
 const char kTempIndexName[] = "index.tmp";
 const char kJournalName[]   = "index.log";
 
 namespace mozilla {
 namespace net {
 
@@ -2364,30 +2362,20 @@ CacheIndex::BuildIndex()
     }
 
     if (NS_FAILED(rv)) {
       FinishBuild(false);
       return;
     }
   }
 
-  TimeStamp start;
-
   while (true) {
-    if (start.IsNull()) {
-      start = TimeStamp::NowLoRes();
-    } else {
-      static TimeDuration const kLimit = TimeDuration::FromMilliseconds(
-                                           kBuildIndexLoopLimit);
-      TimeDuration elapsed = TimeStamp::NowLoRes() - start;
-      if (elapsed >= kLimit) {
-        LOG(("CacheIndex::BuildIndex() - Breaking loop after %u ms.",
-             static_cast<uint32_t>(elapsed.ToMilliseconds())));
-        break;
-      }
+    if (CacheIOThread::YieldAndRerun()) {
+      LOG(("CacheIndex::BuildIndex() - Breaking loop for higher level events."));
+      return;
     }
 
     nsCOMPtr<nsIFile> file;
     {
       // Do not do IO under the lock.
       CacheIndexAutoUnlock unlock(this);
       rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
     }
@@ -2479,26 +2467,17 @@ CacheIndex::BuildIndex()
       entry = mIndex.PutEntry(hash);
       InitEntryFromDiskData(entry, meta, size);
       LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]",
            leaf.get()));
       entry->Log();
     }
   }
 
-  nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
-  MOZ_ASSERT(ioThread);
-
-  rv = ioThread->Dispatch(this, CacheIOThread::BUILD_OR_UPDATE_INDEX);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("CacheIndex::BuildIndex() - Can't dispatch event");
-    LOG(("CacheIndex::BuildIndex() - Can't dispatch event" ));
-    FinishBuild(false);
-    return;
-  }
+  NS_NOTREACHED("We should never get here");
 }
 
 void
 CacheIndex::FinishBuild(bool aSucceeded)
 {
   LOG(("CacheIndex::FinishBuild() [succeeded=%d]", aSucceeded));
 
   MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == BUILDING);
@@ -2615,30 +2594,21 @@ CacheIndex::UpdateIndex()
     }
 
     if (NS_FAILED(rv)) {
       FinishUpdate(false);
       return;
     }
   }
 
-  TimeStamp start;
-
   while (true) {
-    if (start.IsNull()) {
-      start = TimeStamp::NowLoRes();
-    } else {
-      static TimeDuration const kLimit = TimeDuration::FromMilliseconds(
-                                           kUpdateIndexLoopLimit);
-      TimeDuration elapsed = TimeStamp::NowLoRes() - start;
-      if (elapsed >= kLimit) {
-        LOG(("CacheIndex::UpdateIndex() - Breaking loop after %u ms.",
-             static_cast<uint32_t>(elapsed.ToMilliseconds())));
-        break;
-      }
+    if (CacheIOThread::YieldAndRerun()) {
+      LOG(("CacheIndex::UpdateIndex() - Breaking loop for higher level "
+           "events."));
+      return;
     }
 
     nsCOMPtr<nsIFile> file;
     {
       // Do not do IO under the lock.
       CacheIndexAutoUnlock unlock(this);
       rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
     }
@@ -2764,26 +2734,17 @@ CacheIndex::UpdateIndex()
       entry = mIndex.PutEntry(hash);
       InitEntryFromDiskData(entry, meta, size);
       LOG(("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
            "[hash=%s]", leaf.get()));
       entry->Log();
     }
   }
 
-  nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
-  MOZ_ASSERT(ioThread);
-
-  rv = ioThread->Dispatch(this, CacheIOThread::BUILD_OR_UPDATE_INDEX);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("CacheIndex::UpdateIndex() - Can't dispatch event");
-    LOG(("CacheIndex::UpdateIndex() - Can't dispatch event" ));
-    FinishUpdate(false);
-    return;
-  }
+  NS_NOTREACHED("We should never get here");
 }
 
 void
 CacheIndex::FinishUpdate(bool aSucceeded)
 {
   LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
 
   MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == UPDATING);
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -633,23 +633,21 @@ nsresult CacheFilesDeletor::Execute()
   LOG(("CacheFilesDeletor::Execute [this=%p]", this));
 
   if (!mEnumerator) {
     // No enumerator means the job is done.
     return NS_OK;
   }
 
   nsresult rv;
-  TimeStamp start;
 
   switch (mMode) {
   case ALL:
   case DOOMED:
     // Simply delete all files, don't doom then though the backend
-    start = TimeStamp::NowLoRes();
 
     while (mEnumerator->HasMore()) {
       nsCOMPtr<nsIFile> file;
       rv = mEnumerator->GetNextFile(getter_AddRefs(file));
       if (NS_FAILED(rv))
         return rv;
 
 #ifdef PR_LOG
@@ -660,25 +658,19 @@ nsresult CacheFilesDeletor::Execute()
 
       rv = file->Remove(false);
       if (NS_FAILED(rv)) {
         LOG(("  could not remove the file, probably doomed, rv=0x%08x", rv));
       }
 
       ++mRunning;
 
-      if (!(mRunning % (1 << 5)) && mEnumerator->HasMore()) {
-        TimeStamp now(TimeStamp::NowLoRes());
-#define DELETOR_LOOP_LIMIT_MS 200
-        static TimeDuration const kLimitms = TimeDuration::FromMilliseconds(DELETOR_LOOP_LIMIT_MS);
-        if ((now - start) > kLimitms) {
-          LOG(("  deleted %u files, breaking %dms loop", mRunning, DELETOR_LOOP_LIMIT_MS));
-          rv = mIOThread->Dispatch(this, CacheIOThread::EVICT);
-          return rv;
-        }
+      if (CacheIOThread::YieldAndRerun()) {
+        LOG(("  deleted %u files, breaking loop for higher level events."));
+        return NS_OK;
       }
     }
 
     break;
 
   default:
     MOZ_ASSERT(false);
   }
@@ -1061,30 +1053,35 @@ CacheStorageService::PurgeOverMemoryLimi
 
   if (mMemorySize > memoryLimit) {
     LOG(("  memory data consumption over the limit, abandon any entry"));
     PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE);
   }
 
   LOG(("  purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
 
-  mPurging = false;
+  // When we exit because of yield, leave the flag so this event is not reposted
+  // from OnMemoryConsumptionChange unnecessarily until we are dequeued again.
+  mPurging = CacheIOThread::YieldAndRerun();
 }
 
 void
 CacheStorageService::PurgeExpired()
 {
   MOZ_ASSERT(IsOnManagementThread());
 
   mExpirationArray.Sort(ExpirationComparator());
   uint32_t now = NowInSeconds();
 
   uint32_t const memoryLimit = CacheObserver::MemoryLimit();
 
   for (uint32_t i = 0; mMemorySize > memoryLimit && i < mExpirationArray.Length();) {
+    if (CacheIOThread::YieldAndRerun())
+      return;
+
     nsRefPtr<CacheEntry> entry = mExpirationArray[i];
 
     uint32_t expirationTime = entry->GetExpirationTime();
     if (expirationTime > 0 && expirationTime <= now) {
       LOG(("  dooming expired entry=%p, exptime=%u (now=%u)",
         entry.get(), entry->GetExpirationTime(), now));
 
       entry->PurgeAndDoom();
@@ -1104,16 +1101,19 @@ CacheStorageService::PurgeByFrecency(boo
   if (aFrecencyNeedsSort) {
     mFrecencyArray.Sort(FrecencyComparator());
     aFrecencyNeedsSort = false;
   }
 
   uint32_t const memoryLimit = CacheObserver::MemoryLimit();
 
   for (uint32_t i = 0; mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
+    if (CacheIOThread::YieldAndRerun())
+      return;
+
     nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
 
     if (entry->Purge(aWhat)) {
       LOG(("  abandoned (%d), entry=%p, frecency=%1.10f",
         aWhat, entry.get(), entry->GetFrecency()));
       continue;
     }
 
@@ -1124,16 +1124,19 @@ CacheStorageService::PurgeByFrecency(boo
 
 void
 CacheStorageService::PurgeAll(uint32_t aWhat)
 {
   LOG(("CacheStorageService::PurgeAll aWhat=%d", aWhat));
   MOZ_ASSERT(IsOnManagementThread());
 
   for (uint32_t i = 0; i < mFrecencyArray.Length();) {
+    if (CacheIOThread::YieldAndRerun())
+      return;
+
     nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
 
     if (entry->Purge(aWhat)) {
       LOG(("  abandoned entry=%p", entry.get()));
       continue;
     }
 
     // not purged, move to the next one