Bug 674728 - Part 3: Evict cache asynchronized. r=honzab
authorSinker Li <thinker@codemud.net>
Fri, 30 Mar 2012 20:52:07 -0400
changeset 90721 a69635b2249037c3826d3f53ad7b2b56299b1c2f
parent 90720 dd0291644c67b6e7356f5e8fff6b3ce35d3db0e8
child 90722 f430bb8a0049068fb0e70658c0c935d2de7c4e08
push id7819
push userryanvm@gmail.com
push dateSat, 31 Mar 2012 00:59:04 +0000
treeherdermozilla-inbound@391418d235a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs674728
milestone14.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 674728 - Part 3: Evict cache asynchronized. r=honzab
netwerk/base/public/nsIApplicationCache.idl
netwerk/cache/nsDiskCacheDeviceSQL.cpp
netwerk/cache/nsDiskCacheDeviceSQL.h
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
--- a/netwerk/base/public/nsIApplicationCache.idl
+++ b/netwerk/base/public/nsIApplicationCache.idl
@@ -93,16 +93,33 @@ interface nsIApplicationCacheNamespace :
     /**
      * Data associated with this namespace, such as a fallback.  URI data should
      * use the asciiSpec of the URI.
      */
     readonly attribute ACString data;
 };
 
 /**
+ * Callback for asynchronized methods for nsIApplicationCache.
+ */
+[scriptable, uuid(062c8061-7c31-44a4-bd8d-302772e4a7eb)]
+interface nsIApplicationCacheAsyncCallback : nsISupports
+{
+    const long APP_CACHE_REQUEST_SUCCESS = 0;
+    const long APP_CACHE_REQUEST_ERROR = 1;
+
+    /**
+     * Callback function with result code.  It should be a nsresult.
+     *
+     * @param aState is an error code, one of APP_CACHE_REQUEST_*.
+     */
+    void handleAsyncCompletion(in PRUint32 aState);
+};
+
+/**
  * Application caches store resources for offline use.  Each
  * application cache has a unique client ID for use with
  * nsICacheService::openSession() to access the cache's entries.
  *
  * Each entry in the application cache can be marked with a set of
  * types, as discussed in the WHAT-WG offline applications
  * specification.
  *
@@ -183,16 +200,21 @@ interface nsIApplicationCache : nsISuppo
     /**
      * Discard this application cache.  Removes all cached resources
      * for this cache.  If this is the active application cache for the
      * group, the group will be removed.
      */
     void discard();
 
     /**
+     * Discard this application cache in asynchronized.
+     */
+    void discardAsync([optional] in nsIApplicationCacheAsyncCallback aCallback);
+
+    /**
      * Adds item types to a given entry.
      */
     void markEntry(in ACString key, in unsigned long typeBits);
 
     /**
      * Removes types from a given entry.  If the resulting entry has
      * no types left, the entry is removed.
      */
--- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -54,18 +54,20 @@
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsCRT.h"
 #include "nsArrayUtils.h"
 #include "nsIArray.h"
 #include "nsIVariant.h"
 #include "nsThreadUtils.h"
 
+#include "mozIStoragePendingStatement.h"
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
+#include "mozIStorageStatementCallback.h"
 #include "mozIStorageFunction.h"
 #include "mozStorageHelper.h"
 
 #include "nsICacheVisitor.h"
 #include "nsISeekableStream.h"
 
 #include "mozilla/FunctionTimer.h"
 #include "mozilla/Telemetry.h"
