Bug 722033 - Invalidate cache entry in nsHttpChannel::DoInvalidateCacheEntry() asynchronously
authorMichal Novotny <michal.novotny@gmail.com>
Thu, 22 Mar 2012 23:54:20 +0100
changeset 93402 835ac64631ec9c647c7ab93861bb9b9cce2313c9
parent 93401 5b61e3d75735e331ac401f5f7b057c9997d03aa0
child 93403 add2ed43cc05d2531b351c1f1f22b16e97279204
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs722033
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 722033 - Invalidate cache entry in nsHttpChannel::DoInvalidateCacheEntry() asynchronously
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsCacheSession.cpp
netwerk/cache/nsICacheListener.idl
netwerk/cache/nsICacheSession.idl
netwerk/protocol/ftp/nsFtpConnectionThread.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
netwerk/test/unit/test_doomentry.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -1030,16 +1030,101 @@ public:
 protected:
     virtual ~nsProcessRequestEvent() {}
 
 private:
     nsCacheRequest *mRequest;
 };
 
 /******************************************************************************
+ * nsNotifyDoomListener
+ *****************************************************************************/
+
+class nsNotifyDoomListener : public nsRunnable {
+public:
+    nsNotifyDoomListener(nsICacheListener *listener,
+                         nsresult status)
+        : mListener(listener)      // transfers reference
+        , mStatus(status)
+    {}
+
+    NS_IMETHOD Run()
+    {
+        mListener->OnCacheEntryDoomed(mStatus);
+        NS_RELEASE(mListener);
+        return NS_OK;
+    }
+
+private:
+    nsICacheListener *mListener;
+    nsresult          mStatus;
+};
+
+/******************************************************************************
+ * nsDoomEvent
+ *****************************************************************************/
+
+class nsDoomEvent : public nsRunnable {
+public:
+    nsDoomEvent(nsCacheSession *session,
+                const nsACString &key,
+                nsICacheListener *listener)
+    {
+        mKey = *session->ClientID();
+        mKey.Append(':');
+        mKey.Append(key);
+        mStoragePolicy = session->StoragePolicy();
+        mListener = listener;
+        mThread = do_GetCurrentThread();
+        // We addref the listener here and release it in nsNotifyDoomListener
+        // on the callers thread. If posting of nsNotifyDoomListener event fails
+        // we leak the listener which is better than releasing it on a wrong
+        // thread.
+        NS_IF_ADDREF(mListener);
+    }
+
+    NS_IMETHOD Run()
+    {
+        nsCacheServiceAutoLock lock;
+
+        bool foundActive = true;
+        nsresult status = NS_ERROR_NOT_AVAILABLE;
+        nsCacheEntry *entry;
+        entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
+        if (!entry) {
+            bool collision = false;
+            foundActive = false;
+            entry = nsCacheService::gService->SearchCacheDevices(&mKey,
+                                                                 mStoragePolicy,
+                                                                 &collision);
+        }
+
+        if (entry) {
+            status = NS_OK;
+            nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
+        }
+
+        if (mListener) {
+            mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
+                              NS_DISPATCH_NORMAL);
+            // posted event will release the reference on the correct thread
+            mListener = nsnull;
+        }
+
+        return NS_OK;
+    }
+
+private:
+    nsCString             mKey;
+    nsCacheStoragePolicy  mStoragePolicy;
+    nsICacheListener     *mListener;
+    nsCOMPtr<nsIThread>   mThread;
+};
+
+/******************************************************************************
  * nsCacheService
  *****************************************************************************/
 nsCacheService *   nsCacheService::gService = nsnull;
 
 static nsCOMPtr<nsIMemoryReporter> MemoryCacheReporter = nsnull;
 
 NS_THREADSAFE_MEMORY_REPORTER_IMPLEMENT(NetworkMemoryCache,
     "explicit/network-memory-cache",
@@ -1345,16 +1430,32 @@ nsCacheService::IsStorageEnabledForPolic
     if (gService == nsnull) return NS_ERROR_NOT_AVAILABLE;
     nsCacheServiceAutoLock lock;
 
     *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
     return NS_OK;
 }
 
 
