From c8537395fdf075b36684c54937e7c73836e1f4af Mon Sep 17 00:00:00 2001 From: Michal Novotny Date: Thu, 22 Mar 2012 23:54:20 +0100 Subject: [PATCH] Bug 722033 - Invalidate cache entry in nsHttpChannel::DoInvalidateCacheEntry() asynchronously --- netwerk/cache/nsCacheService.cpp | 101 +++++++++++ netwerk/cache/nsCacheService.h | 5 + netwerk/cache/nsCacheSession.cpp | 6 + netwerk/cache/nsICacheListener.idl | 9 +- netwerk/cache/nsICacheSession.idl | 9 +- .../protocol/ftp/nsFtpConnectionThread.cpp | 8 + netwerk/protocol/http/nsHttpChannel.cpp | 20 +-- netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp | 6 + netwerk/test/unit/test_doomentry.js | 170 ++++++++++++++++++ netwerk/test/unit/xpcshell.ini | 1 + 10 files changed, 320 insertions(+), 15 deletions(-) create mode 100644 netwerk/test/unit/test_doomentry.js diff --git a/netwerk/cache/nsCacheService.cpp b/netwerk/cache/nsCacheService.cpp index a9b2f5b2c27..cf2509a2ca2 100644 --- a/netwerk/cache/nsCacheService.cpp +++ b/netwerk/cache/nsCacheService.cpp @@ -1034,6 +1034,91 @@ 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 mThread; +}; + /****************************************************************************** * nsCacheService *****************************************************************************/ @@ -1350,6 +1435,22 @@ nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy, } +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) { diff --git a/netwerk/cache/nsCacheService.h b/netwerk/cache/nsCacheService.h index 77102eca844..3d141fbad8b 100644 --- a/netwerk/cache/nsCacheService.h +++ b/netwerk/cache/nsCacheService.h @@ -99,6 +99,10 @@ public: static nsresult IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy, bool * result); + static nsresult DoomEntry(nsCacheSession *session, + const nsACString &key, + nsICacheListener *listener); + /** * Methods called by nsCacheEntryDescriptor */ @@ -198,6 +202,7 @@ private: friend class nsSetSmartSizeEvent; friend class nsBlockOnCacheThreadEvent; friend class nsSetDiskSmartSizeCallback; + friend class nsDoomEvent; /** * Internal Methods diff --git a/netwerk/cache/nsCacheSession.cpp b/netwerk/cache/nsCacheSession.cpp index 4cb5819927c..fb4e3b0936b 100644 --- a/netwerk/cache/nsCacheSession.cpp +++ b/netwerk/cache/nsCacheSession.cpp @@ -128,3 +128,9 @@ 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); +} diff --git a/netwerk/cache/nsICacheListener.idl b/netwerk/cache/nsICacheListener.idl index 7d8c60398f2..d22469e25e7 100644 --- a/netwerk/cache/nsICacheListener.idl +++ b/netwerk/cache/nsICacheListener.idl @@ -47,7 +47,7 @@ interface nsICacheEntryDescriptor; -[scriptable, uuid(638c3848-778b-4851-8ff3-9400f65b8773)] +[scriptable, uuid(8eadf2ed-8cac-4961-8025-6da6d5827e74)] interface nsICacheListener : nsISupports { /** @@ -58,4 +58,11 @@ interface nsICacheListener : nsISupports 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); }; diff --git a/netwerk/cache/nsICacheSession.idl b/netwerk/cache/nsICacheSession.idl index 9bf14b03b51..d49f23c9ddd 100644 --- a/netwerk/cache/nsICacheSession.idl +++ b/netwerk/cache/nsICacheSession.idl @@ -46,7 +46,7 @@ interface nsICacheEntryDescriptor; interface nsICacheListener; -[scriptable, uuid(2ec1026f-e3a5-481c-908e-8d559235b721)] +[scriptable, uuid(1dd7708c-de48-4ffe-b5aa-cd218c762887)] interface nsICacheSession : nsISupports { /** @@ -98,4 +98,11 @@ interface nsICacheSession : nsISupports * 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); }; diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp index dca28ad3425..32dde15ef03 100644 --- a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp @@ -2095,6 +2095,14 @@ nsFtpState::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, //----------------------------------------------------------------------------- +NS_IMETHODIMP +nsFtpState::OnCacheEntryDoomed(nsresult status) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//----------------------------------------------------------------------------- + NS_IMETHODIMP nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context) { diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index cb88d6b6d16..2689fa290c0 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -4891,6 +4891,12 @@ nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntryDescriptor *entry, return Connect(false); } +NS_IMETHODIMP +nsHttpChannel::OnCacheEntryDoomed(nsresult status) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + nsresult nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn) { @@ -5209,19 +5215,7 @@ nsHttpChannel::DoInvalidateCacheEntry(nsACString &key) if (NS_FAILED(rv)) return; - // Now, find the actual cache-entry - nsCOMPtr 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 diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp index a62805c85cd..0f88b980d14 100644 --- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp +++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp @@ -612,6 +612,12 @@ nsWyciwygChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor * aCacheEntry, n return NS_OK; } +NS_IMETHODIMP +nsWyciwygChannel::OnCacheEntryDoomed(nsresult status) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + //----------------------------------------------------------------------------- // nsWyciwygChannel::nsIStreamListener //----------------------------------------------------------------------------- diff --git a/netwerk/test/unit/test_doomentry.js b/netwerk/test/unit/test_doomentry.js new file mode 100644 index 00000000000..941881c304e --- /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(); +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 9d61663e63a..f2f6e7e2eaa 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -83,6 +83,7 @@ fail-if = os == "android" [test_bug667907.js] [test_bug667818.js] [test_bug669001.js] +[test_doomentry.js] [test_cacheflags.js] [test_channel_close.js] [test_compareURIs.js]