@@ -128,27 +130,37 @@ class AutoResetStatement
 
 class EvictionObserver
 {
   public:
   EvictionObserver(mozIStorageConnection *db,
                    nsOfflineCacheEvictionFunction *evictionFunction)
     : mDB(db), mEvictionFunction(evictionFunction)
     {
+      if (mEvictionFunction->AddObserver() != 1) {
+	// not first observer
+	return;
+      }
+
       mDB->ExecuteSimpleSQL(
           NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete AFTER DELETE"
                              " ON moz_cache FOR EACH ROW BEGIN SELECT"
                              " cache_eviction_observer("
                              "  OLD.key, OLD.generation);"
                              " END;"));
       mEvictionFunction->Reset();
     }
 
     ~EvictionObserver()
     {
+      if (mEvictionFunction->RemoveObserver() != 0) {
+	// not last observer
+	return;
+      }
+
       mDB->ExecuteSimpleSQL(
         NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
       mEvictionFunction->Reset();
     }
 
     void Apply() { return mEvictionFunction->Apply(); }
 
   private:
@@ -169,16 +181,167 @@ class EvictionObserver
  */
 static PRUint64
 DCacheHash(const char * key)
 {
   // initval 0x7416f295 was chosen randomly
   return (PRUint64(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
 }
 
+/**
+ * EvictAsyncHandler
+ */
+class EvictAsyncHandler : public mozIStorageStatementCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+
+  EvictAsyncHandler();
+  ~EvictAsyncHandler();
+
+  nsresult Init(const char *aClientID,
+		nsIApplicationCacheAsyncCallback *aCallback,
+		mozIStorageConnection *aDB,
+		nsOfflineCacheEvictionFunction *aEvictionFunction) {
+    mClientID = NS_strdup(aClientID);
+    if (mClientID == NULL)
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    mCallback = aCallback;
+    mDB = aDB;
+    mEvictionFunction = aEvictionFunction;
+
+    return NS_OK;
+  }
+
+  nsresult Start() {
+    mEvictionObserver = new EvictionObserver(mDB, mEvictionFunction);
+    HandleCompletion(mozIStorageStatementCallback::REASON_FINISHED);
+
+    return NS_OK;
+  }
+
+private:
+  void ReportError() {
+    mCallback->HandleAsyncCompletion(nsIApplicationCacheAsyncCallback::APP_CACHE_REQUEST_ERROR);
+  }
+
+  void ReportSuccess() {
+    mCallback->HandleAsyncCompletion(nsIApplicationCacheAsyncCallback::APP_CACHE_REQUEST_SUCCESS);
+  }
+
+private:
+  const char *mClientID;
+  nsCOMPtr<mozIStorageConnection> mDB;
+  nsCOMPtr<nsIApplicationCacheAsyncCallback> mCallback;
+  nsRefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
+  EvictionObserver *mEvictionObserver;
+
+  /* Current step in the receipt to complete a eviction. */
+  int mStep;
+};
+
+NS_IMPL_ISUPPORTS1(EvictAsyncHandler, mozIStorageStatementCallback)
+
+EvictAsyncHandler::EvictAsyncHandler() :
+  mClientID(NULL), mEvictionFunction(NULL), mStep(0)
+{
+}
+
+EvictAsyncHandler::~EvictAsyncHandler() {
+  if (mClientID)
+      NS_Free((void *)mClientID);
+  if (mEvictionObserver)
+    delete mEvictionObserver;
+}
+
+NS_IMETHODIMP
+EvictAsyncHandler::HandleResult(mozIStorageResultSet *aResultSet) {
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+EvictAsyncHandler::HandleError(mozIStorageError *aError) {
+  return NS_OK;
+}
+
+static nsresult
+EvictEntriesSteps(mozIStorageConnection *mDB,
+		  const char *clientID,
+		  int step,
+		  mozIStorageStatement **aStatement);
+
+NS_IMETHODIMP
+EvictAsyncHandler::HandleCompletion(unsigned short aReason) {
+  if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
+    mCallback->HandleAsyncCompletion(nsIApplicationCacheAsyncCallback::APP_CACHE_REQUEST_ERROR);
+    return NS_OK;
+  }
+
+  nsresult rv;
+  nsCOMPtr<mozIStorageStatement> statement;
+  rv = EvictEntriesSteps(mDB, mClientID, mStep++, getter_AddRefs(statement));
+  if (NS_FAILED(rv)) {
+    ReportError();
+    return NS_OK;
+  }
+
+  if (statement) {
+    nsCOMPtr<mozIStoragePendingStatement> pending;
+    rv = statement->ExecuteAsync(this, getter_AddRefs(pending));
+    if (NS_FAILED(rv)) {
+      ReportError();
+    }
+  } else {
+    // Complete the eviction, no more commands.
+    mEvictionObserver->Apply();
+    ReportSuccess();
+  }
+
+  return NS_OK;
+}
+
+/**
+ * RemoveFilesAsync removes files in a separated thread.
+ */
+class RemoveFilesAsync : public nsRunnable
+{
+public:
+  /**
+   * @param aItems is an array of nsIFile to remove.
+   */
+  RemoveFilesAsync(nsCOMArray<nsIFile> &aItems) :
+    mItems(aItems) {}
+  ~RemoveFilesAsync();
+
+  NS_IMETHOD Run();
+
+private:
+  nsCOMArray<nsIFile> mItems;
+  nsCOMPtr<nsIThread> mIOThread;
+};
+
+RemoveFilesAsync::~RemoveFilesAsync() {
+}
+
+NS_IMETHODIMP
+RemoveFilesAsync::Run() {
+  for (PRInt32 i = 0; i < mItems.Count(); i++) {
+#if defined(PR_LOGGING)
+    nsCAutoString path;
+    mItems[i]->GetNativePath(path);
+    LOG(("  removing %s\n", path.get()));
+#endif
+
+    mItems[i]->Remove(false);
+  }
+  return NS_OK;
+}
+
 /******************************************************************************
  * nsOfflineCacheEvictionFunction
  */
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsOfflineCacheEvictionFunction, mozIStorageFunction)
 
 // helper function for directly exposing the same data file binding
 // path algorithm used in nsOfflineCacheBinding::Create
@@ -236,26 +399,27 @@ nsOfflineCacheEvictionFunction::OnFuncti
   return NS_OK;
 }
 
 void
 nsOfflineCacheEvictionFunction::Apply()
 {
   LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
 
-  for (PRInt32 i = 0; i < mItems.Count(); i++) {
-#if defined(PR_LOGGING)
-    nsCAutoString path;
-    mItems[i]->GetNativePath(path);
-    LOG(("  removing %s\n", path.get()));
-#endif
-
-    mItems[i]->Remove(false);
+  if (!mIOThread) {
+    nsresult rv;
+
+    rv = NS_NewThread(getter_AddRefs(mIOThread));
+    NS_ASSERTION(NS_SUCCEEDED(rv), "fail to create a new thread");
   }
 
+  nsCOMPtr<RemoveFilesAsync> removeFiles = new RemoveFilesAsync(mItems);
+  NS_ASSERTION(removeFiles, "fail to instantiate RemoveFilesAsync");
+  mIOThread->Dispatch(removeFiles, NS_DISPATCH_NORMAL);
+
   Reset();
 }
 
 /******************************************************************************
  * nsOfflineCacheDeviceInfo
  */
 
 class nsOfflineCacheDeviceInfo : public nsICacheDeviceInfo
@@ -695,16 +859,32 @@ nsApplicationCache::Discard()
   {
     mDevice->DeactivateGroup(mGroup);
   }
 
   return mDevice->EvictEntries(mClientID.get());
 }
 
 NS_IMETHODIMP