+nsresult
+nsCacheService::DoomEntry(nsCacheSession   *session,
+                          const nsACString &key,
+                          nsICacheListener *listener)
+{
+    CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
+                     session, PromiseFlatCString(key).get()));
+    NS_ASSERTION(gService, "nsCacheService::gService is null.");
+
+    if (!gService->mInitialized)
+        return NS_ERROR_NOT_INITIALIZED;
+
+    return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
+}
+
+
 bool          
 nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy  storagePolicy)
 {
     if (gService->mEnableMemoryDevice &&
         (storagePolicy == nsICache::STORE_ANYWHERE ||
          storagePolicy == nsICache::STORE_IN_MEMORY)) {
         return true;
     }
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -94,16 +94,20 @@ public:
                                     nsICacheListener *         listener,
                                     nsICacheEntryDescriptor ** result);
 
     static nsresult  EvictEntriesForSession(nsCacheSession *   session);
 
     static nsresult  IsStorageEnabledForPolicy(nsCacheStoragePolicy  storagePolicy,
                                                bool *              result);
 
+    static nsresult  DoomEntry(nsCacheSession   *session,
+                               const nsACString &key,
+                               nsICacheListener *listener);
+
     /**
      * Methods called by nsCacheEntryDescriptor
      */
 
     static void      CloseDescriptor(nsCacheEntryDescriptor * descriptor);
 
     static nsresult  GetFileForEntry(nsCacheEntry *         entry,
                                      nsIFile **             result);
@@ -193,16 +197,17 @@ public:
 
 private:
     friend class nsCacheServiceAutoLock;
     friend class nsOfflineCacheDevice;
     friend class nsProcessRequestEvent;
     friend class nsSetSmartSizeEvent;
     friend class nsBlockOnCacheThreadEvent;
     friend class nsSetDiskSmartSizeCallback;
+    friend class nsDoomEvent;
 
     /**
      * Internal Methods
      */
 
     static void      Lock();
     static void      Unlock();
 
--- a/netwerk/cache/nsCacheSession.cpp
+++ b/netwerk/cache/nsCacheSession.cpp
@@ -123,8 +123,14 @@ NS_IMETHODIMP nsCacheSession::EvictEntri
 }
 
 
 NS_IMETHODIMP nsCacheSession::IsStorageEnabled(bool *result)
 {
 
     return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result);
 }
+
+NS_IMETHODIMP nsCacheSession::DoomEntry(const nsACString &key,
+                                        nsICacheListener *listener)
+{
+    return nsCacheService::DoomEntry(this, key, listener);
+}
--- a/netwerk/cache/nsICacheListener.idl
+++ b/netwerk/cache/nsICacheListener.idl
@@ -42,20 +42,27 @@
 
 
 #include "nsISupports.idl"
 #include "nsICache.idl"
 
 
 interface nsICacheEntryDescriptor;
 
-[scriptable, uuid(638c3848-778b-4851-8ff3-9400f65b8773)]
+[scriptable, uuid(8eadf2ed-8cac-4961-8025-6da6d5827e74)]
 interface nsICacheListener : nsISupports
 {
     /**
      * Called when the requested access (or appropriate subset) is
      * acquired.  The status parameter equals NS_OK on success.
      * See nsICacheService.idl for accessGranted values.
      */
     void onCacheEntryAvailable(in nsICacheEntryDescriptor descriptor,
                                in nsCacheAccessMode       accessGranted,
                                in nsresult                status);
+
+    /**
+     * Called when nsCacheSession::DoomEntry() is completed. The status
+     * parameter is NS_OK when the entry was doomed, or NS_ERROR_NOT_AVAILABLE
+     * when there is no such entry.
+     */
+    void onCacheEntryDoomed(in nsresult status);
 };
--- a/netwerk/cache/nsICacheSession.idl
+++ b/netwerk/cache/nsICacheSession.idl
@@ -41,17 +41,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 #include "nsICache.idl"
 
 interface nsICacheEntryDescriptor;
 interface nsICacheListener;
 
