From 84ddddcba8c8ac6d33b1acc2f6432ea1b0564e5d Mon Sep 17 00:00:00 2001 From: Sinker Li Date: Fri, 30 Mar 2012 20:52:07 -0400 Subject: [PATCH] Bug 674728 - Part 3: Evict cache asynchronized. r=honzab --- netwerk/base/public/nsIApplicationCache.idl | 22 ++ netwerk/cache/nsDiskCacheDeviceSQL.cpp | 324 ++++++++++++++++---- netwerk/cache/nsDiskCacheDeviceSQL.h | 10 + uriloader/prefetch/nsOfflineCacheUpdate.cpp | 67 ++-- uriloader/prefetch/nsOfflineCacheUpdate.h | 4 +- 5 files changed, 338 insertions(+), 89 deletions(-) diff --git a/netwerk/base/public/nsIApplicationCache.idl b/netwerk/base/public/nsIApplicationCache.idl index bdadddc627a4..5e2606f2dea2 100644 --- a/netwerk/base/public/nsIApplicationCache.idl +++ b/netwerk/base/public/nsIApplicationCache.idl @@ -97,6 +97,23 @@ interface nsIApplicationCacheNamespace : nsISupports 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 @@ -187,6 +204,11 @@ interface nsIApplicationCache : nsISupports */ void discard(); + /** + * Discard this application cache in asynchronized. + */ + void discardAsync([optional] in nsIApplicationCacheAsyncCallback aCallback); + /** * Adds item types to a given entry. */ diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/nsDiskCacheDeviceSQL.cpp index 323164a2e471..ee26d9584ac9 100644 --- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp +++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp @@ -59,8 +59,10 @@ #include "nsIVariant.h" #include "nsThreadUtils.h" +#include "mozIStoragePendingStatement.h" #include "mozIStorageService.h" #include "mozIStorageStatement.h" +#include "mozIStorageStatementCallback.h" #include "mozIStorageFunction.h" #include "mozStorageHelper.h" @@ -133,6 +135,11 @@ class EvictionObserver 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" @@ -144,6 +151,11 @@ class EvictionObserver ~EvictionObserver() { + if (mEvictionFunction->RemoveObserver() != 0) { + // not last observer + return; + } + mDB->ExecuteSimpleSQL( NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;")); mEvictionFunction->Reset(); @@ -174,6 +186,157 @@ DCacheHash(const char * key) 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 mDB; + nsCOMPtr mCallback; + nsRefPtr 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 statement; + rv = EvictEntriesSteps(mDB, mClientID, mStep++, getter_AddRefs(statement)); + if (NS_FAILED(rv)) { + ReportError(); + return NS_OK; + } + + if (statement) { + nsCOMPtr 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 &aItems) : + mItems(aItems) {} + ~RemoveFilesAsync(); + + NS_IMETHOD Run(); + +private: + nsCOMArray mItems; + nsCOMPtr 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 */ @@ -241,16 +404,17 @@ 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 + if (!mIOThread) { + nsresult rv; - mItems[i]->Remove(false); + rv = NS_NewThread(getter_AddRefs(mIOThread)); + NS_ASSERTION(NS_SUCCEEDED(rv), "fail to create a new thread"); } + nsCOMPtr removeFiles = new RemoveFilesAsync(mItems); + NS_ASSERTION(removeFiles, "fail to instantiate RemoveFilesAsync"); + mIOThread->Dispatch(removeFiles, NS_DISPATCH_NORMAL); + Reset(); } @@ -699,6 +863,22 @@ nsApplicationCache::Discard() 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) @@ -1732,82 +1912,102 @@ nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor) 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 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 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); + while (1) { + rv = EvictEntriesSteps(mDB, clientID, step++, getter_AddRefs(statement)); + if (NS_FAILED(rv)) break; - rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID)); - NS_ENSURE_SUCCESS(rv, rv); + if (!statement) break; // finish - 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); + 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); + return 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); - } +nsresult +nsOfflineCacheDevice::EvictEntriesAsync(const char *clientID, + nsIApplicationCacheAsyncCallback *aCallback) +{ + LOG(("nsOfflineCacheDevice::EvictEntriesAsync [cid=%s]\n", + clientID ? clientID : "")); - rv = statement->Execute(); - NS_ENSURE_SUCCESS(rv, rv); + EvictAsyncHandler *evictAsyncHandler = new EvictAsyncHandler(); + if (evictAsyncHandler == NULL) + return NS_ERROR_OUT_OF_MEMORY; + evictAsyncHandler->Init(clientID, aCallback, mDB, mEvictionFunction); + evictAsyncHandler->Start(); return NS_OK; } diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.h b/netwerk/cache/nsDiskCacheDeviceSQL.h index 6fee2c91b792..8d007d75e5e1 100644 --- a/netwerk/cache/nsDiskCacheDeviceSQL.h +++ b/netwerk/cache/nsDiskCacheDeviceSQL.h @@ -77,14 +77,21 @@ public: 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 mItems; + nsCOMPtr mIOThread; + + int mObserverCount; }; @@ -131,6 +138,9 @@ public: virtual nsresult EvictEntries(const char * clientID); + virtual nsresult EvictEntriesAsync(const char * clientID, + nsIApplicationCacheAsyncCallback *aCallback); + /* Entry ownership */ nsresult GetOwnerDomains(const char * clientID, PRUint32 * count, diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp index c38f8c3f3022..fc5704a23c83 100644 --- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp +++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp @@ -1125,9 +1125,10 @@ nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest, // nsOfflineCacheUpdate::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_ISUPPORTS2(nsOfflineCacheUpdate, +NS_IMPL_ISUPPORTS3(nsOfflineCacheUpdate, nsIOfflineCacheUpdateObserver, - nsIOfflineCacheUpdate) + nsIOfflineCacheUpdate, + nsIApplicationCacheAsyncCallback) //----------------------------------------------------------------------------- // nsOfflineCacheUpdate @@ -1447,26 +1448,15 @@ nsOfflineCacheUpdate::LoadCompleted() 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_FAILED(rv)) { - mSucceeded = false; - NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); - Finish(); - return; - } + rv = item->Cancel(); + if (NS_SUCCEEDED(rv)) { mPinnedEntryRetriesCount++; - // Retry current item, so mCurrentItem is not advanced. - ProcessNextURI(); - return; + // Do a retrying for current item, so mCurrentItem is not advanced. + rv = EvictOneNonPinnedAsync(); + } + + if (NS_SUCCEEDED(rv)) return; } } @@ -1891,7 +1881,8 @@ nsOfflineCacheUpdate::Finish() static nsresult EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService, - PRUint32 count, const char * const *groups) + PRUint32 count, const char * const *groups, + nsIApplicationCacheAsyncCallback *aCallback) { { nsresult rv; unsigned int i; @@ -1915,16 +1906,24 @@ EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService, 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; @@ -1937,7 +1936,7 @@ nsOfflineCacheUpdate::EvictOneNonPinned() 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; @@ -2159,3 +2158,19 @@ nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *application { 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(); +} diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.h b/uriloader/prefetch/nsOfflineCacheUpdate.h index 2d1b7b62ef06..5990ed88e5b6 100644 --- a/uriloader/prefetch/nsOfflineCacheUpdate.h +++ b/uriloader/prefetch/nsOfflineCacheUpdate.h @@ -213,11 +213,13 @@ public: 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(); @@ -258,7 +260,7 @@ private: nsresult FinishNoNotify(); // Find one non-pinned cache group and evict it. - nsresult EvictOneNonPinned(); + nsresult EvictOneNonPinnedAsync(); enum { STATE_UNINITIALIZED,