+nsApplicationCache::DiscardAsync(nsIApplicationCacheAsyncCallback *aCallback)
+{
+  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+  mValid = false;
+
+  if (mDevice->IsActiveCache(mGroup, mClientID))
+  {
+    mDevice->DeactivateGroup(mGroup);
+  }
+
+  return mDevice->EvictEntriesAsync(mClientID.get(), aCallback);
+}
+
+NS_IMETHODIMP
 nsApplicationCache::MarkEntry(const nsACString &key,
                               PRUint32 typeBits)
 {
   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
 
   return mDevice->MarkEntry(mClientID, key, typeBits);
 }
@@ -1727,92 +1907,112 @@ nsOfflineCacheDevice::Visit(nsICacheVisi
     if (NS_FAILED(rv) || !keepGoing)
       break;
   }
 
   info->mRec = nsnull;
   return NS_OK;
 }
 
+static const char *sEvictCmdsClientID[] = {
+  "DELETE FROM moz_cache WHERE ClientID=? AND Flags = 0;",
+  "DELETE FROM moz_cache_groups WHERE ActiveClientID=?;",
+  "DELETE FROM moz_cache_namespaces WHERE ClientID=?",
+  NULL
+};
+
+static const char *sEvictCmds[] = {
+  "DELETE FROM moz_cache WHERE Flags = 0;",
+  "DELETE FROM moz_cache_groups;",
+  "DELETE FROM moz_cache_namespaces;",
+  NULL
+};
+
+/**
+ * Create SQL statement for every step of eviction of cache entries.
+ *
+ * @param mDB is the database connection used for the eviction.
+ * @param clientID is the clientID of cache entries being evicting.
+ * @param step is the number of current step. (start from 0)
+ * @param statement is a pointer to return the stathement.
+ */
+static nsresult
+EvictEntriesSteps(mozIStorageConnection *mDB,
+		  const char *clientID,
+		  int step,
+		  mozIStorageStatement **aStatement)
+{
+  const char **cmds = clientID ? sEvictCmdsClientID : sEvictCmds;
+  const char *cmd = cmds[step];
+
+  if (cmd == NULL) {
+    *aStatement = NULL;
+    return NS_OK;
+  }
+
+  // called to evict all entries matching the given clientID.
+
+  nsresult rv;
+  nsCOMPtr<mozIStorageStatement> statement;
+  rv = mDB->CreateStatement(nsDependentCString(cmd),
+			    getter_AddRefs(statement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (clientID) {
+    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  *aStatement = statement.forget().get();
+
+  return NS_OK;
+}
+
 nsresult
 nsOfflineCacheDevice::EvictEntries(const char *clientID)
 {
   LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
        clientID ? clientID : ""));
 
-  // called to evict all entries matching the given clientID.
+  int step = 0;
+  nsresult rv = NS_OK;;
 
   // need trigger to fire user defined function after a row is deleted
   // so we can delete the corresponding data file.
   EvictionObserver evictionObserver(mDB, mEvictionFunction);
 
   nsCOMPtr<mozIStorageStatement> statement;
-  nsresult rv;
-  if (clientID)
-  {
-    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=? AND Flags = 0;"),
-                              getter_AddRefs(statement));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
-                              getter_AddRefs(statement));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else
-  {
-    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE Flags = 0;"),
-                              getter_AddRefs(statement));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
-                              getter_AddRefs(statement));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
+  while (1) {
+    rv = EvictEntriesSteps(mDB, clientID, step++, getter_AddRefs(statement));
+    if (NS_FAILED(rv)) break;
+
+    if (!statement) break;	// finish
+
+    statement->Execute();
+    if (NS_FAILED(rv)) break;
   }
 
   evictionObserver.Apply();
 
