зеркало из https://github.com/mozilla/gecko-dev.git
1911 строки
60 KiB
C++
1911 строки
60 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsCacheService.h"
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/FileUtils.h"
|
|
|
|
#include "nsCache.h"
|
|
#include "nsCacheRequest.h"
|
|
#include "nsCacheEntry.h"
|
|
#include "nsCacheEntryDescriptor.h"
|
|
#include "nsCacheDevice.h"
|
|
#include "nsICacheVisitor.h"
|
|
#include "nsDiskCacheDeviceSQL.h"
|
|
#include "nsCacheUtils.h"
|
|
#include "../cache2/CacheObserver.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIOService.h"
|
|
#include "nsDirectoryServiceDefs.h"
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsDeleteDir.h"
|
|
#include "nsNetCID.h"
|
|
#include <math.h> // for log()
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPrefs_browser.h"
|
|
|
|
#include "mozilla/net/NeckoCommon.h"
|
|
#include <algorithm>
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::net;
|
|
|
|
/******************************************************************************
|
|
* nsCacheProfilePrefObserver
|
|
*****************************************************************************/
|
|
#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
|
|
#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
|
|
#define OFFLINE_CACHE_CAPACITY 512000
|
|
|
|
#define CACHE_COMPRESSION_LEVEL 1
|
|
|
|
static const char* observerList[] = {
|
|
"profile-before-change", "profile-do-change",
|
|
NS_XPCOM_SHUTDOWN_OBSERVER_ID, "last-pb-context-exited",
|
|
"suspend_process_notification", "resume_process_notification"};
|
|
|
|
class nsCacheProfilePrefObserver : public nsIObserver {
|
|
virtual ~nsCacheProfilePrefObserver() = default;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
nsCacheProfilePrefObserver()
|
|
: mHaveProfile(false),
|
|
mOfflineCacheEnabled(false),
|
|
mOfflineStorageCacheEnabled(false),
|
|
mOfflineCacheCapacity(0),
|
|
mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL),
|
|
mSanitizeOnShutdown(false),
|
|
mClearCacheOnShutdown(false) {}
|
|
|
|
nsresult Install();
|
|
void Remove();
|
|
nsresult ReadPrefs(nsIPrefBranch* branch);
|
|
|
|
nsIFile* DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
|
|
|
|
bool OfflineCacheEnabled();
|
|
int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
|
|
nsIFile* OfflineCacheParentDirectory() {
|
|
return mOfflineCacheParentDirectory;
|
|
}
|
|
|
|
int32_t CacheCompressionLevel();
|
|
|
|
bool SanitizeAtShutdown() {
|
|
return mSanitizeOnShutdown && mClearCacheOnShutdown;
|
|
}
|
|
|
|
private:
|
|
bool mHaveProfile;
|
|
|
|
nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
|
|
|
|
bool mOfflineCacheEnabled;
|
|
bool mOfflineStorageCacheEnabled;
|
|
int32_t mOfflineCacheCapacity; // in kilobytes
|
|
nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
|
|
|
|
int32_t mCacheCompressionLevel;
|
|
|
|
bool mSanitizeOnShutdown;
|
|
bool mClearCacheOnShutdown;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
|
|
|
|
class nsBlockOnCacheThreadEvent : public Runnable {
|
|
public:
|
|
nsBlockOnCacheThreadEvent()
|
|
: mozilla::Runnable("nsBlockOnCacheThreadEvent") {}
|
|
NS_IMETHOD Run() override {
|
|
nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
|
|
CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
|
|
nsCacheService::gService->mNotified = true;
|
|
nsCacheService::gService->mCondVar.Notify();
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
nsresult nsCacheProfilePrefObserver::Install() {
|
|
// install profile-change observer
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (!observerService) return NS_ERROR_FAILURE;
|
|
|
|
nsresult rv, rv2 = NS_OK;
|
|
for (auto& observer : observerList) {
|
|
rv = observerService->AddObserver(this, observer, false);
|
|
if (NS_FAILED(rv)) rv2 = rv;
|
|
}
|
|
|
|
// install preferences observer
|
|
nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
|
if (!branch) return NS_ERROR_FAILURE;
|
|
|
|
// Determine if we have a profile already
|
|
// Install() is called *after* the profile-after-change notification
|
|
// when there is only a single profile, or it is specified on the
|
|
// commandline at startup.
|
|
// In that case, we detect the presence of a profile by the existence
|
|
// of the NS_APP_USER_PROFILE_50_DIR directory.
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
|
getter_AddRefs(directory));
|
|
if (NS_SUCCEEDED(rv)) mHaveProfile = true;
|
|
|
|
rv = ReadPrefs(branch);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return rv2;
|
|
}
|
|
|
|
void nsCacheProfilePrefObserver::Remove() {
|
|
// remove Observer Service observers
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
for (auto& observer : observerList) {
|
|
obs->RemoveObserver(this, observer);
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheProfilePrefObserver::Observe(nsISupports* subject, const char* topic,
|
|
const char16_t* data_unicode) {
|
|
NS_ConvertUTF16toUTF8 data(data_unicode);
|
|
CACHE_LOG_INFO(("Observe [topic=%s data=%s]\n", topic, data.get()));
|
|
|
|
if (!nsCacheService::IsInitialized()) {
|
|
if (!strcmp("resume_process_notification", topic)) {
|
|
// A suspended process has a closed cache, so re-open it here.
|
|
nsCacheService::GlobalInstance()->Init();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
|
|
// xpcom going away, shutdown cache service
|
|
nsCacheService::GlobalInstance()->Shutdown();
|
|
} else if (!strcmp("profile-before-change", topic)) {
|
|
// profile before change
|
|
mHaveProfile = false;
|
|
|
|
// XXX shutdown devices
|
|
nsCacheService::OnProfileShutdown();
|
|
} else if (!strcmp("suspend_process_notification", topic)) {
|
|
// A suspended process may never return, so shutdown the cache to reduce
|
|
// cache corruption.
|
|
nsCacheService::GlobalInstance()->Shutdown();
|
|
} else if (!strcmp("profile-do-change", topic)) {
|
|
// profile after change
|
|
mHaveProfile = true;
|
|
nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
|
if (!branch) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
(void)ReadPrefs(branch);
|
|
nsCacheService::OnProfileChanged();
|
|
|
|
} else if (!strcmp("last-pb-context-exited", topic)) {
|
|
nsCacheService::LeavePrivateBrowsing();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
static already_AddRefed<nsIFile> GetCacheDirectory(const char* aSubdir,
|
|
bool aAllowProcDirCache) {
|
|
nsCOMPtr<nsIFile> directory;
|
|
|
|
// try to get the disk cache parent directory
|
|
Unused << NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
|
|
getter_AddRefs(directory));
|
|
if (!directory) {
|
|
// try to get the profile directory (there may not be a profile yet)
|
|
nsCOMPtr<nsIFile> profDir;
|
|
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
|
|
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
|
|
getter_AddRefs(directory));
|
|
if (!directory)
|
|
directory = profDir;
|
|
else if (profDir) {
|
|
nsCacheService::MoveOrRemoveDiskCache(profDir, directory, aSubdir);
|
|
}
|
|
}
|
|
if (!directory && aAllowProcDirCache) {
|
|
Unused << NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
|
|
getter_AddRefs(directory));
|
|
}
|
|
return directory.forget();
|
|
}
|
|
|
|
nsresult nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch) {
|
|
if (!mDiskCacheParentDirectory) {
|
|
// use file cache in build tree only if asked, to avoid cache dir litter
|
|
bool allowProcDirCache = PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE");
|
|
mDiskCacheParentDirectory = GetCacheDirectory("Cache", allowProcDirCache);
|
|
}
|
|
|
|
// read offline cache device prefs
|
|
mOfflineCacheEnabled = StaticPrefs::browser_cache_offline_enable();
|
|
mOfflineStorageCacheEnabled =
|
|
StaticPrefs::browser_cache_offline_storage_enable();
|
|
|
|
mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
|
|
(void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &mOfflineCacheCapacity);
|
|
mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
|
|
|
|
(void)branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
|
|
NS_GET_IID(nsIFile),
|
|
getter_AddRefs(mOfflineCacheParentDirectory));
|
|
|
|
if (!mOfflineCacheParentDirectory) {
|
|
#ifdef DEBUG
|
|
bool allowProcDirCache = true;
|
|
#else
|
|
bool allowProcDirCache = false;
|
|
#endif
|
|
mOfflineCacheParentDirectory =
|
|
GetCacheDirectory("OfflineCache", allowProcDirCache);
|
|
}
|
|
|
|
if (!mDiskCacheParentDirectory || !mOfflineCacheParentDirectory) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!mOfflineStorageCacheEnabled) {
|
|
// Dispatch cleanup task
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
NS_NewRunnableFunction("Delete OfflineCache", []() {
|
|
nsCOMPtr<nsIFile> dir;
|
|
nsCacheService::GetAppCacheDirectory(getter_AddRefs(dir));
|
|
bool exists = false;
|
|
if (dir && NS_SUCCEEDED(dir->Exists(&exists)) && exists) {
|
|
// Delay delete by 1 minute to avoid IO thrash on startup.
|
|
CACHE_LOG_INFO(
|
|
("Queuing Delete of AppCacheDirectory in 60 seconds"));
|
|
nsDeleteDir::DeleteDir(dir, false, 60000);
|
|
}
|
|
});
|
|
Unused << nsCacheService::DispatchToCacheIOThread(runnable);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheService::DispatchToCacheIOThread(nsIRunnable* event) {
|
|
if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
|
|
return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
nsresult nsCacheService::SyncWithCacheIOThread() {
|
|
if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
|
|
gService->mLock.AssertCurrentThreadOwns();
|
|
|
|
nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
|
|
|
|
// dispatch event - it will notify the monitor when it's done
|
|
nsresult rv = gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Failed dispatching block-event");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// wait until notified, then return
|
|
gService->mNotified = false;
|
|
while (!gService->mNotified) {
|
|
gService->mCondVar.Wait();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsCacheProfilePrefObserver::OfflineCacheEnabled() {
|
|
if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
|
|
return false;
|
|
|
|
return mOfflineCacheEnabled && mOfflineStorageCacheEnabled;
|
|
}
|
|
|
|
int32_t nsCacheProfilePrefObserver::CacheCompressionLevel() {
|
|
return mCacheCompressionLevel;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* nsProcessRequestEvent
|
|
*****************************************************************************/
|
|
|
|
class nsProcessRequestEvent : public Runnable {
|
|
public:
|
|
explicit nsProcessRequestEvent(nsCacheRequest* aRequest)
|
|
: mozilla::Runnable("nsProcessRequestEvent") {
|
|
mRequest = aRequest;
|
|
}
|
|
|
|
NS_IMETHOD Run() override {
|
|
nsresult rv;
|
|
|
|
NS_ASSERTION(mRequest->mListener,
|
|
"Sync OpenCacheEntry() posted to background thread!");
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
|
|
rv = nsCacheService::gService->ProcessRequest(mRequest, false, nullptr);
|
|
|
|
// Don't delete the request if it was queued
|
|
if (!(mRequest->IsBlocking() && rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
|
|
delete mRequest;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
virtual ~nsProcessRequestEvent() = default;
|
|
|
|
private:
|
|
nsCacheRequest* mRequest;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* nsDoomEvent
|
|
*****************************************************************************/
|
|
|
|
class nsDoomEvent : public Runnable {
|
|
public:
|
|
nsDoomEvent(nsCacheSession* session, const nsACString& key,
|
|
nsICacheListener* listener)
|
|
: mozilla::Runnable("nsDoomEvent") {
|
|
mKey = *session->ClientID();
|
|
mKey.Append(':');
|
|
mKey.Append(key);
|
|
mStoragePolicy = session->StoragePolicy();
|
|
mListener = listener;
|
|
mEventTarget = GetCurrentEventTarget();
|
|
// 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() override {
|
|
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) {
|
|
mEventTarget->Dispatch(new nsNotifyDoomListener(mListener, status),
|
|
NS_DISPATCH_NORMAL);
|
|
// posted event will release the reference on the correct thread
|
|
mListener = nullptr;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCString mKey;
|
|
nsCacheStoragePolicy mStoragePolicy;
|
|
nsICacheListener* mListener;
|
|
nsCOMPtr<nsIEventTarget> mEventTarget;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* nsCacheService
|
|
*****************************************************************************/
|
|
nsCacheService* nsCacheService::gService = nullptr;
|
|
|
|
NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal)
|
|
|
|
nsCacheService::nsCacheService()
|
|
: mObserver(nullptr),
|
|
mLock("nsCacheService.mLock"),
|
|
mCondVar(mLock, "nsCacheService.mCondVar"),
|
|
mNotified(false),
|
|
mTimeStampLock("nsCacheService.mTimeStampLock"),
|
|
mInitialized(false),
|
|
mClearingEntries(false),
|
|
mEnableOfflineDevice(false),
|
|
mOfflineDevice(nullptr),
|
|
mDoomedEntries{},
|
|
mTotalEntries(0),
|
|
mCacheHits(0),
|
|
mCacheMisses(0),
|
|
mMaxKeyLength(0),
|
|
mMaxDataSize(0),
|
|
mMaxMetaSize(0),
|
|
mDeactivateFailures(0),
|
|
mDeactivatedUnboundEntries(0) {
|
|
NS_ASSERTION(gService == nullptr, "multiple nsCacheService instances!");
|
|
gService = this;
|
|
|
|
// create list of cache devices
|
|
PR_INIT_CLIST(&mDoomedEntries);
|
|
}
|
|
|
|
nsCacheService::~nsCacheService() {
|
|
if (mInitialized) // Shutdown hasn't been called yet.
|
|
(void)Shutdown();
|
|
|
|
if (mObserver) {
|
|
mObserver->Remove();
|
|
NS_RELEASE(mObserver);
|
|
}
|
|
|
|
gService = nullptr;
|
|
}
|
|
|
|
nsresult nsCacheService::Init() {
|
|
// Thie method must be called on the main thread because mCacheIOThread must
|
|
// only be modified on the main thread.
|
|
if (!NS_IsMainThread()) {
|
|
NS_ERROR("nsCacheService::Init called off the main thread");
|
|
return NS_ERROR_NOT_SAME_THREAD;
|
|
}
|
|
|
|
NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
|
|
if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
|
|
|
|
if (mozilla::net::IsNeckoChild()) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = NS_NewNamedThread("Cache I/O", getter_AddRefs(mCacheIOThread));
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Can't create cache IO thread");
|
|
}
|
|
|
|
rv = nsDeleteDir::Init();
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Can't initialize nsDeleteDir");
|
|
}
|
|
|
|
// initialize hashtable for active cache entries
|
|
mActiveEntries.Init();
|
|
|
|
// create profile/preference observer
|
|
if (!mObserver) {
|
|
mObserver = new nsCacheProfilePrefObserver();
|
|
NS_ADDREF(mObserver);
|
|
mObserver->Install();
|
|
}
|
|
|
|
mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
|
|
|
|
mInitialized = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsCacheService::Shutdown() {
|
|
// This method must be called on the main thread because mCacheIOThread must
|
|
// only be modified on the main thread.
|
|
if (!NS_IsMainThread()) {
|
|
MOZ_CRASH("nsCacheService::Shutdown called off the main thread");
|
|
}
|
|
|
|
nsCOMPtr<nsIThread> cacheIOThread;
|
|
Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
|
|
|
|
bool shouldSanitize = false;
|
|
nsCOMPtr<nsIFile> parentDir;
|
|
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
|
|
NS_ASSERTION(
|
|
mInitialized,
|
|
"can't shutdown nsCacheService unless it has been initialized.");
|
|
if (!mInitialized) return;
|
|
|
|
mClearingEntries = true;
|
|
DoomActiveEntries(nullptr);
|
|
}
|
|
|
|
CloseAllStreams();
|
|
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
|
|
NS_ASSERTION(mInitialized, "Bad state");
|
|
|
|
mInitialized = false;
|
|
|
|
// Clear entries
|
|
ClearDoomList();
|
|
|
|
// Make sure to wait for any pending cache-operations before
|
|
// proceeding with destructive actions (bug #620660)
|
|
(void)SyncWithCacheIOThread();
|
|
mActiveEntries.Shutdown();
|
|
|
|
// obtain the disk cache directory in case we need to sanitize it
|
|
parentDir = mObserver->DiskCacheParentDirectory();
|
|
shouldSanitize = mObserver->SanitizeAtShutdown();
|
|
|
|
if (mOfflineDevice) mOfflineDevice->Shutdown();
|
|
|
|
NS_IF_RELEASE(mOfflineDevice);
|
|
|
|
for (auto iter = mCustomOfflineDevices.Iter(); !iter.Done(); iter.Next()) {
|
|
iter.Data()->Shutdown();
|
|
iter.Remove();
|
|
}
|
|
|
|
LogCacheStatistics();
|
|
|
|
mClearingEntries = false;
|
|
mCacheIOThread.swap(cacheIOThread);
|
|
}
|
|
|
|
if (cacheIOThread) nsShutdownThread::BlockingShutdown(cacheIOThread);
|
|
|
|
if (shouldSanitize) {
|
|
nsresult rv = parentDir->AppendNative("Cache"_ns);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
bool exists;
|
|
if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
|
|
nsDeleteDir::DeleteDir(parentDir, false);
|
|
}
|
|
Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE>
|
|
timer;
|
|
nsDeleteDir::Shutdown(shouldSanitize);
|
|
} else {
|
|
Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN>
|
|
timer;
|
|
nsDeleteDir::Shutdown(shouldSanitize);
|
|
}
|
|
}
|
|
|
|
nsresult nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID,
|
|
void** aResult) {
|
|
nsresult rv;
|
|
|
|
if (aOuter != nullptr) return NS_ERROR_NO_AGGREGATION;
|
|
|
|
RefPtr<nsCacheService> cacheService = new nsCacheService();
|
|
rv = cacheService->Init();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = cacheService->QueryInterface(aIID, aResult);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheService::CreateSession(const char* clientID,
|
|
nsCacheStoragePolicy storagePolicy,
|
|
bool streamBased, nsICacheSession** result) {
|
|
*result = nullptr;
|
|
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult nsCacheService::CreateSessionInternal(
|
|
const char* clientID, nsCacheStoragePolicy storagePolicy, bool streamBased,
|
|
nsICacheSession** result) {
|
|
RefPtr<nsCacheSession> session =
|
|
new nsCacheSession(clientID, storagePolicy, streamBased);
|
|
session.forget(result);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheService::EvictEntriesForSession(nsCacheSession* session) {
|
|
NS_ASSERTION(gService, "nsCacheService::gService is null.");
|
|
return gService->EvictEntriesForClient(session->ClientID()->get(),
|
|
session->StoragePolicy());
|
|
}
|
|
|
|
namespace {
|
|
|
|
class EvictionNotifierRunnable : public Runnable {
|
|
public:
|
|
explicit EvictionNotifierRunnable(nsISupports* aSubject)
|
|
: mozilla::Runnable("EvictionNotifierRunnable"), mSubject(aSubject) {}
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
|
|
private:
|
|
nsCOMPtr<nsISupports> mSubject;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
EvictionNotifierRunnable::Run() {
|
|
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
|
|
if (obsSvc) {
|
|
obsSvc->NotifyObservers(mSubject, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
|
|
nullptr);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
nsresult nsCacheService::EvictEntriesForClient(
|
|
const char* clientID, nsCacheStoragePolicy storagePolicy) {
|
|
RefPtr<EvictionNotifierRunnable> r =
|
|
new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
|
|
NS_DispatchToMainThread(r);
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
|
|
nsresult res = NS_OK;
|
|
|
|
// Only clear the offline cache if it has been specifically asked for.
|
|
if (storagePolicy == nsICache::STORE_OFFLINE) {
|
|
if (mEnableOfflineDevice) {
|
|
nsresult rv = NS_OK;
|
|
if (!mOfflineDevice) rv = CreateOfflineDevice();
|
|
if (mOfflineDevice) rv = mOfflineDevice->EvictEntries(clientID);
|
|
if (NS_FAILED(rv)) res = rv;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult nsCacheService::IsStorageEnabledForPolicy(
|
|
nsCacheStoragePolicy storagePolicy, bool* result) {
|
|
if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
|
|
|
|
*result = nsCacheService::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()));
|
|
if (!gService || !gService->mInitialized) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
|
|
}
|
|
|
|
bool nsCacheService::IsStorageEnabledForPolicy_Locked(
|
|
nsCacheStoragePolicy storagePolicy) {
|
|
if (gService->mEnableOfflineDevice &&
|
|
storagePolicy == nsICache::STORE_OFFLINE) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor* visitor) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor* visitor) {
|
|
NS_ENSURE_ARG_POINTER(visitor);
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
|
|
|
|
if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// XXX record the fact that a visitation is in progress,
|
|
// XXX i.e. keep list of visitors in progress.
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
if (mEnableOfflineDevice) {
|
|
if (!mOfflineDevice) {
|
|
rv = CreateOfflineDevice();
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
rv = mOfflineDevice->Visit(visitor);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// XXX notify any shutdown process that visitation is complete for THIS
|
|
// visitor.
|
|
// XXX keep queue of visitors
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
|
|
if (obsvc) {
|
|
obsvc->NotifyObservers(nullptr, "network-clear-cache-stored-anywhere",
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult nsCacheService::EvictEntriesInternal(
|
|
nsCacheStoragePolicy storagePolicy) {
|
|
if (storagePolicy == nsICache::STORE_ANYWHERE) {
|
|
// if not called on main thread, dispatch the notification to the main
|
|
// thread to notify observers
|
|
if (!NS_IsMainThread()) {
|
|
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
|
|
"nsCacheService::FireClearNetworkCacheStoredAnywhereNotification",
|
|
this,
|
|
&nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
|
|
NS_DispatchToMainThread(event);
|
|
} else {
|
|
// else you're already on main thread - notify observers
|
|
FireClearNetworkCacheStoredAnywhereNotification();
|
|
}
|
|
}
|
|
return EvictEntriesForClient(nullptr, storagePolicy);
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheService::GetCacheIOTarget(
|
|
nsIEventTarget** aCacheIOTarget) {
|
|
NS_ENSURE_ARG_POINTER(aCacheIOTarget);
|
|
|
|
// Because mCacheIOThread can only be changed on the main thread, it can be
|
|
// read from the main thread without the lock. This is useful to prevent
|
|
// blocking the main thread on other cache operations.
|
|
if (!NS_IsMainThread()) {
|
|
Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
|
|
}
|
|
|
|
nsresult rv;
|
|
if (mCacheIOThread) {
|
|
*aCacheIOTarget = do_AddRef(mCacheIOThread).take();
|
|
rv = NS_OK;
|
|
} else {
|
|
*aCacheIOTarget = nullptr;
|
|
rv = NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!NS_IsMainThread()) {
|
|
Unlock();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheService::GetLockHeldTime(double* aLockHeldTime) {
|
|
MutexAutoLock lock(mTimeStampLock);
|
|
|
|
if (mLockAcquiredTimeStamp.IsNull()) {
|
|
*aLockHeldTime = 0.0;
|
|
} else {
|
|
*aLockHeldTime =
|
|
(TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Internal Methods
|
|
*/
|
|
nsresult nsCacheService::GetOfflineDevice(nsOfflineCacheDevice** aDevice) {
|
|
if (!mOfflineDevice) {
|
|
nsresult rv = CreateOfflineDevice();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
*aDevice = do_AddRef(mOfflineDevice).take();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheService::GetCustomOfflineDevice(
|
|
nsIFile* aProfileDir, int32_t aQuota, nsOfflineCacheDevice** aDevice) {
|
|
nsresult rv;
|
|
|
|
nsAutoString profilePath;
|
|
rv = aProfileDir->GetPath(profilePath);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
|
|
rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
(*aDevice)->SetAutoShutdown();
|
|
mCustomOfflineDevices.Put(profilePath, RefPtr{*aDevice});
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheService::CreateOfflineDevice() {
|
|
CACHE_LOG_INFO(("Creating default offline device"));
|
|
|
|
if (mOfflineDevice) return NS_OK;
|
|
if (!nsCacheService::IsInitialized()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return CreateCustomOfflineDevice(mObserver->OfflineCacheParentDirectory(),
|
|
mObserver->OfflineCacheCapacity(),
|
|
&mOfflineDevice);
|
|
}
|
|
|
|
nsresult nsCacheService::CreateCustomOfflineDevice(
|
|
nsIFile* aProfileDir, int32_t aQuota, nsOfflineCacheDevice** aDevice) {
|
|
NS_ENSURE_ARG(aProfileDir);
|
|
|
|
if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
|
|
CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
|
|
aProfileDir->HumanReadablePath().get(), aQuota));
|
|
}
|
|
|
|
if (!mInitialized) {
|
|
NS_WARNING("nsCacheService not initialized");
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
RefPtr<nsOfflineCacheDevice> device = new nsOfflineCacheDevice();
|
|
|
|
// set the preferences
|
|
device->SetCacheParentDirectory(aProfileDir);
|
|
device->SetCapacity(aQuota);
|
|
|
|
nsresult rv = device->InitWithSqlite(mStorageService);
|
|
if (NS_FAILED(rv)) {
|
|
CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8" PRIx32
|
|
")\n",
|
|
static_cast<uint32_t>(rv)));
|
|
CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
|
|
device = nullptr;
|
|
}
|
|
|
|
device.forget(aDevice);
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsCacheService::RemoveCustomOfflineDevice(
|
|
nsOfflineCacheDevice* aDevice) {
|
|
nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
|
|
if (!profileDir) return NS_ERROR_UNEXPECTED;
|
|
|
|
nsAutoString profilePath;
|
|
nsresult rv = profileDir->GetPath(profilePath);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mCustomOfflineDevices.Remove(profilePath);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheService::CreateRequest(nsCacheSession* session,
|
|
const nsACString& clientKey,
|
|
nsCacheAccessMode accessRequested,
|
|
bool blockingMode,
|
|
nsICacheListener* listener,
|
|
nsCacheRequest** request) {
|
|
NS_ASSERTION(request, "CreateRequest: request is null");
|
|
|
|
nsAutoCString key(*session->ClientID());
|
|
key.Append(':');
|
|
key.Append(clientKey);
|
|
|
|
if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
|
|
|
|
// create request
|
|
*request =
|
|
new nsCacheRequest(key, listener, accessRequested, blockingMode, session);
|
|
|
|
if (!listener) return NS_OK; // we're sync, we're done.
|
|
|
|
// get the request's thread
|
|
(*request)->mEventTarget = GetCurrentEventTarget();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class nsCacheListenerEvent : public Runnable {
|
|
public:
|
|
nsCacheListenerEvent(nsICacheListener* listener,
|
|
nsICacheEntryDescriptor* descriptor,
|
|
nsCacheAccessMode accessGranted, nsresult status)
|
|
: mozilla::Runnable("nsCacheListenerEvent"),
|
|
mListener(listener) // transfers reference
|
|
,
|
|
mDescriptor(descriptor) // transfers reference (may be null)
|
|
,
|
|
mAccessGranted(accessGranted),
|
|
mStatus(status) {}
|
|
|
|
NS_IMETHOD Run() override {
|
|
mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
|
|
|
|
NS_RELEASE(mListener);
|
|
NS_IF_RELEASE(mDescriptor);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
// We explicitly leak mListener or mDescriptor if Run is not called
|
|
// because otherwise we cannot guarantee that they are destroyed on
|
|
// the right thread.
|
|
|
|
nsICacheListener* mListener;
|
|
nsICacheEntryDescriptor* mDescriptor;
|
|
nsCacheAccessMode mAccessGranted;
|
|
nsresult mStatus;
|
|
};
|
|
|
|
nsresult nsCacheService::NotifyListener(nsCacheRequest* request,
|
|
nsICacheEntryDescriptor* descriptor,
|
|
nsCacheAccessMode accessGranted,
|
|
nsresult status) {
|
|
NS_ASSERTION(request->mEventTarget, "no thread set in async request!");
|
|
|
|
// Swap ownership, and release listener on target thread...
|
|
nsICacheListener* listener = request->mListener;
|
|
request->mListener = nullptr;
|
|
|
|
nsCOMPtr<nsIRunnable> ev =
|
|
new nsCacheListenerEvent(listener, descriptor, accessGranted, status);
|
|
if (!ev) {
|
|
// Better to leak listener and descriptor if we fail because we don't
|
|
// want to destroy them inside the cache service lock or on potentially
|
|
// the wrong thread.
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return request->mEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
nsresult nsCacheService::ProcessRequest(nsCacheRequest* request,
|
|
bool calledFromOpenCacheEntry,
|
|
nsICacheEntryDescriptor** result) {
|
|
// !!! must be called with mLock held !!!
|
|
nsresult rv;
|
|
nsCacheEntry* entry = nullptr;
|
|
nsCacheEntry* doomedEntry = nullptr;
|
|
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
|
|
if (result) *result = nullptr;
|
|
|
|
while (true) { // Activate entry loop
|
|
rv = ActivateEntry(request, &entry,
|
|
&doomedEntry); // get the entry for this request
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
while (true) { // Request Access loop
|
|
NS_ASSERTION(entry, "no entry in Request Access loop!");
|
|
// entry->RequestAccess queues request on entry
|
|
rv = entry->RequestAccess(request, &accessGranted);
|
|
if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
|
|
|
|
if (request->IsBlocking()) {
|
|
if (request->mListener) {
|
|
// async exits - validate, doom, or close will resume
|
|
return rv;
|
|
}
|
|
|
|
// XXX this is probably wrong...
|
|
Unlock();
|
|
rv = request->WaitForValidation();
|
|
Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
|
|
}
|
|
|
|
PR_REMOVE_AND_INIT_LINK(request);
|
|
if (NS_FAILED(rv))
|
|
break; // non-blocking mode returns WAIT_FOR_VALIDATION error
|
|
// okay, we're ready to process this request, request access again
|
|
}
|
|
if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
|
|
|
|
if (entry->IsNotInUse()) {
|
|
// this request was the last one keeping it around, so get rid of it
|
|
DeactivateEntry(entry);
|
|
}
|
|
// loop back around to look for another entry
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv) && request->mProfileDir) {
|
|
// Custom cache directory has been demanded. Preset the cache device.
|
|
if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
|
|
// Failsafe check: this is implemented only for offline cache atm.
|
|
rv = NS_ERROR_FAILURE;
|
|
} else {
|
|
RefPtr<nsOfflineCacheDevice> customCacheDevice;
|
|
rv = GetCustomOfflineDevice(request->mProfileDir, -1,
|
|
getter_AddRefs(customCacheDevice));
|
|
if (NS_SUCCEEDED(rv)) entry->SetCustomCacheDevice(customCacheDevice);
|
|
}
|
|
}
|
|
|
|
nsICacheEntryDescriptor* descriptor = nullptr;
|
|
|
|
if (NS_SUCCEEDED(rv))
|
|
rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
|
|
|
|
// If doomedEntry is set, ActivatEntry() doomed an existing entry and
|
|
// created a new one for that cache-key. However, any pending requests
|
|
// on the doomed entry were not processed and we need to do that here.
|
|
// This must be done after adding the created entry to list of active
|
|
// entries (which is done in ActivateEntry()) otherwise the hashkeys crash
|
|
// (see bug ##561313). It is also important to do this after creating a
|
|
// descriptor for this request, or some other request may end up being
|
|
// executed first for the newly created entry.
|
|
// Finally, it is worth to emphasize that if doomedEntry is set,
|
|
// ActivateEntry() created a new entry for the request, which will be
|
|
// initialized by RequestAccess() and they both should have returned NS_OK.
|
|
if (doomedEntry) {
|
|
(void)ProcessPendingRequests(doomedEntry);
|
|
if (doomedEntry->IsNotInUse()) DeactivateEntry(doomedEntry);
|
|
doomedEntry = nullptr;
|
|
}
|
|
|
|
if (request->mListener) { // Asynchronous
|
|
|
|
if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
|
|
return rv; // skip notifying listener, just return rv to caller
|
|
|
|
// call listener to report error or descriptor
|
|
nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
|
|
if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
|
|
rv = rv2; // trigger delete request
|
|
}
|
|
} else { // Synchronous
|
|
*result = descriptor;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsCacheService::OpenCacheEntry(nsCacheSession* session,
|
|
const nsACString& key,
|
|
nsCacheAccessMode accessRequested,
|
|
bool blockingMode,
|
|
nsICacheListener* listener,
|
|
nsICacheEntryDescriptor** result) {
|
|
CACHE_LOG_DEBUG(
|
|
("Opening entry for session %p, key %s, mode %d, blocking %d\n", session,
|
|
PromiseFlatCString(key).get(), accessRequested, blockingMode));
|
|
if (result) *result = nullptr;
|
|
|
|
if (!gService || !gService->mInitialized) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
nsCacheRequest* request = nullptr;
|
|
|
|
nsresult rv = gService->CreateRequest(session, key, accessRequested,
|
|
blockingMode, listener, &request);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
CACHE_LOG_DEBUG(("Created request %p\n", request));
|
|
|
|
// Process the request on the background thread if we are on the main thread
|
|
// and the the request is asynchronous
|
|
if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
|
|
nsCOMPtr<nsIRunnable> ev = new nsProcessRequestEvent(request);
|
|
rv = DispatchToCacheIOThread(ev);
|
|
|
|
// delete request if we didn't post the event
|
|
if (NS_FAILED(rv)) delete request;
|
|
} else {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
|
|
rv = gService->ProcessRequest(request, true, result);
|
|
|
|
// delete requests that have completed
|
|
if (!(listener && blockingMode &&
|
|
(rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
|
|
delete request;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsCacheService::ActivateEntry(nsCacheRequest* request,
|
|
nsCacheEntry** result,
|
|
nsCacheEntry** doomedEntry) {
|
|
CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
|
|
if (!mInitialized || mClearingEntries) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
|
|
if (result) *result = nullptr;
|
|
if (doomedEntry) *doomedEntry = nullptr;
|
|
if ((!request) || (!result) || (!doomedEntry)) return NS_ERROR_NULL_POINTER;
|
|
|
|
// check if the request can be satisfied
|
|
if (!request->IsStreamBased()) return NS_ERROR_FAILURE;
|
|
if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// search active entries (including those not bound to device)
|
|
nsCacheEntry* entry = mActiveEntries.GetEntry(&(request->mKey));
|
|
CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
|
|
|
|
if (!entry) {
|
|
// search cache devices for entry
|
|
bool collision = false;
|
|
entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(),
|
|
&collision);
|
|
CACHE_LOG_DEBUG(
|
|
("Device search for request %p returned %p\n", request, entry));
|
|
// When there is a hashkey collision just refuse to cache it...
|
|
if (collision) return NS_ERROR_CACHE_IN_USE;
|
|
|
|
if (entry) entry->MarkInitialized();
|
|
} else {
|
|
NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
|
|
}
|
|
|
|
if (entry) {
|
|
++mCacheHits;
|
|
entry->Fetched();
|
|
} else {
|
|
++mCacheMisses;
|
|
}
|
|
|
|
if (entry && ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
|
|
((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
|
|
(entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
|
|
request->WillDoomEntriesIfExpired()))))
|
|
|
|
{
|
|
// this is FORCE-WRITE request or the entry has expired
|
|
// we doom entry without processing pending requests, but store it in
|
|
// doomedEntry which causes pending requests to be processed below
|
|
rv = DoomEntry_Internal(entry, false);
|
|
*doomedEntry = entry;
|
|
if (NS_FAILED(rv)) {
|
|
// XXX what to do? Increment FailedDooms counter?
|
|
}
|
|
entry = nullptr;
|
|
}
|
|
|
|
if (!entry) {
|
|
if (!(request->AccessRequested() & nsICache::ACCESS_WRITE)) {
|
|
// this is a READ-ONLY request
|
|
rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
|
|
goto error;
|
|
}
|
|
|
|
entry = new nsCacheEntry(request->mKey, request->IsStreamBased(),
|
|
request->StoragePolicy());
|
|
if (!entry) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
if (request->IsPrivate()) entry->MarkPrivate();
|
|
|
|
entry->Fetched();
|
|
++mTotalEntries;
|
|
|
|
// XXX we could perform an early bind in some cases based on storage policy
|
|
}
|
|
|
|
if (!entry->IsActive()) {
|
|
rv = mActiveEntries.AddEntry(entry);
|
|
if (NS_FAILED(rv)) goto error;
|
|
CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
|
|
entry->MarkActive(); // mark entry active, because it's now in
|
|
// mActiveEntries
|
|
}
|
|
*result = entry;
|
|
return NS_OK;
|
|
|
|
error:
|
|
*result = nullptr;
|
|
delete entry;
|
|
return rv;
|
|
}
|
|
|
|
nsCacheEntry* nsCacheService::SearchCacheDevices(nsCString* key,
|
|
nsCacheStoragePolicy policy,
|
|
bool* collision) {
|
|
Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
|
|
nsCacheEntry* entry = nullptr;
|
|
|
|
*collision = false;
|
|
if (policy == nsICache::STORE_OFFLINE ||
|
|
(policy == nsICache::STORE_ANYWHERE && gIOService->IsOffline())) {
|
|
if (mEnableOfflineDevice) {
|
|
if (!mOfflineDevice) {
|
|
nsresult rv = CreateOfflineDevice();
|
|
if (NS_FAILED(rv)) return nullptr;
|
|
}
|
|
|
|
entry = mOfflineDevice->FindEntry(key, collision);
|
|
}
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
nsCacheDevice* nsCacheService::EnsureEntryHasDevice(nsCacheEntry* entry) {
|
|
nsCacheDevice* device = entry->CacheDevice();
|
|
// return device if found, possibly null if the entry is doomed i.e prevent
|
|
// doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
|
|
if (device || entry->IsDoomed()) return device;
|
|
|
|
if (!device && entry->IsStreamData() && entry->IsAllowedOffline() &&
|
|
mEnableOfflineDevice) {
|
|
if (!mOfflineDevice) {
|
|
(void)CreateOfflineDevice(); // ignore the error (check for
|
|
// mOfflineDevice instead)
|
|
}
|
|
|
|
device = entry->CustomCacheDevice() ? entry->CustomCacheDevice()
|
|
: mOfflineDevice;
|
|
|
|
if (device) {
|
|
entry->MarkBinding();
|
|
nsresult rv = device->BindEntry(entry);
|
|
entry->ClearBinding();
|
|
if (NS_FAILED(rv)) device = nullptr;
|
|
}
|
|
}
|
|
|
|
if (device) entry->SetCacheDevice(device);
|
|
return device;
|
|
}
|
|
|
|
nsresult nsCacheService::DoomEntry(nsCacheEntry* entry) {
|
|
return gService->DoomEntry_Internal(entry, true);
|
|
}
|
|
|
|
nsresult nsCacheService::DoomEntry_Internal(nsCacheEntry* entry,
|
|
bool doProcessPendingRequests) {
|
|
if (entry->IsDoomed()) return NS_OK;
|
|
|
|
CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
|
|
nsresult rv = NS_OK;
|
|
entry->MarkDoomed();
|
|
|
|
NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
|
|
nsCacheDevice* device = entry->CacheDevice();
|
|
if (device) device->DoomEntry(entry);
|
|
|
|
if (entry->IsActive()) {
|
|
// remove from active entries
|
|
mActiveEntries.RemoveEntry(entry);
|
|
CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
|
|
entry->MarkInactive();
|
|
}
|
|
|
|
// put on doom list to wait for descriptors to close
|
|
NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
|
|
PR_APPEND_LINK(entry, &mDoomedEntries);
|
|
|
|
// handle pending requests only if we're supposed to
|
|
if (doProcessPendingRequests) {
|
|
// tell pending requests to get on with their lives...
|
|
rv = ProcessPendingRequests(entry);
|
|
|
|
// All requests have been removed, but there may still be open descriptors
|
|
if (entry->IsNotInUse()) {
|
|
DeactivateEntry(entry); // tell device to get rid of it
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void nsCacheService::OnProfileShutdown() {
|
|
if (!gService || !gService->mInitialized) {
|
|
// The cache service has been shut down, but someone is still holding
|
|
// a reference to it. Ignore this call.
|
|
return;
|
|
}
|
|
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
|
|
gService->mClearingEntries = true;
|
|
gService->DoomActiveEntries(nullptr);
|
|
}
|
|
|
|
gService->CloseAllStreams();
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
|
|
gService->ClearDoomList();
|
|
|
|
// Make sure to wait for any pending cache-operations before
|
|
// proceeding with destructive actions (bug #620660)
|
|
(void)SyncWithCacheIOThread();
|
|
|
|
if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
|
|
gService->mOfflineDevice->Shutdown();
|
|
}
|
|
for (auto iter = gService->mCustomOfflineDevices.Iter(); !iter.Done();
|
|
iter.Next()) {
|
|
iter.Data()->Shutdown();
|
|
iter.Remove();
|
|
}
|
|
|
|
gService->mEnableOfflineDevice = false;
|
|
|
|
gService->mClearingEntries = false;
|
|
}
|
|
|
|
void nsCacheService::OnProfileChanged() {
|
|
if (!gService) return;
|
|
|
|
CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
|
|
|
|
gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
|
|
|
|
if (gService->mOfflineDevice) {
|
|
gService->mOfflineDevice->SetCacheParentDirectory(
|
|
gService->mObserver->OfflineCacheParentDirectory());
|
|
gService->mOfflineDevice->SetCapacity(
|
|
gService->mObserver->OfflineCacheCapacity());
|
|
|
|
// XXX initialization of mOfflineDevice could be made lazily, if
|
|
// mEnableOfflineDevice is false
|
|
nsresult rv =
|
|
gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
|
|
if (NS_FAILED(rv)) {
|
|
NS_ERROR(
|
|
"nsCacheService::OnProfileChanged: Re-initializing offline device "
|
|
"failed");
|
|
gService->mEnableOfflineDevice = false;
|
|
// XXX delete mOfflineDevice?
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsCacheService::SetOfflineCacheEnabled(bool enabled) {
|
|
if (!gService) return;
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
|
|
gService->mEnableOfflineDevice = enabled;
|
|
}
|
|
|
|
void nsCacheService::SetOfflineCacheCapacity(int32_t capacity) {
|
|
if (!gService) return;
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
|
|
|
|
if (gService->mOfflineDevice) {
|
|
gService->mOfflineDevice->SetCapacity(capacity);
|
|
}
|
|
|
|
gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
|
|
}
|
|
|
|
/******************************************************************************
|
|
* static methods for nsCacheEntryDescriptor
|
|
*****************************************************************************/
|
|
void nsCacheService::CloseDescriptor(nsCacheEntryDescriptor* descriptor) {
|
|
// ask entry to remove descriptor
|
|
nsCacheEntry* entry = descriptor->CacheEntry();
|
|
bool doomEntry;
|
|
bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
|
|
|
|
if (!entry->IsValid()) {
|
|
gService->ProcessPendingRequests(entry);
|
|
}
|
|
|
|
if (doomEntry) {
|
|
gService->DoomEntry_Internal(entry, true);
|
|
return;
|
|
}
|
|
|
|
if (!stillActive) {
|
|
gService->DeactivateEntry(entry);
|
|
}
|
|
}
|
|
|
|
nsresult nsCacheService::GetFileForEntry(nsCacheEntry* entry,
|
|
nsIFile** result) {
|
|
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
|
|
if (!device) return NS_ERROR_UNEXPECTED;
|
|
|
|
return device->GetFileForEntry(entry, result);
|
|
}
|
|
|
|
nsresult nsCacheService::OpenInputStreamForEntry(nsCacheEntry* entry,
|
|
nsCacheAccessMode mode,
|
|
uint32_t offset,
|
|
nsIInputStream** result) {
|
|
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
|
|
if (!device) return NS_ERROR_UNEXPECTED;
|
|
|
|
return device->OpenInputStreamForEntry(entry, mode, offset, result);
|
|
}
|
|
|
|
nsresult nsCacheService::OpenOutputStreamForEntry(nsCacheEntry* entry,
|
|
nsCacheAccessMode mode,
|
|
uint32_t offset,
|
|
nsIOutputStream** result) {
|
|
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
|
|
if (!device) return NS_ERROR_UNEXPECTED;
|
|
|
|
return device->OpenOutputStreamForEntry(entry, mode, offset, result);
|
|
}
|
|
|
|
nsresult nsCacheService::OnDataSizeChange(nsCacheEntry* entry,
|
|
int32_t deltaSize) {
|
|
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
|
|
if (!device) return NS_ERROR_UNEXPECTED;
|
|
|
|
return device->OnDataSizeChange(entry, deltaSize);
|
|
}
|
|
|
|
void nsCacheService::LockAcquired() {
|
|
MutexAutoLock lock(mTimeStampLock);
|
|
mLockAcquiredTimeStamp = TimeStamp::Now();
|
|
}
|
|
|
|
void nsCacheService::LockReleased() {
|
|
MutexAutoLock lock(mTimeStampLock);
|
|
mLockAcquiredTimeStamp = TimeStamp();
|
|
}
|
|
|
|
void nsCacheService::Lock() {
|
|
gService->mLock.Lock();
|
|
gService->LockAcquired();
|
|
}
|
|
|
|
void nsCacheService::Lock(mozilla::Telemetry::HistogramID mainThreadLockerID) {
|
|
mozilla::Telemetry::HistogramID lockerID;
|
|
mozilla::Telemetry::HistogramID generalID;
|
|
|
|
if (NS_IsMainThread()) {
|
|
lockerID = mainThreadLockerID;
|
|
generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
|
|
} else {
|
|
lockerID = mozilla::Telemetry::HistogramCount;
|
|
generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
|
|
}
|
|
|
|
TimeStamp start(TimeStamp::Now());
|
|
|
|
nsCacheService::Lock();
|
|
|
|
TimeStamp stop(TimeStamp::Now());
|
|
|
|
// Telemetry isn't thread safe on its own, but this is OK because we're
|
|
// protecting it with the cache lock.
|
|
if (lockerID != mozilla::Telemetry::HistogramCount) {
|
|
mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
|
|
}
|
|
mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
|
|
}
|
|
|
|
void nsCacheService::Unlock() {
|
|
gService->mLock.AssertCurrentThreadOwns();
|
|
|
|
nsTArray<nsISupports*> doomed = std::move(gService->mDoomedObjects);
|
|
|
|
gService->LockReleased();
|
|
gService->mLock.Unlock();
|
|
|
|
for (uint32_t i = 0; i < doomed.Length(); ++i) doomed[i]->Release();
|
|
}
|
|
|
|
void nsCacheService::ReleaseObject_Locked(nsISupports* obj,
|
|
nsIEventTarget* target) {
|
|
gService->mLock.AssertCurrentThreadOwns();
|
|
|
|
bool isCur;
|
|
if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
|
|
gService->mDoomedObjects.AppendElement(obj);
|
|
} else {
|
|
NS_ProxyRelease("nsCacheService::ReleaseObject_Locked::obj", target,
|
|
dont_AddRef(obj));
|
|
}
|
|
}
|
|
|
|
nsresult nsCacheService::SetCacheElement(nsCacheEntry* entry,
|
|
nsISupports* element) {
|
|
entry->SetData(element);
|
|
entry->TouchData();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheService::ValidateEntry(nsCacheEntry* entry) {
|
|
nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
|
|
if (!device) return NS_ERROR_UNEXPECTED;
|
|
|
|
entry->MarkValid();
|
|
nsresult rv = gService->ProcessPendingRequests(entry);
|
|
NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
|
|
// XXX what else should be done?
|
|
|
|
return rv;
|
|
}
|
|
|
|
int32_t nsCacheService::CacheCompressionLevel() {
|
|
int32_t level = gService->mObserver->CacheCompressionLevel();
|
|
return level;
|
|
}
|
|
|
|
void nsCacheService::DeactivateEntry(nsCacheEntry* entry) {
|
|
CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
|
|
nsresult rv = NS_OK;
|
|
NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
|
|
nsCacheDevice* device = nullptr;
|
|
|
|
if (mMaxDataSize < entry->DataSize()) mMaxDataSize = entry->DataSize();
|
|
if (mMaxMetaSize < entry->MetaDataSize())
|
|
mMaxMetaSize = entry->MetaDataSize();
|
|
|
|
if (entry->IsDoomed()) {
|
|
// remove from Doomed list
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
} else if (entry->IsActive()) {
|
|
// remove from active entries
|
|
mActiveEntries.RemoveEntry(entry);
|
|
CACHE_LOG_DEBUG(
|
|
("Removed deactivated entry %p from mActiveEntries\n", entry));
|
|
entry->MarkInactive();
|
|
|
|
// bind entry if necessary to store meta-data
|
|
device = EnsureEntryHasDevice(entry);
|
|
if (!device) {
|
|
CACHE_LOG_DEBUG(
|
|
("DeactivateEntry: unable to bind active "
|
|
"entry %p\n",
|
|
entry));
|
|
NS_WARNING("DeactivateEntry: unable to bind active entry\n");
|
|
return;
|
|
}
|
|
} else {
|
|
// if mInitialized == false,
|
|
// then we're shutting down and this state is okay.
|
|
NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
|
|
}
|
|
|
|
device = entry->CacheDevice();
|
|
if (device) {
|
|
rv = device->DeactivateEntry(entry);
|
|
if (NS_FAILED(rv)) {
|
|
// increment deactivate failure count
|
|
++mDeactivateFailures;
|
|
}
|
|
} else {
|
|
// increment deactivating unbound entry statistic
|
|
++mDeactivatedUnboundEntries;
|
|
delete entry; // because no one else will
|
|
}
|
|
}
|
|
|
|
nsresult nsCacheService::ProcessPendingRequests(nsCacheEntry* entry) {
|
|
nsresult rv = NS_OK;
|
|
nsCacheRequest* request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
|
|
nsCacheRequest* nextRequest;
|
|
bool newWriter = false;
|
|
|
|
CACHE_LOG_DEBUG((
|
|
"ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
|
|
(entry->IsInitialized() ? "" : "Un"), (entry->IsDoomed() ? "DOOMED" : ""),
|
|
(entry->IsValid() ? "V" : "Inv"), entry));
|
|
|
|
if (request == &entry->mRequestQ) return NS_OK; // no queued requests
|
|
|
|
if (!entry->IsDoomed() && entry->IsInvalid()) {
|
|
// 1st descriptor closed w/o MarkValid()
|
|
NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ),
|
|
"shouldn't be here with open descriptors");
|
|
|
|
#if DEBUG
|
|
// verify no ACCESS_WRITE requests(shouldn't have any of these)
|
|
while (request != &entry->mRequestQ) {
|
|
NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
|
|
"ACCESS_WRITE request should have been given a new entry");
|
|
request = (nsCacheRequest*)PR_NEXT_LINK(request);
|
|
}
|
|
request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
|
|
#endif
|
|
// find first request with ACCESS_READ_WRITE (if any) and promote it to 1st
|
|
// writer
|
|
while (request != &entry->mRequestQ) {
|
|
if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
|
|
newWriter = true;
|
|
CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
|
|
break;
|
|
}
|
|
|
|
request = (nsCacheRequest*)PR_NEXT_LINK(request);
|
|
}
|
|
|
|
if (request == &entry->mRequestQ) // no requests asked for
|
|
// ACCESS_READ_WRITE, back to top
|
|
request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
|
|
|
|
// XXX what should we do if there are only READ requests in queue?
|
|
// XXX serialize their accesses, give them only read access, but force them
|
|
// to check validate flag?
|
|
// XXX or do readers simply presume the entry is valid
|
|
// See fix for bug #467392 below
|
|
}
|
|
|
|
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
|
|
|
|
while (request != &entry->mRequestQ) {
|
|
nextRequest = (nsCacheRequest*)PR_NEXT_LINK(request);
|
|
CACHE_LOG_DEBUG((" %sync request %p for %p\n",
|
|
(request->mListener ? "As" : "S"), request, entry));
|
|
|
|
if (request->mListener) {
|
|
// Async request
|
|
PR_REMOVE_AND_INIT_LINK(request);
|
|
|
|
if (entry->IsDoomed()) {
|
|
rv = ProcessRequest(request, false, nullptr);
|
|
if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
|
|
rv = NS_OK;
|
|
else
|
|
delete request;
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// XXX what to do?
|
|
}
|
|
} else if (entry->IsValid() || newWriter) {
|
|
rv = entry->RequestAccess(request, &accessGranted);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv),
|
|
"if entry is valid, RequestAccess must succeed.");
|
|
// XXX if (newWriter) {
|
|
// NS_ASSERTION( accessGranted ==
|
|
// request->AccessRequested(), "why not?");
|
|
// }
|
|
|
|
// entry->CreateDescriptor dequeues request, and queues descriptor
|
|
nsICacheEntryDescriptor* descriptor = nullptr;
|
|
rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
|
|
|
|
// post call to listener to report error or descriptor
|
|
rv = NotifyListener(request, descriptor, accessGranted, rv);
|
|
delete request;
|
|
if (NS_FAILED(rv)) {
|
|
// XXX what to do?
|
|
}
|
|
|
|
} else {
|
|
// read-only request to an invalid entry - need to wait for
|
|
// the entry to become valid so we post an event to process
|
|
// the request again later (bug #467392)
|
|
nsCOMPtr<nsIRunnable> ev = new nsProcessRequestEvent(request);
|
|
rv = DispatchToCacheIOThread(ev);
|
|
if (NS_FAILED(rv)) {
|
|
delete request; // avoid leak
|
|
}
|
|
}
|
|
} else {
|
|
// Synchronous request
|
|
request->WakeUp();
|
|
}
|
|
if (newWriter) break; // process remaining requests after validation
|
|
request = nextRequest;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsCacheService::IsDoomListEmpty() {
|
|
nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
|
|
return &mDoomedEntries == entry;
|
|
}
|
|
|
|
void nsCacheService::ClearDoomList() {
|
|
nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
|
|
|
|
while (entry != &mDoomedEntries) {
|
|
nsCacheEntry* next = (nsCacheEntry*)PR_NEXT_LINK(entry);
|
|
|
|
entry->DetachDescriptors();
|
|
DeactivateEntry(entry);
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
void nsCacheService::DoomActiveEntries(DoomCheckFn check) {
|
|
AutoTArray<nsCacheEntry*, 8> array;
|
|
|
|
for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
|
|
nsCacheEntry* entry =
|
|
static_cast<nsCacheEntryHashTableEntry*>(iter.Get())->cacheEntry;
|
|
|
|
if (check && !check(entry)) {
|
|
continue;
|
|
}
|
|
|
|
array.AppendElement(entry);
|
|
|
|
// entry is being removed from the active entry list
|
|
entry->MarkInactive();
|
|
iter.Remove();
|
|
}
|
|
|
|
uint32_t count = array.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
DoomEntry_Internal(array[i], true);
|
|
}
|
|
}
|
|
|
|
void nsCacheService::CloseAllStreams() {
|
|
nsTArray<RefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
|
|
nsTArray<RefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
|
|
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
|
|
|
|
nsTArray<nsCacheEntry*> entries;
|
|
|
|
#if DEBUG
|
|
// make sure there is no active entry
|
|
for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
|
|
auto entry = static_cast<nsCacheEntryHashTableEntry*>(iter.Get());
|
|
entries.AppendElement(entry->cacheEntry);
|
|
}
|
|
NS_ASSERTION(entries.IsEmpty(), "Bad state");
|
|
#endif
|
|
|
|
// Get doomed entries
|
|
nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
|
|
while (entry != &mDoomedEntries) {
|
|
nsCacheEntry* next = (nsCacheEntry*)PR_NEXT_LINK(entry);
|
|
entries.AppendElement(entry);
|
|
entry = next;
|
|
}
|
|
|
|
// Iterate through all entries and collect input and output streams
|
|
for (size_t i = 0; i < entries.Length(); i++) {
|
|
entry = entries.ElementAt(i);
|
|
|
|
nsTArray<RefPtr<nsCacheEntryDescriptor> > descs;
|
|
entry->GetDescriptors(descs);
|
|
|
|
for (uint32_t j = 0; j < descs.Length(); j++) {
|
|
if (descs[j]->mOutputWrapper)
|
|
outputs.AppendElement(descs[j]->mOutputWrapper);
|
|
|
|
for (size_t k = 0; k < descs[j]->mInputWrappers.Length(); k++)
|
|
inputs.AppendElement(descs[j]->mInputWrappers[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t i;
|
|
for (i = 0; i < inputs.Length(); i++) inputs[i]->Close();
|
|
|
|
for (i = 0; i < outputs.Length(); i++) outputs[i]->Close();
|
|
}
|
|
|
|
bool nsCacheService::GetClearingEntries() {
|
|
AssertOwnsLock();
|
|
return gService->mClearingEntries;
|
|
}
|
|
|
|
// static
|
|
void nsCacheService::GetCacheBaseDirectoty(nsIFile** result) {
|
|
*result = nullptr;
|
|
if (!gService || !gService->mObserver) return;
|
|
|
|
nsCOMPtr<nsIFile> directory = gService->mObserver->DiskCacheParentDirectory();
|
|
if (!directory) return;
|
|
|
|
directory->Clone(result);
|
|
}
|
|
|
|
// static
|
|
void nsCacheService::GetDiskCacheDirectory(nsIFile** result) {
|
|
nsCOMPtr<nsIFile> directory;
|
|
GetCacheBaseDirectoty(getter_AddRefs(directory));
|
|
if (!directory) return;
|
|
|
|
nsresult rv = directory->AppendNative("Cache"_ns);
|
|
if (NS_FAILED(rv)) return;
|
|
|
|
directory.forget(result);
|
|
}
|
|
|
|
// static
|
|
void nsCacheService::GetAppCacheDirectory(nsIFile** result) {
|
|
nsCOMPtr<nsIFile> directory;
|
|
GetCacheBaseDirectoty(getter_AddRefs(directory));
|
|
if (!directory) return;
|
|
|
|
nsresult rv = directory->AppendNative("OfflineCache"_ns);
|
|
if (NS_FAILED(rv)) return;
|
|
|
|
directory.forget(result);
|
|
}
|
|
|
|
void nsCacheService::LogCacheStatistics() {
|
|
uint32_t hitPercentage = 0;
|
|
double sum = (double)(mCacheHits + mCacheMisses);
|
|
if (sum != 0) {
|
|
hitPercentage = (uint32_t)((((double)mCacheHits) / sum) * 100);
|
|
}
|
|
CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
|
|
CACHE_LOG_INFO((" TotalEntries = %d\n", mTotalEntries));
|
|
CACHE_LOG_INFO((" Cache Hits = %d\n", mCacheHits));
|
|
CACHE_LOG_INFO((" Cache Misses = %d\n", mCacheMisses));
|
|
CACHE_LOG_INFO((" Cache Hit %% = %d%%\n", hitPercentage));
|
|
CACHE_LOG_INFO((" Max Key Length = %d\n", mMaxKeyLength));
|
|
CACHE_LOG_INFO((" Max Meta Size = %d\n", mMaxMetaSize));
|
|
CACHE_LOG_INFO((" Max Data Size = %d\n", mMaxDataSize));
|
|
CACHE_LOG_INFO(("\n"));
|
|
CACHE_LOG_INFO(
|
|
(" Deactivate Failures = %d\n", mDeactivateFailures));
|
|
CACHE_LOG_INFO(
|
|
(" Deactivated Unbound Entries = %d\n", mDeactivatedUnboundEntries));
|
|
}
|
|
|
|
void nsCacheService::MoveOrRemoveDiskCache(nsIFile* aOldCacheDir,
|
|
nsIFile* aNewCacheDir,
|
|
const char* aCacheSubdir) {
|
|
bool same;
|
|
if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same) return;
|
|
|
|
nsCOMPtr<nsIFile> aOldCacheSubdir;
|
|
aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
|
|
|
|
nsresult rv = aOldCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
|
|
if (NS_FAILED(rv)) return;
|
|
|
|
bool exists;
|
|
if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists) return;
|
|
|
|
nsCOMPtr<nsIFile> aNewCacheSubdir;
|
|
aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
|
|
|
|
rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
|
|
if (NS_FAILED(rv)) return;
|
|
|
|
PathString newPath = aNewCacheSubdir->NativePath();
|
|
|
|
if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
|
|
// New cache directory does not exist, try to move the old one here
|
|
// rename needs an empty target directory
|
|
|
|
// Make sure the parent of the target sub-dir exists
|
|
rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
|
|
if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
|
|
PathString oldPath = aOldCacheSubdir->NativePath();
|
|
#ifdef XP_WIN
|
|
if (MoveFileW(oldPath.get(), newPath.get()))
|
|
#else
|
|
if (rename(oldPath.get(), newPath.get()) == 0)
|
|
#endif
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delay delete by 1 minute to avoid IO thrash on startup.
|
|
nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
|
|
}
|
|
|
|
static bool IsEntryPrivate(nsCacheEntry* entry) { return entry->IsPrivate(); }
|
|
|
|
void nsCacheService::LeavePrivateBrowsing() {
|
|
nsCacheServiceAutoLock lock;
|
|
|
|
gService->DoomActiveEntries(IsEntryPrivate);
|
|
}
|