Bug 674728 - Part 3: Evict cache asynchronized. r=honzab
authorSinker Li <thinker@codemud.net>
Fri, 30 Mar 2012 20:52:07 -0400
changeset 90762 a69635b2249037c3826d3f53ad7b2b56299b1c2f
parent 90761 dd0291644c67b6e7356f5e8fff6b3ce35d3db0e8
child 90763 f430bb8a0049068fb0e70658c0c935d2de7c4e08
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs674728
milestone14.0a1
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