-  statement = nsnull;
-  // Also evict any namespaces associated with this clientID.
-  if (clientID)
-  {
-    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
-                              getter_AddRefs(statement));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else
-  {
-    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
-                              getter_AddRefs(statement));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  rv = statement->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::EvictEntriesAsync(const char *clientID,
+					nsIApplicationCacheAsyncCallback *aCallback)
+{
+  LOG(("nsOfflineCacheDevice::EvictEntriesAsync [cid=%s]\n",
+       clientID ? clientID : ""));
+
+  EvictAsyncHandler *evictAsyncHandler = new EvictAsyncHandler();
+  if (evictAsyncHandler == NULL)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  evictAsyncHandler->Init(clientID, aCallback, mDB, mEvictionFunction);
+  evictAsyncHandler->Start();
   return NS_OK;
 }
 
 nsresult
 nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
                                 const nsACString &key,
                                 PRUint32 typeBits)
 {
--- a/netwerk/cache/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -72,24 +72,31 @@ private:
 
 class nsOfflineCacheEvictionFunction : public mozIStorageFunction {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEFUNCTION
 
   nsOfflineCacheEvictionFunction(nsOfflineCacheDevice *device)
     : mDevice(device)
+    , mObserverCount(0)
   {}
 
   void Reset() { mItems.Clear(); }
   void Apply();
 
+  int AddObserver() { return ++mObserverCount; }
+  int RemoveObserver() { return --mObserverCount; }
+
 private:
   nsOfflineCacheDevice *mDevice;
   nsCOMArray<nsIFile> mItems;
+  nsCOMPtr<nsIThread> mIOThread;
+
+  int mObserverCount;
 
 };
 
 class nsOfflineCacheDevice : public nsCacheDevice
                            , public nsIApplicationCacheService
 {
 public:
   nsOfflineCacheDevice();
@@ -126,16 +133,19 @@ public:
                                           nsIFile **        result);
 
   virtual nsresult        OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize);
   
   virtual nsresult        Visit(nsICacheVisitor * visitor);
 
   virtual nsresult        EvictEntries(const char * clientID);
 