-[scriptable, uuid(2ec1026f-e3a5-481c-908e-8d559235b721)]
+[scriptable, uuid(1dd7708c-de48-4ffe-b5aa-cd218c762887)]
 interface nsICacheSession : nsISupports
 {
     /**
      * Expired entries will be doomed or evicted if this attribute is set to
      * true.  If false, expired entries will be returned (useful for offline-
      * mode and clients, such as HTTP, that can update the valid lifetime of
      * cached content).  This attribute defaults to true.
      */
@@ -93,9 +93,16 @@ interface nsICacheSession : nsISupports
      */
     void evictEntries();
     
     /**
      * Return whether any of the cache devices implied by the session storage policy
      * are currently enabled for instantiation if they don't already exist.
      */
     boolean isStorageEnabled();
+
+    /**
+     * Asynchronously doom an entry specified by the key. Listener will be
+     * notified about the status of the operation. Null may be passed if caller
+     * doesn't care about the result.
+     */
+    void doomEntry(in ACString key, in nsICacheListener listener);
 };
--- a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
@@ -2091,16 +2091,24 @@ nsFtpState::OnCacheEntryAvailable(nsICac
 
     Connect();
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
+nsFtpState::OnCacheEntryDoomed(nsresult status)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
 nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context)
 {
     mStorReplyReceived = false;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context,
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4886,16 +4886,22 @@ nsHttpChannel::OnCacheEntryAvailableInte
         // check result of OnOfflineCacheEntryForWritingAvailable()
         if (NS_FAILED(rv))
             return rv;
     }
 
     return Connect(false);
 }
 
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryDoomed(nsresult status)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 nsresult
 nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
 {
     LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
 
     NS_ASSERTION(!mTransaction, "should not have a transaction");
     nsresult rv;
 
@@ -5204,29 +5210,17 @@ nsHttpChannel::DoInvalidateCacheEntry(ns
     nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
 
     nsresult rv = gHttpHandler->GetCacheSession(storagePolicy,
                                                 getter_AddRefs(session));
 
     if (NS_FAILED(rv))
         return;
 
-    // Now, find the actual cache-entry
-    nsCOMPtr<nsICacheEntryDescriptor> tmpCacheEntry;
-    rv = session->OpenCacheEntry(key, nsICache::ACCESS_READ,
-                                 false,
-                                 getter_AddRefs(tmpCacheEntry));
-
-    // If entry was found, set its expiration-time = 0
-    if(NS_SUCCEEDED(rv)) {
-        tmpCacheEntry->SetExpirationTime(0);
-        LOG(("  cache-entry invalidated [key=%s]\n", key.Data()));
-    } else {
-        LOG(("  cache-entry not found [key=%s]\n", key.Data()));
-    }
+    session->DoomEntry(key, nsnull);
 }
 
 nsCacheStoragePolicy
 nsHttpChannel::DetermineStoragePolicy()
 {
     nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
     if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
         policy = nsICache::STORE_IN_MEMORY;
--- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -607,16 +607,22 @@ nsWyciwygChannel::OnCacheEntryAvailable(
     CloseCacheEntry(rv);
 
     NotifyListener();
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsWyciwygChannel::OnCacheEntryDoomed(nsresult status)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 //-----------------------------------------------------------------------------
 // nsWyciwygChannel::nsIStreamListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsWyciwygChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
                                   nsIInputStream *input,
                                   PRUint32 offset, PRUint32 count)
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_doomentry.js
@@ -0,0 +1,170 @@
+/**
+ * Test for nsICacheSession.doomEntry().
+ * It tests dooming
+ *   - an existent inactive entry
+ *   - a non-existent inactive entry
+ *   - an existent active entry
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+var _CSvc;
+function get_cache_service() {
+  if (_CSvc)
+    return _CSvc;
+
+  return _CSvc = Cc["@mozilla.org/network/cache-service;1"].
+                 getService(Ci.nsICacheService);
+}
+
+function GetOutputStreamForEntry(key, asFile, append, callback)
+{
+  this._key = key;
+  this._asFile = asFile;
+  this._append = append;
+  this._callback = callback;
+  this.run();
+}
+
+GetOutputStreamForEntry.prototype = {
+  _key: "",
+  _asFile: false,
+  _append: false,
+  _callback: null,
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsICacheListener) ||
+        iid.equals(Ci.nsISupports))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  onCacheEntryAvailable: function (entry, access, status) {
+    if (!entry)
+      do_throw("entry not available");
+
+    var ostream = entry.openOutputStream(this._append ? entry.dataSize : 0);
+    this._callback(entry, ostream);
+  },
+
+  run: function() {
+    var cache = get_cache_service();
+    var session = cache.createSession(
+                    "HTTP",
+                    this._asFile ? Ci.nsICache.STORE_ON_DISK_AS_FILE
+                                 : Ci.nsICache.STORE_ON_DISK,
+                    Ci.nsICache.STREAM_BASED);
+    var cacheEntry = session.asyncOpenCacheEntry(
+                       this._key,
+                       this._append ? Ci.nsICache.ACCESS_READ_WRITE
+                                    : Ci.nsICache.ACCESS_WRITE,
+                       this);
+  }
+};
+
+function DoomEntry(key, callback) {
+  this._key = key;
+  this._callback = callback;
+  this.run();
+}
+
+DoomEntry.prototype = {
+  _key: "",
+  _callback: null,
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsICacheListener) ||
+        iid.equals(Ci.nsISupports))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  onCacheEntryDoomed: function (status) {
+    this._callback(status);
+  },
+
+  run: function() {
+    get_cache_service()
+      .createSession("HTTP",
+                     Ci.nsICache.STORE_ANYWHERE,
+                     Ci.nsICache.STREAM_BASED)
+      .doomEntry(this._key, this);
+  }
+};
+
+function write_and_check(str, data, len)
+{
+  var written = str.write(data, len);
+  if (written != len) {
+    do_throw("str.write has not written all data!\n" +
+             "  Expected: " + len  + "\n" +
+             "  Actual: " + written + "\n");
+  }
+}
+
+function write_entry()
+{
+  new GetOutputStreamForEntry("testentry", true, false, write_entry_cont);
+}
+
+function write_entry_cont(entry, ostream)
+{
+  var data = "testdata";
+  write_and_check(ostream, data, data.length);
+  ostream.close();
+  entry.close();
+  new DoomEntry("testentry", check_doom1);
+}
+
+function check_doom1(status)
+{
+  do_check_eq(status, Cr.NS_OK);
+  new DoomEntry("nonexistententry", check_doom2);
+}
+
+function check_doom2(status)
+{
+  do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE);
+  new GetOutputStreamForEntry("testentry", true, false, write_entry2);
+}
+
+var gEntry;
+var gOstream;
+function write_entry2(entry, ostream)
+{
+  // write some data and doom the entry while it is active
+  var data = "testdata";
+  write_and_check(ostream, data, data.length);
+  gEntry = entry;
+  gOstream = ostream;
+  new DoomEntry("testentry", check_doom3);
+}
+
+function check_doom3(status)
+{
+  do_check_eq(status, Cr.NS_OK);
+  // entry was doomed but writing should still succeed
+  var data = "testdata";
+  write_and_check(gOstream, data, data.length);
+  gEntry.close();
+  gOstream.close();
+  // dooming the same entry again should fail
+  new DoomEntry("testentry", check_doom4);
+}
+
+function check_doom4(status)
+{
+  do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE);
+  do_test_finished();
+}
+
+function run_test() {
+  do_get_profile();
+
+  // clear the cache
+  get_cache_service().evictEntries(Ci.nsICache.STORE_ANYWHERE);
+  write_entry();
+  do_test_pending();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -78,16 +78,17 @@ fail-if = os == "android"
 [test_bug654926_test_seek.js]
 # Bug 675049: test fails consistently on Android
 fail-if = os == "android"  
 [test_bug659569.js]
 [test_bug660066.js]
 [test_bug667907.js]
 [test_bug667818.js]
 [test_bug669001.js]
+[test_doomentry.js]
 [test_cacheflags.js]
 [test_channel_close.js]
 [test_compareURIs.js]
 [test_compressappend.js]
 [test_content_encoding_gzip.js]
 [test_content_sniffer.js]
 [test_cookie_header.js]
 [test_data_protocol.js]