Bug 722033 - Invalidate cache entry in nsHttpChannel::DoInvalidateCacheEntry() asynchronously
authorMichal Novotny <michal.novotny@gmail.com>
Thu, 22 Mar 2012 23:54:20 +0100
changeset 91901 835ac64631ec9c647c7ab93861bb9b9cce2313c9
parent 91900 5b61e3d75735e331ac401f5f7b057c9997d03aa0
child 91902 add2ed43cc05d2531b351c1f1f22b16e97279204
push idunknown
push userunknown
push dateunknown
bugs722033
milestone14.0a1
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]