+  virtual nsresult EvictEntriesAsync(const char * clientID,
+				     nsIApplicationCacheAsyncCallback *aCallback);
+
   /* Entry ownership */
   nsresult                GetOwnerDomains(const char *        clientID,
                                           PRUint32 *          count,
                                           char ***            domains);
   nsresult                GetOwnerURIs(const char *           clientID,
                                        const nsACString &     ownerDomain,
                                        PRUint32 *             count,
                                        char ***               uris);
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -1120,19 +1120,20 @@ nsOfflineManifestItem::OnStopRequest(nsI
 
     return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus);
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate::nsISupports
 //-----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS2(nsOfflineCacheUpdate,
+NS_IMPL_ISUPPORTS3(nsOfflineCacheUpdate,
                    nsIOfflineCacheUpdateObserver,
-                   nsIOfflineCacheUpdate)
+                   nsIOfflineCacheUpdate,
+                   nsIApplicationCacheAsyncCallback)
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineCacheUpdate::nsOfflineCacheUpdate()
     : mState(STATE_UNINITIALIZED)
     , mOwner(nsnull)
@@ -1442,36 +1443,25 @@ nsOfflineCacheUpdate::LoadCompleted()
         PRUint32 dummy_cache_type;
         rv = mApplicationCache->GetTypes(item->mCacheKey, &dummy_cache_type);
         bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed
 
         if (item_doomed &&
             mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit &&
             (item->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
                                 nsIApplicationCache::ITEM_FALLBACK))) {
-            rv = EvictOneNonPinned();
-            if (NS_FAILED(rv)) {
-                mSucceeded = false;
-                NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
-                Finish();
-                return;
+        rv = item->Cancel();
+
+        if (NS_SUCCEEDED(rv)) {
+            mPinnedEntryRetriesCount++;
+            // Do a retrying for current item, so mCurrentItem is not advanced.
+            rv = EvictOneNonPinnedAsync();
             }
 
-            rv = item->Cancel();
-            if (NS_FAILED(rv)) {
-                mSucceeded = false;
-                NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
-                Finish();
-                return;
-            }
-
-            mPinnedEntryRetriesCount++;
-            // Retry current item, so mCurrentItem is not advanced.
-            ProcessNextURI();
-            return;
+        if (NS_SUCCEEDED(rv)) return;
         }
     }
 
     // Advance to next item.
     mCurrentItem++;
     mPinnedEntryRetriesCount = 0;
 
     // Check for failures.  3XX, 4XX and 5XX errors on items explicitly
@@ -1886,17 +1876,18 @@ nsOfflineCacheUpdate::Finish()
 
     NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED);
 
     return rv;
 }
 
 static nsresult
 EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService,
-                      PRUint32 count, const char * const *groups)
+                      PRUint32 count, const char * const *groups,
+                      nsIApplicationCacheAsyncCallback *aCallback) {
 {
     nsresult rv;
     unsigned int i;
 
     for (i = 0; i < count; i++) {
         nsCOMPtr<nsIURI> uri;
         rv = NS_NewURI(getter_AddRefs(uri), groups[i]);
         NS_ENSURE_SUCCESS(rv, rv);
@@ -1910,39 +1901,47 @@ EvictOneOfCacheGroups(nsIApplicationCach
 
         bool pinned;
         rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(uri,
                                                                  NULL,
                                                                  &pinned);
         NS_ENSURE_SUCCESS(rv, rv);
 
         if (!pinned) {
-            rv = cache->Discard();
-            return NS_OK;
+            // Call HandleAsyncCompletion() when the task is completed.
+            rv = cache->DiscardAsync(aCallback);
+           return NS_OK;
         }
     }
 
     return NS_ERROR_FILE_NOT_FOUND;
 }
 
-nsresult
-nsOfflineCacheUpdate::EvictOneNonPinned()
+/**
+ * Evict one of non-pinned cache group in asynchronized.
+ *
+ * This method returns immediately.  It will start an async task to
+ * evict a selected cache group.  HandleAsyncCompletion() will be
+ * called while the eviction is completed.
+ */
+ nsresult
+nsOfflineCacheUpdate::EvictOneNonPinnedAsync()
 {
     nsresult rv;
 
     nsCOMPtr<nsIApplicationCacheService> cacheService =
         do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     PRUint32 count;
     char **groups;
     rv = cacheService->GetGroupsTimeOrdered(&count, &groups);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = EvictOneOfCacheGroups(cacheService, count, groups);
+    rv = EvictOneOfCacheGroups(cacheService, count, groups, this);
 
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, groups);
     return rv;
 }
 
 //-----------------------------------------------------------------------------
 // nsOfflineCacheUpdate::nsIOfflineCacheUpdate
 //-----------------------------------------------------------------------------
@@ -2154,8 +2153,24 @@ nsOfflineCacheUpdate::UpdateStateChanged
     return rv;
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *applicationCache)
 {
     return AssociateDocuments(applicationCache);
 }
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsIApplicationCacheAsyncCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::HandleAsyncCompletion(PRUint32 aState) {
+    if (aState != APP_CACHE_REQUEST_SUCCESS) {
+        mSucceeded = false;
+        NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+        Finish();
+        return NS_OK;
+    }
+
+    return ProcessNextURI();
+}
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -208,21 +208,23 @@ class nsOfflineCacheUpdateOwner
 {
 public:
     virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate) = 0;
 };
 
 class nsOfflineCacheUpdate : public nsIOfflineCacheUpdate
                            , public nsIOfflineCacheUpdateObserver
                            , public nsOfflineCacheUpdateOwner
+                           , public nsIApplicationCacheAsyncCallback
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOFFLINECACHEUPDATE
     NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+    NS_DECL_NSIAPPLICATIONCACHEASYNCCALLBACK
 
     nsOfflineCacheUpdate();
     ~nsOfflineCacheUpdate();
 
     static nsresult GetCacheKey(nsIURI *aURI, nsACString &aKey);
 
     nsresult Init();
 
@@ -253,17 +255,17 @@ private:
     nsresult AssociateDocuments(nsIApplicationCache* cache);
 
     nsresult GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
     nsresult NotifyState(PRUint32 state);
     nsresult Finish();
     nsresult FinishNoNotify();
 
     // Find one non-pinned cache group and evict it.
-    nsresult EvictOneNonPinned();
+    nsresult EvictOneNonPinnedAsync();
 
     enum {
         STATE_UNINITIALIZED,
         STATE_INITIALIZED,
         STATE_CHECKING,
         STATE_DOWNLOADING,
         STATE_CANCELLED,
         STATE_FINISHED