forked from mirrors/gecko-dev
This should avoid the crash caused by the CacheOutputCloseListener being fired on the main thread too late during shutdown by keeping the previous behaviour when on the main thread. Differential Revision: https://phabricator.services.mozilla.com/D165062
1943 lines
56 KiB
C++
1943 lines
56 KiB
C++
/* 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 <algorithm>
|
|
#include <math.h>
|
|
|
|
#include "CacheEntry.h"
|
|
|
|
#include "CacheFileUtils.h"
|
|
#include "CacheIndex.h"
|
|
#include "CacheLog.h"
|
|
#include "CacheObserver.h"
|
|
#include "CacheStorageService.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/psm/TransportSecurityInfo.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsIAsyncOutputStream.h"
|
|
#include "nsICacheEntryOpenCallback.h"
|
|
#include "nsICacheStorage.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsISeekableStream.h"
|
|
#include "nsISizeOf.h"
|
|
#include "nsIURI.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
namespace mozilla::net {
|
|
|
|
static uint32_t const ENTRY_WANTED = nsICacheEntryOpenCallback::ENTRY_WANTED;
|
|
static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
|
|
nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
|
|
static uint32_t const ENTRY_NEEDS_REVALIDATION =
|
|
nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
|
|
static uint32_t const ENTRY_NOT_WANTED =
|
|
nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
|
|
|
|
NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
|
|
|
|
// CacheEntryHandle
|
|
|
|
CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) : mEntry(aEntry) {
|
|
#ifdef DEBUG
|
|
if (!mEntry->HandlesCount()) {
|
|
// CacheEntry.mHandlesCount must go from zero to one only under
|
|
// the service lock. Can access CacheStorageService::Self() w/o a check
|
|
// since CacheEntry hrefs it.
|
|
CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
|
|
}
|
|
#endif
|
|
|
|
mEntry->AddHandleRef();
|
|
|
|
LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
|
|
}
|
|
|
|
NS_IMETHODIMP CacheEntryHandle::Dismiss() {
|
|
LOG(("CacheEntryHandle::Dismiss %p", this));
|
|
|
|
if (mClosed.compareExchange(false, true)) {
|
|
mEntry->OnHandleClosed(this);
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG((" already dropped"));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
CacheEntryHandle::~CacheEntryHandle() {
|
|
mEntry->ReleaseHandleRef();
|
|
Dismiss();
|
|
|
|
LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
|
|
}
|
|
|
|
// CacheEntry::Callback
|
|
|
|
CacheEntry::Callback::Callback(CacheEntry* aEntry,
|
|
nsICacheEntryOpenCallback* aCallback,
|
|
bool aReadOnly, bool aCheckOnAnyThread,
|
|
bool aSecret)
|
|
: mEntry(aEntry),
|
|
mCallback(aCallback),
|
|
mTarget(GetCurrentEventTarget()),
|
|
mReadOnly(aReadOnly),
|
|
mRevalidating(false),
|
|
mCheckOnAnyThread(aCheckOnAnyThread),
|
|
mRecheckAfterWrite(false),
|
|
mNotWanted(false),
|
|
mSecret(aSecret),
|
|
mDoomWhenFoundPinned(false),
|
|
mDoomWhenFoundNonPinned(false) {
|
|
MOZ_COUNT_CTOR(CacheEntry::Callback);
|
|
|
|
// The counter may go from zero to non-null only under the service lock
|
|
// but here we expect it to be already positive.
|
|
MOZ_ASSERT(mEntry->HandlesCount());
|
|
mEntry->AddHandleRef();
|
|
}
|
|
|
|
CacheEntry::Callback::Callback(CacheEntry* aEntry,
|
|
bool aDoomWhenFoundInPinStatus)
|
|
: mEntry(aEntry),
|
|
mReadOnly(false),
|
|
mRevalidating(false),
|
|
mCheckOnAnyThread(true),
|
|
mRecheckAfterWrite(false),
|
|
mNotWanted(false),
|
|
mSecret(false),
|
|
mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus),
|
|
mDoomWhenFoundNonPinned(!aDoomWhenFoundInPinStatus) {
|
|
MOZ_COUNT_CTOR(CacheEntry::Callback);
|
|
MOZ_ASSERT(mEntry->HandlesCount());
|
|
mEntry->AddHandleRef();
|
|
}
|
|
|
|
CacheEntry::Callback::Callback(CacheEntry::Callback const& aThat)
|
|
: mEntry(aThat.mEntry),
|
|
mCallback(aThat.mCallback),
|
|
mTarget(aThat.mTarget),
|
|
mReadOnly(aThat.mReadOnly),
|
|
mRevalidating(aThat.mRevalidating),
|
|
mCheckOnAnyThread(aThat.mCheckOnAnyThread),
|
|
mRecheckAfterWrite(aThat.mRecheckAfterWrite),
|
|
mNotWanted(aThat.mNotWanted),
|
|
mSecret(aThat.mSecret),
|
|
mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned),
|
|
mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned) {
|
|
MOZ_COUNT_CTOR(CacheEntry::Callback);
|
|
|
|
// The counter may go from zero to non-null only under the service lock
|
|
// but here we expect it to be already positive.
|
|
MOZ_ASSERT(mEntry->HandlesCount());
|
|
mEntry->AddHandleRef();
|
|
}
|
|
|
|
CacheEntry::Callback::~Callback() {
|
|
ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget);
|
|
|
|
mEntry->ReleaseHandleRef();
|
|
MOZ_COUNT_DTOR(CacheEntry::Callback);
|
|
}
|
|
|
|
// We have locks on both this and aEntry
|
|
void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) {
|
|
aEntry->mLock.AssertCurrentThreadOwns();
|
|
mEntry->mLock.AssertCurrentThreadOwns();
|
|
if (mEntry == aEntry) return;
|
|
|
|
// The counter may go from zero to non-null only under the service lock
|
|
// but here we expect it to be already positive.
|
|
MOZ_ASSERT(aEntry->HandlesCount());
|
|
aEntry->AddHandleRef();
|
|
mEntry->ReleaseHandleRef();
|
|
mEntry = aEntry;
|
|
}
|
|
|
|
// This is called on entries in another entry's mCallback array, under the lock
|
|
// of that other entry. No other threads can access this entry at this time.
|
|
bool CacheEntry::Callback::DeferDoom(bool* aDoom) const
|
|
MOZ_NO_THREAD_SAFETY_ANALYSIS {
|
|
MOZ_ASSERT(mEntry->mPinningKnown);
|
|
|
|
if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) ||
|
|
MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
|
|
*aDoom =
|
|
(MOZ_UNLIKELY(mDoomWhenFoundNonPinned) &&
|
|
MOZ_LIKELY(!mEntry->mPinned)) ||
|
|
(MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
nsresult CacheEntry::Callback::OnCheckThread(bool* aOnCheckThread) const {
|
|
if (!mCheckOnAnyThread) {
|
|
// Check we are on the target
|
|
return mTarget->IsOnCurrentThread(aOnCheckThread);
|
|
}
|
|
|
|
// We can invoke check anywhere
|
|
*aOnCheckThread = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread) const {
|
|
return mTarget->IsOnCurrentThread(aOnAvailThread);
|
|
}
|
|
|
|
// CacheEntry
|
|
|
|
NS_IMPL_ISUPPORTS(CacheEntry, nsIRunnable, CacheFileListener)
|
|
|
|
/* static */
|
|
uint64_t CacheEntry::GetNextId() {
|
|
static Atomic<uint64_t, Relaxed> id(0);
|
|
return ++id;
|
|
}
|
|
|
|
CacheEntry::CacheEntry(const nsACString& aStorageID, const nsACString& aURI,
|
|
const nsACString& aEnhanceID, bool aUseDisk,
|
|
bool aSkipSizeCheck, bool aPin)
|
|
: mURI(aURI),
|
|
mEnhanceID(aEnhanceID),
|
|
mStorageID(aStorageID),
|
|
mUseDisk(aUseDisk),
|
|
mSkipSizeCheck(aSkipSizeCheck),
|
|
mPinned(aPin),
|
|
mSecurityInfoLoaded(false),
|
|
mPreventCallbacks(false),
|
|
mHasData(false),
|
|
mPinningKnown(false),
|
|
mCacheEntryId(GetNextId()) {
|
|
LOG(("CacheEntry::CacheEntry [this=%p]", this));
|
|
|
|
mService = CacheStorageService::Self();
|
|
|
|
CacheStorageService::Self()->RecordMemoryOnlyEntry(this, !aUseDisk,
|
|
true /* overwrite */);
|
|
}
|
|
|
|
CacheEntry::~CacheEntry() { LOG(("CacheEntry::~CacheEntry [this=%p]", this)); }
|
|
|
|
char const* CacheEntry::StateString(uint32_t aState) {
|
|
switch (aState) {
|
|
case NOTLOADED:
|
|
return "NOTLOADED";
|
|
case LOADING:
|
|
return "LOADING";
|
|
case EMPTY:
|
|
return "EMPTY";
|
|
case WRITING:
|
|
return "WRITING";
|
|
case READY:
|
|
return "READY";
|
|
case REVALIDATING:
|
|
return "REVALIDATING";
|
|
}
|
|
|
|
return "?";
|
|
}
|
|
|
|
nsresult CacheEntry::HashingKeyWithStorage(nsACString& aResult) const {
|
|
return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
|
|
}
|
|
|
|
nsresult CacheEntry::HashingKey(nsACString& aResult) const {
|
|
return HashingKey(""_ns, mEnhanceID, mURI, aResult);
|
|
}
|
|
|
|
// static
|
|
nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
|
|
const nsACString& aEnhanceID, nsIURI* aURI,
|
|
nsACString& aResult) {
|
|
nsAutoCString spec;
|
|
nsresult rv = aURI->GetAsciiSpec(spec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return HashingKey(aStorageID, aEnhanceID, spec, aResult);
|
|
}
|
|
|
|
// static
|
|
nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
|
|
const nsACString& aEnhanceID,
|
|
const nsACString& aURISpec,
|
|
nsACString& aResult) {
|
|
/**
|
|
* This key is used to salt hash that is a base for disk file name.
|
|
* Changing it will cause we will not be able to find files on disk.
|
|
*/
|
|
|
|
aResult.Assign(aStorageID);
|
|
|
|
if (!aEnhanceID.IsEmpty()) {
|
|
CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
|
|
}
|
|
|
|
// Appending directly
|
|
aResult.Append(':');
|
|
aResult.Append(aURISpec);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback,
|
|
uint32_t aFlags) {
|
|
bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
|
|
bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
|
|
bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
|
|
bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
|
|
bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
|
|
bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
|
|
|
|
if (MOZ_LOG_TEST(gCache2Log, LogLevel::Debug)) {
|
|
MutexAutoLock lock(mLock);
|
|
LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
|
|
this, StateString(mState), aFlags, aCallback));
|
|
}
|
|
#ifdef DEBUG
|
|
{
|
|
// yes, if logging is on in DEBUG we'll take the lock twice in a row
|
|
MutexAutoLock lock(mLock);
|
|
MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
|
|
MOZ_ASSERT(!(truncate && mState > LOADING),
|
|
"Must not call truncate on already loaded entry");
|
|
}
|
|
#endif
|
|
|
|
Callback callback(this, aCallback, readonly, multithread, secret);
|
|
|
|
if (!Open(callback, truncate, priority, bypassIfBusy)) {
|
|
// We get here when the callback wants to bypass cache when it's busy.
|
|
LOG((" writing or revalidating, callback wants to bypass cache"));
|
|
callback.mNotWanted = true;
|
|
InvokeAvailableCallback(callback);
|
|
}
|
|
}
|
|
|
|
bool CacheEntry::Open(Callback& aCallback, bool aTruncate, bool aPriority,
|
|
bool aBypassIfBusy) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
// Check state under the lock
|
|
if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
|
|
return false;
|
|
}
|
|
|
|
RememberCallback(aCallback);
|
|
|
|
// Load() opens the lock
|
|
if (Load(aTruncate, aPriority)) {
|
|
// Loading is in progress...
|
|
return true;
|
|
}
|
|
|
|
InvokeCallbacks();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CacheEntry::Load(bool aTruncate, bool aPriority) {
|
|
LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
|
|
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
if (mState > LOADING) {
|
|
LOG((" already loaded"));
|
|
return false;
|
|
}
|
|
|
|
if (mState == LOADING) {
|
|
LOG((" already loading"));
|
|
return true;
|
|
}
|
|
|
|
mState = LOADING;
|
|
|
|
MOZ_ASSERT(!mFile);
|
|
|
|
nsresult rv;
|
|
|
|
nsAutoCString fileKey;
|
|
rv = HashingKeyWithStorage(fileKey);
|
|
|
|
bool reportMiss = false;
|
|
|
|
// Check the index under two conditions for two states and take appropriate
|
|
// action:
|
|
// 1. When this is a disk entry and not told to truncate, check there is a
|
|
// disk file.
|
|
// If not, set the 'truncate' flag to true so that this entry will open
|
|
// instantly as a new one.
|
|
// 2. When this is a memory-only entry, check there is a disk file.
|
|
// If there is or could be, doom that file.
|
|
if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
|
|
// Check the index right now to know we have or have not the entry
|
|
// as soon as possible.
|
|
CacheIndex::EntryStatus status;
|
|
if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
|
|
switch (status) {
|
|
case CacheIndex::DOES_NOT_EXIST:
|
|
// Doesn't apply to memory-only entries, Load() is called only once
|
|
// for them and never again for their session lifetime.
|
|
if (!aTruncate && mUseDisk) {
|
|
LOG(
|
|
(" entry doesn't exist according information from the index, "
|
|
"truncating"));
|
|
reportMiss = true;
|
|
aTruncate = true;
|
|
}
|
|
break;
|
|
case CacheIndex::EXISTS:
|
|
case CacheIndex::DO_NOT_KNOW:
|
|
if (!mUseDisk) {
|
|
LOG(
|
|
(" entry open as memory-only, but there is a file, status=%d, "
|
|
"dooming it",
|
|
status));
|
|
CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mFile = new CacheFile();
|
|
|
|
BackgroundOp(Ops::REGISTER);
|
|
|
|
bool directLoad = aTruncate || !mUseDisk;
|
|
if (directLoad) {
|
|
// mLoadStart will be used to calculate telemetry of life-time of this
|
|
// entry. Low resulution is then enough.
|
|
mLoadStart = TimeStamp::NowLoRes();
|
|
mPinningKnown = true;
|
|
} else {
|
|
mLoadStart = TimeStamp::Now();
|
|
}
|
|
|
|
{
|
|
mozilla::MutexAutoUnlock unlock(mLock);
|
|
|
|
if (reportMiss) {
|
|
CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
|
|
CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
|
|
}
|
|
|
|
LOG((" performing load, file=%p", mFile.get()));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = mFile->Init(fileKey, aTruncate, !mUseDisk, mSkipSizeCheck, aPriority,
|
|
mPinned, directLoad ? nullptr : this);
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
mFileStatus = rv;
|
|
AsyncDoom(nullptr);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (directLoad) {
|
|
// Just fake the load has already been done as "new".
|
|
mFileStatus = NS_OK;
|
|
mState = EMPTY;
|
|
}
|
|
|
|
return mState == LOADING;
|
|
}
|
|
|
|
NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) {
|
|
LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]", this,
|
|
static_cast<uint32_t>(aResult), aIsNew));
|
|
|
|
MOZ_ASSERT(!mLoadStart.IsNull());
|
|
|
|
if (NS_SUCCEEDED(aResult)) {
|
|
if (aIsNew) {
|
|
CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
|
|
CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
|
|
} else {
|
|
CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
|
|
CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
|
|
}
|
|
}
|
|
|
|
// OnFileReady, that is the only code that can transit from LOADING
|
|
// to any follow-on state and can only be invoked ones on an entry.
|
|
// Until this moment there is no consumer that could manipulate
|
|
// the entry state.
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
MOZ_ASSERT(mState == LOADING);
|
|
|
|
mState = (aIsNew || NS_FAILED(aResult)) ? EMPTY : READY;
|
|
|
|
mFileStatus = aResult;
|
|
|
|
mPinned = mFile->IsPinned();
|
|
|
|
mPinningKnown = true;
|
|
LOG((" pinning=%d", (bool)mPinned));
|
|
|
|
if (mState == READY) {
|
|
mHasData = true;
|
|
|
|
uint32_t frecency;
|
|
mFile->GetFrecency(&frecency);
|
|
// mFrecency is held in a double to increase computance precision.
|
|
// It is ok to persist frecency only as a uint32 with some math involved.
|
|
mFrecency = INT2FRECENCY(frecency);
|
|
}
|
|
|
|
InvokeCallbacks();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) {
|
|
if (mDoomCallback) {
|
|
RefPtr<DoomCallbackRunnable> event =
|
|
new DoomCallbackRunnable(this, aResult);
|
|
NS_DispatchToMainThread(event);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(
|
|
bool aMemoryOnly, nsICacheEntryOpenCallback* aCallback) {
|
|
LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
|
|
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
// Hold callbacks invocation, AddStorageEntry would invoke from doom
|
|
// prematurly
|
|
mPreventCallbacks = true;
|
|
|
|
RefPtr<CacheEntryHandle> handle;
|
|
RefPtr<CacheEntry> newEntry;
|
|
{
|
|
if (mPinned) {
|
|
MOZ_ASSERT(mUseDisk);
|
|
// We want to pin even no-store entries (the case we recreate a disk entry
|
|
// as a memory-only entry.)
|
|
aMemoryOnly = false;
|
|
}
|
|
|
|
mozilla::MutexAutoUnlock unlock(mLock);
|
|
|
|
// The following call dooms this entry (calls DoomAlreadyRemoved on us)
|
|
nsresult rv = CacheStorageService::Self()->AddStorageEntry(
|
|
GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk && !aMemoryOnly,
|
|
mSkipSizeCheck, mPinned,
|
|
nsICacheStorage::OPEN_TRUNCATE, // truncate existing (this one)
|
|
getter_AddRefs(handle));
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
newEntry = handle->Entry();
|
|
LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32, this,
|
|
newEntry.get(), static_cast<uint32_t>(rv)));
|
|
newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
|
|
} else {
|
|
LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32, this,
|
|
static_cast<uint32_t>(rv)));
|
|
AsyncDoom(nullptr);
|
|
}
|
|
}
|
|
|
|
mPreventCallbacks = false;
|
|
|
|
if (!newEntry) return nullptr;
|
|
|
|
newEntry->TransferCallbacks(*this);
|
|
mCallbacks.Clear();
|
|
|
|
// Must return a new write handle, since the consumer is expected to
|
|
// write to this newly recreated entry. The |handle| is only a common
|
|
// reference counter and doesn't revert entry state back when write
|
|
// fails and also doesn't update the entry frecency. Not updating
|
|
// frecency causes entries to not be purged from our memory pools.
|
|
RefPtr<CacheEntryHandle> writeHandle = newEntry->NewWriteHandle();
|
|
return writeHandle.forget();
|
|
}
|
|
|
|
void CacheEntry::TransferCallbacks(CacheEntry& aFromEntry) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
aFromEntry.mLock.AssertCurrentThreadOwns();
|
|
|
|
LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry));
|
|
|
|
if (!mCallbacks.Length()) {
|
|
mCallbacks.SwapElements(aFromEntry.mCallbacks);
|
|
} else {
|
|
mCallbacks.AppendElements(aFromEntry.mCallbacks);
|
|
}
|
|
|
|
uint32_t callbacksLength = mCallbacks.Length();
|
|
if (callbacksLength) {
|
|
// Carry the entry reference (unfortunately, needs to be done manually...)
|
|
for (uint32_t i = 0; i < callbacksLength; ++i) {
|
|
mCallbacks[i].ExchangeEntry(this);
|
|
}
|
|
|
|
BackgroundOp(Ops::CALLBACKS, true);
|
|
}
|
|
}
|
|
|
|
void CacheEntry::RememberCallback(Callback& aCallback) {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this,
|
|
aCallback.mCallback.get(), StateString(mState)));
|
|
|
|
mCallbacks.AppendElement(aCallback);
|
|
}
|
|
|
|
void CacheEntry::InvokeCallbacksLock() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
InvokeCallbacks();
|
|
}
|
|
|
|
void CacheEntry::InvokeCallbacks() {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
|
|
|
|
// Invoke first all r/w callbacks, then all r/o callbacks.
|
|
if (InvokeCallbacks(false)) InvokeCallbacks(true);
|
|
|
|
LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
|
|
}
|
|
|
|
bool CacheEntry::InvokeCallbacks(bool aReadOnly) {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
RefPtr<CacheEntryHandle> recreatedHandle;
|
|
|
|
uint32_t i = 0;
|
|
while (i < mCallbacks.Length()) {
|
|
if (mPreventCallbacks) {
|
|
LOG((" callbacks prevented!"));
|
|
return false;
|
|
}
|
|
|
|
if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
|
|
LOG((" entry is being written/revalidated"));
|
|
return false;
|
|
}
|
|
|
|
bool recreate;
|
|
if (mCallbacks[i].DeferDoom(&recreate)) {
|
|
mCallbacks.RemoveElementAt(i);
|
|
if (!recreate) {
|
|
continue;
|
|
}
|
|
|
|
LOG((" defer doom marker callback hit positive, recreating"));
|
|
recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
|
|
break;
|
|
}
|
|
|
|
if (mCallbacks[i].mReadOnly != aReadOnly) {
|
|
// Callback is not r/w or r/o, go to another one in line
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
bool onCheckThread;
|
|
nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
|
|
|
|
if (NS_SUCCEEDED(rv) && !onCheckThread) {
|
|
// Redispatch to the target thread
|
|
rv = mCallbacks[i].mTarget->Dispatch(
|
|
NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this,
|
|
&CacheEntry::InvokeCallbacksLock),
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
LOG((" re-dispatching to target thread"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Callback callback = mCallbacks[i];
|
|
mCallbacks.RemoveElementAt(i);
|
|
|
|
if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
|
|
// Callback didn't fire, put it back and go to another one in line.
|
|
// Only reason InvokeCallback returns false is that onCacheEntryCheck
|
|
// returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
|
|
// readers or potential writers would be unnecessarily kept from being
|
|
// invoked.
|
|
size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
|
|
mCallbacks.InsertElementAt(pos, callback);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
if (recreatedHandle) {
|
|
// Must be released outside of the lock, enters InvokeCallback on the new
|
|
// entry
|
|
mozilla::MutexAutoUnlock unlock(mLock);
|
|
recreatedHandle = nullptr;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CacheEntry::InvokeCallback(Callback& aCallback) {
|
|
mLock.AssertCurrentThreadOwns();
|
|
LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
|
|
StateString(mState), aCallback.mCallback.get()));
|
|
|
|
// When this entry is doomed we want to notify the callback any time
|
|
if (!mIsDoomed) {
|
|
// When we are here, the entry must be loaded from disk
|
|
MOZ_ASSERT(mState > LOADING);
|
|
|
|
if (mState == WRITING || mState == REVALIDATING) {
|
|
// Prevent invoking other callbacks since one of them is now writing
|
|
// or revalidating this entry. No consumers should get this entry
|
|
// until metadata are filled with values downloaded from the server
|
|
// or the entry revalidated and output stream has been opened.
|
|
LOG((" entry is being written/revalidated, callback bypassed"));
|
|
return false;
|
|
}
|
|
|
|
// mRecheckAfterWrite flag already set means the callback has already passed
|
|
// the onCacheEntryCheck call. Until the current write is not finished this
|
|
// callback will be bypassed.
|
|
if (!aCallback.mRecheckAfterWrite) {
|
|
if (!aCallback.mReadOnly) {
|
|
if (mState == EMPTY) {
|
|
// Advance to writing state, we expect to invoke the callback and let
|
|
// it fill content of this entry. Must set and check the state here
|
|
// to prevent more then one
|
|
mState = WRITING;
|
|
LOG((" advancing to WRITING state"));
|
|
}
|
|
|
|
if (!aCallback.mCallback) {
|
|
// We can be given no callback only in case of recreate, it is ok
|
|
// to advance to WRITING state since the caller of recreate is
|
|
// expected to write this entry now.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (mState == READY) {
|
|
// Metadata present, validate the entry
|
|
uint32_t checkResult;
|
|
{
|
|
// mayhemer: TODO check and solve any potential races of concurent
|
|
// OnCacheEntryCheck
|
|
mozilla::MutexAutoUnlock unlock(mLock);
|
|
|
|
RefPtr<CacheEntryHandle> handle = NewHandle();
|
|
|
|
nsresult rv =
|
|
aCallback.mCallback->OnCacheEntryCheck(handle, &checkResult);
|
|
LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32,
|
|
static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult)));
|
|
|
|
if (NS_FAILED(rv)) checkResult = ENTRY_NOT_WANTED;
|
|
}
|
|
|
|
aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION;
|
|
|
|
switch (checkResult) {
|
|
case ENTRY_WANTED:
|
|
// Nothing more to do here, the consumer is responsible to handle
|
|
// the result of OnCacheEntryCheck it self.
|
|
// Proceed to callback...
|
|
break;
|
|
|
|
case RECHECK_AFTER_WRITE_FINISHED:
|
|
LOG(
|
|
(" consumer will check on the entry again after write is "
|
|
"done"));
|
|
// The consumer wants the entry to complete first.
|
|
aCallback.mRecheckAfterWrite = true;
|
|
break;
|
|
|
|
case ENTRY_NEEDS_REVALIDATION:
|
|
LOG((" will be holding callbacks until entry is revalidated"));
|
|
// State is READY now and from that state entry cannot transit to
|
|
// any other state then REVALIDATING for which cocurrency is not an
|
|
// issue. Potentially no need to lock here.
|
|
mState = REVALIDATING;
|
|
break;
|
|
|
|
case ENTRY_NOT_WANTED:
|
|
LOG((" consumer not interested in the entry"));
|
|
// Do not give this entry to the consumer, it is not interested in
|
|
// us.
|
|
aCallback.mNotWanted = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aCallback.mCallback) {
|
|
if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
|
|
// If we don't have data and the callback wants a complete entry,
|
|
// don't invoke now.
|
|
bool bypass = !mHasData;
|
|
if (!bypass && NS_SUCCEEDED(mFileStatus)) {
|
|
int64_t _unused;
|
|
bypass = !mFile->DataSize(&_unused);
|
|
}
|
|
|
|
if (bypass) {
|
|
LOG((" bypassing, entry data still being written"));
|
|
return false;
|
|
}
|
|
|
|
// Entry is complete now, do the check+avail call again
|
|
aCallback.mRecheckAfterWrite = false;
|
|
return InvokeCallback(aCallback);
|
|
}
|
|
|
|
mozilla::MutexAutoUnlock unlock(mLock);
|
|
InvokeAvailableCallback(aCallback);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) {
|
|
nsresult rv;
|
|
uint32_t state;
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
state = mState;
|
|
LOG(
|
|
("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, "
|
|
"r/o=%d, "
|
|
"n/w=%d]",
|
|
this, StateString(mState), aCallback.mCallback.get(),
|
|
aCallback.mReadOnly, aCallback.mNotWanted));
|
|
|
|
// When we are here, the entry must be loaded from disk
|
|
MOZ_ASSERT(state > LOADING || mIsDoomed);
|
|
}
|
|
|
|
bool onAvailThread;
|
|
rv = aCallback.OnAvailThread(&onAvailThread);
|
|
if (NS_FAILED(rv)) {
|
|
LOG((" target thread dead?"));
|
|
return;
|
|
}
|
|
|
|
if (!onAvailThread) {
|
|
// Dispatch to the right thread
|
|
RefPtr<AvailableCallbackRunnable> event =
|
|
new AvailableCallbackRunnable(this, aCallback);
|
|
|
|
rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
|
|
LOG((" redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
|
|
return;
|
|
}
|
|
|
|
if (mIsDoomed || aCallback.mNotWanted) {
|
|
LOG(
|
|
(" doomed or not wanted, notifying OCEA with "
|
|
"NS_ERROR_CACHE_KEY_NOT_FOUND"));
|
|
aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
|
|
NS_ERROR_CACHE_KEY_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
if (state == READY) {
|
|
LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
|
|
|
|
if (!aCallback.mSecret) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
BackgroundOp(Ops::FRECENCYUPDATE);
|
|
}
|
|
|
|
OnFetched(aCallback);
|
|
|
|
RefPtr<CacheEntryHandle> handle = NewHandle();
|
|
aCallback.mCallback->OnCacheEntryAvailable(handle, false, NS_OK);
|
|
return;
|
|
}
|
|
|
|
// R/O callbacks may do revalidation, let them fall through
|
|
if (aCallback.mReadOnly && !aCallback.mRevalidating) {
|
|
LOG(
|
|
(" r/o and not ready, notifying OCEA with "
|
|
"NS_ERROR_CACHE_KEY_NOT_FOUND"));
|
|
aCallback.mCallback->OnCacheEntryAvailable(nullptr, false,
|
|
NS_ERROR_CACHE_KEY_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
// This is a new or potentially non-valid entry and needs to be fetched first.
|
|
// The CacheEntryHandle blocks other consumers until the channel
|
|
// either releases the entry or marks metadata as filled or whole entry valid,
|
|
// i.e. until MetaDataReady() or SetValid() on the entry is called
|
|
// respectively.
|
|
|
|
// Consumer will be responsible to fill or validate the entry metadata and
|
|
// data.
|
|
|
|
OnFetched(aCallback);
|
|
|
|
RefPtr<CacheEntryHandle> handle = NewWriteHandle();
|
|
rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING,
|
|
NS_OK);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
LOG((" writing/revalidating failed (0x%08" PRIx32 ")",
|
|
static_cast<uint32_t>(rv)));
|
|
|
|
// Consumer given a new entry failed to take care of the entry.
|
|
OnHandleClosed(handle);
|
|
return;
|
|
}
|
|
|
|
LOG((" writing/revalidating"));
|
|
}
|
|
|
|
void CacheEntry::OnFetched(Callback const& aCallback) {
|
|
if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
|
|
// Let the last-fetched and fetch-count properties be updated.
|
|
mFile->OnFetched();
|
|
}
|
|
}
|
|
|
|
CacheEntryHandle* CacheEntry::NewHandle() { return new CacheEntryHandle(this); }
|
|
|
|
CacheEntryHandle* CacheEntry::NewWriteHandle() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
// Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
|
|
// used only along with OPEN_READONLY, but there is no need to enforce that.
|
|
BackgroundOp(Ops::FRECENCYUPDATE);
|
|
|
|
return (mWriter = NewHandle());
|
|
}
|
|
|
|
void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
|
|
StateString(mState), aHandle));
|
|
|
|
if (mIsDoomed && NS_SUCCEEDED(mFileStatus) &&
|
|
// Note: mHandlesCount is dropped before this method is called
|
|
(mHandlesCount == 0 ||
|
|
(mHandlesCount == 1 && mWriter && mWriter != aHandle))) {
|
|
// This entry is no longer referenced from outside and is doomed.
|
|
// We can do this also when there is just reference from the writer,
|
|
// no one else could ever reach the written data.
|
|
// Tell the file to kill the handle, i.e. bypass any I/O operations
|
|
// on it except removing the file.
|
|
mFile->Kill();
|
|
}
|
|
|
|
if (mWriter != aHandle) {
|
|
LOG((" not the writer"));
|
|
return;
|
|
}
|
|
|
|
if (mOutputStream) {
|
|
LOG((" abandoning phantom output stream"));
|
|
// No one took our internal output stream, so there are no data
|
|
// and output stream has to be open symultaneously with input stream
|
|
// on this entry again.
|
|
mHasData = false;
|
|
// This asynchronously ends up invoking callbacks on this entry
|
|
// through OnOutputClosed() call.
|
|
mOutputStream->Close();
|
|
mOutputStream = nullptr;
|
|
} else {
|
|
// We must always redispatch, otherwise there is a risk of stack
|
|
// overflow. This code can recurse deeply. It won't execute sooner
|
|
// than we release mLock.
|
|
BackgroundOp(Ops::CALLBACKS, true);
|
|
}
|
|
|
|
mWriter = nullptr;
|
|
|
|
if (mState == WRITING) {
|
|
LOG((" reverting to state EMPTY - write failed"));
|
|
mState = EMPTY;
|
|
} else if (mState == REVALIDATING) {
|
|
LOG((" reverting to state READY - reval failed"));
|
|
mState = READY;
|
|
}
|
|
|
|
if (mState == READY && !mHasData) {
|
|
// We may get to this state when following steps happen:
|
|
// 1. a new entry is given to a consumer
|
|
// 2. the consumer calls MetaDataReady(), we transit to READY
|
|
// 3. abandons the entry w/o opening the output stream, mHasData left false
|
|
//
|
|
// In this case any following consumer will get a ready entry (with
|
|
// metadata) but in state like the entry data write was still happening (was
|
|
// in progress) and will indefinitely wait for the entry data or even the
|
|
// entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
|
|
LOG(
|
|
(" we are in READY state, pretend we have data regardless it"
|
|
" has actully been never touched"));
|
|
mHasData = true;
|
|
}
|
|
}
|
|
|
|
void CacheEntry::OnOutputClosed() {
|
|
// Called when the file's output stream is closed. Invoke any callbacks
|
|
// waiting for complete entry.
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
InvokeCallbacks();
|
|
}
|
|
|
|
bool CacheEntry::IsReferenced() const {
|
|
CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
|
|
|
|
// Increasing this counter from 0 to non-null and this check both happen only
|
|
// under the service lock.
|
|
return mHandlesCount > 0;
|
|
}
|
|
|
|
bool CacheEntry::IsFileDoomed() {
|
|
if (NS_SUCCEEDED(mFileStatus)) {
|
|
return mFile->IsDoomed();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32_t CacheEntry::GetMetadataMemoryConsumption() {
|
|
NS_ENSURE_SUCCESS(mFileStatus, 0);
|
|
|
|
uint32_t size;
|
|
if (NS_FAILED(mFile->ElementsSize(&size))) return 0;
|
|
|
|
return size;
|
|
}
|
|
|
|
// nsICacheEntry
|
|
|
|
nsresult CacheEntry::GetPersistent(bool* aPersistToDisk) {
|
|
// No need to sync when only reading.
|
|
// When consumer needs to be consistent with state of the memory storage
|
|
// entries table, then let it use GetUseDisk getter that must be called under
|
|
// the service lock.
|
|
*aPersistToDisk = mUseDisk;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetKey(nsACString& aKey) {
|
|
aKey.Assign(mURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) {
|
|
*aCacheEntryId = mCacheEntryId;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetFetchCount(uint32_t* aFetchCount) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->GetFetchCount(aFetchCount);
|
|
}
|
|
|
|
nsresult CacheEntry::GetLastFetched(uint32_t* aLastFetched) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->GetLastFetched(aLastFetched);
|
|
}
|
|
|
|
nsresult CacheEntry::GetLastModified(uint32_t* aLastModified) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->GetLastModified(aLastModified);
|
|
}
|
|
|
|
nsresult CacheEntry::GetExpirationTime(uint32_t* aExpirationTime) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->GetExpirationTime(aExpirationTime);
|
|
}
|
|
|
|
nsresult CacheEntry::GetOnStartTime(uint64_t* aTime) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
return mFile->GetOnStartTime(aTime);
|
|
}
|
|
|
|
nsresult CacheEntry::GetOnStopTime(uint64_t* aTime) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
return mFile->GetOnStopTime(aTime);
|
|
}
|
|
|
|
nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime,
|
|
uint64_t aOnStopTime) {
|
|
if (NS_SUCCEEDED(mFileStatus)) {
|
|
return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult CacheEntry::SetContentType(uint8_t aContentType) {
|
|
NS_ENSURE_ARG_MAX(aContentType, nsICacheEntry::CONTENT_TYPE_LAST - 1);
|
|
|
|
if (NS_SUCCEEDED(mFileStatus)) {
|
|
return mFile->SetContentType(aContentType);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult CacheEntry::GetIsForcedValid(bool* aIsForcedValid) {
|
|
NS_ENSURE_ARG(aIsForcedValid);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
MOZ_ASSERT(mState > LOADING);
|
|
}
|
|
#endif
|
|
if (mPinned) {
|
|
*aIsForcedValid = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString key;
|
|
nsresult rv = HashingKey(key);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
*aIsForcedValid =
|
|
CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
|
|
LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this,
|
|
*aIsForcedValid));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) {
|
|
LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this,
|
|
aSecondsToTheFuture));
|
|
|
|
nsAutoCString key;
|
|
nsresult rv = HashingKey(key);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key,
|
|
aSecondsToTheFuture);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::MarkForcedValidUse() {
|
|
LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this));
|
|
|
|
nsAutoCString key;
|
|
nsresult rv = HashingKey(key);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID, key);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
nsresult rv = mFile->SetExpirationTime(aExpirationTime);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Aligned assignment, thus atomic.
|
|
mSortingExpirationTime = aExpirationTime;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream** _retval) {
|
|
LOG(("CacheEntry::OpenInputStream [this=%p]", this));
|
|
return OpenInputStreamInternal(offset, nullptr, _retval);
|
|
}
|
|
|
|
nsresult CacheEntry::OpenAlternativeInputStream(const nsACString& type,
|
|
nsIInputStream** _retval) {
|
|
LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
|
|
PromiseFlatCString(type).get()));
|
|
return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
|
|
}
|
|
|
|
nsresult CacheEntry::OpenInputStreamInternal(int64_t offset,
|
|
const char* aAltDataType,
|
|
nsIInputStream** _retval) {
|
|
LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
|
|
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
nsresult rv;
|
|
|
|
RefPtr<CacheEntryHandle> selfHandle = NewHandle();
|
|
|
|
nsCOMPtr<nsIInputStream> stream;
|
|
if (aAltDataType) {
|
|
rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
|
|
getter_AddRefs(stream));
|
|
if (NS_FAILED(rv)) {
|
|
// Failure of this method may be legal when the alternative data requested
|
|
// is not avaialble or of a different type. Console error logs are
|
|
// ensured by CacheFile::OpenAlternativeInputStream.
|
|
return rv;
|
|
}
|
|
} else {
|
|
rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (!mHasData) {
|
|
// So far output stream on this new entry not opened, do it now.
|
|
LOG((" creating phantom output stream"));
|
|
rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
stream.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize,
|
|
nsIOutputStream** _retval) {
|
|
LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
|
|
|
|
nsresult rv;
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
MOZ_ASSERT(mState > EMPTY);
|
|
|
|
if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
|
|
LOG((" entry would exceed size limit"));
|
|
return NS_ERROR_FILE_TOO_BIG;
|
|
}
|
|
|
|
if (mOutputStream && !mIsDoomed) {
|
|
LOG((" giving phantom output stream"));
|
|
mOutputStream.forget(_retval);
|
|
} else {
|
|
rv = OpenOutputStreamInternal(offset, _retval);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// Entry considered ready when writer opens output stream.
|
|
if (mState < READY) mState = READY;
|
|
|
|
// Invoke any pending readers now.
|
|
InvokeCallbacks();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::OpenAlternativeOutputStream(
|
|
const nsACString& type, int64_t predictedSize,
|
|
nsIAsyncOutputStream** _retval) {
|
|
LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
|
|
PromiseFlatCString(type).get()));
|
|
|
|
nsresult rv;
|
|
|
|
if (type.IsEmpty()) {
|
|
// The empty string is reserved to mean no alt-data available.
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
|
|
LOG((" entry not in state to write alt-data"));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
|
|
LOG((" entry would exceed size limit"));
|
|
return NS_ERROR_FILE_TOO_BIG;
|
|
}
|
|
|
|
nsCOMPtr<nsIAsyncOutputStream> stream;
|
|
rv = mFile->OpenAlternativeOutputStream(
|
|
nullptr, PromiseFlatCString(type).get(), getter_AddRefs(stream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
stream.swap(*_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset,
|
|
nsIOutputStream** _retval) {
|
|
LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
|
|
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
if (mIsDoomed) {
|
|
LOG((" doomed..."));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
MOZ_ASSERT(mState > LOADING);
|
|
|
|
nsresult rv;
|
|
|
|
// No need to sync on mUseDisk here, we don't need to be consistent
|
|
// with content of the memory storage entries hash table.
|
|
if (!mUseDisk) {
|
|
rv = mFile->SetMemoryOnly();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
RefPtr<CacheOutputCloseListener> listener =
|
|
new CacheOutputCloseListener(this);
|
|
|
|
nsCOMPtr<nsIOutputStream> stream;
|
|
rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Prevent opening output stream again.
|
|
mHasData = true;
|
|
|
|
stream.swap(*_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
if (mSecurityInfoLoaded) {
|
|
*aSecurityInfo = do_AddRef(mSecurityInfo).take();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
nsCString info;
|
|
nsresult rv = mFile->GetElement("security-info", getter_Copies(info));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
|
|
if (!info.IsVoid()) {
|
|
rv = mozilla::psm::TransportSecurityInfo::Read(
|
|
info, getter_AddRefs(securityInfo));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
if (!securityInfo) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
mSecurityInfo.swap(securityInfo);
|
|
mSecurityInfoLoaded = true;
|
|
|
|
*aSecurityInfo = do_AddRef(mSecurityInfo).take();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) {
|
|
nsresult rv;
|
|
|
|
NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
mSecurityInfo = aSecurityInfo;
|
|
mSecurityInfoLoaded = true;
|
|
}
|
|
|
|
nsCString info;
|
|
if (aSecurityInfo) {
|
|
rv = aSecurityInfo->ToString(info);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) {
|
|
NS_ENSURE_ARG(aStorageDataSize);
|
|
|
|
int64_t dataSize;
|
|
nsresult rv = GetDataSize(&dataSize);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
*aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback* aCallback) {
|
|
LOG(("CacheEntry::AsyncDoom [this=%p]", this));
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (mIsDoomed || mDoomCallback) {
|
|
return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
|
|
}
|
|
|
|
RemoveForcedValidity();
|
|
|
|
mIsDoomed = true;
|
|
mDoomCallback = aCallback;
|
|
}
|
|
|
|
// This immediately removes the entry from the master hashtable and also
|
|
// immediately dooms the file. This way we make sure that any consumer
|
|
// after this point asking for the same entry won't get
|
|
// a) this entry
|
|
// b) a new entry with the same file
|
|
PurgeAndDoom();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetMetaDataElement(const char* aKey, char** aRetval) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->GetElement(aKey, aRetval);
|
|
}
|
|
|
|
nsresult CacheEntry::SetMetaDataElement(const char* aKey, const char* aValue) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->SetElement(aKey, aValue);
|
|
}
|
|
|
|
nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) {
|
|
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
return mFile->VisitMetaData(aVisitor);
|
|
}
|
|
|
|
nsresult CacheEntry::MetaDataReady() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this,
|
|
StateString(mState)));
|
|
|
|
MOZ_ASSERT(mState > EMPTY);
|
|
|
|
if (mState == WRITING) mState = READY;
|
|
|
|
InvokeCallbacks();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::SetValid() {
|
|
nsCOMPtr<nsIOutputStream> outputStream;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
LOG(("CacheEntry::SetValid [this=%p, state=%s]", this,
|
|
StateString(mState)));
|
|
|
|
MOZ_ASSERT(mState > EMPTY);
|
|
|
|
mState = READY;
|
|
mHasData = true;
|
|
|
|
InvokeCallbacks();
|
|
|
|
outputStream.swap(mOutputStream);
|
|
}
|
|
|
|
if (outputStream) {
|
|
LOG((" abandoning phantom output stream"));
|
|
outputStream->Close();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::Recreate(bool aMemoryOnly, nsICacheEntry** _retval) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
|
|
|
|
RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
|
|
if (handle) {
|
|
handle.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
BackgroundOp(Ops::CALLBACKS, true);
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult CacheEntry::GetDataSize(int64_t* aDataSize) {
|
|
LOG(("CacheEntry::GetDataSize [this=%p]", this));
|
|
*aDataSize = 0;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (!mHasData) {
|
|
LOG((" write in progress (no data)"));
|
|
return NS_ERROR_IN_PROGRESS;
|
|
}
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
|
|
|
|
// mayhemer: TODO Problem with compression?
|
|
if (!mFile->DataSize(aDataSize)) {
|
|
LOG((" write in progress (stream active)"));
|
|
return NS_ERROR_IN_PROGRESS;
|
|
}
|
|
|
|
LOG((" size=%" PRId64, *aDataSize));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) {
|
|
LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
|
|
if (NS_FAILED(mFileStatus)) {
|
|
return mFileStatus;
|
|
}
|
|
return mFile->GetAltDataSize(aDataSize);
|
|
}
|
|
|
|
nsresult CacheEntry::GetAltDataType(nsACString& aType) {
|
|
LOG(("CacheEntry::GetAltDataType [this=%p]", this));
|
|
if (NS_FAILED(mFileStatus)) {
|
|
return mFileStatus;
|
|
}
|
|
return mFile->GetAltDataType(aType);
|
|
}
|
|
|
|
nsresult CacheEntry::MarkValid() {
|
|
// NOT IMPLEMENTED ACTUALLY
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::MaybeMarkValid() {
|
|
// NOT IMPLEMENTED ACTUALLY
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool* aWriteAccess) {
|
|
*aWriteAccess = aWriteAllowed;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::Close() {
|
|
// NOT IMPLEMENTED ACTUALLY
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) {
|
|
if (NS_FAILED(mFileStatus)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
|
|
}
|
|
|
|
nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo) {
|
|
nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID);
|
|
if (!info) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
info.forget(aInfo);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIRunnable
|
|
|
|
NS_IMETHODIMP CacheEntry::Run() {
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
BackgroundOp(mBackgroundOperations.Grab());
|
|
return NS_OK;
|
|
}
|
|
|
|
// Management methods
|
|
|
|
double CacheEntry::GetFrecency() const {
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
return mFrecency;
|
|
}
|
|
|
|
uint32_t CacheEntry::GetExpirationTime() const {
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
return mSortingExpirationTime;
|
|
}
|
|
|
|
bool CacheEntry::IsRegistered() const {
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
return mRegistration == REGISTERED;
|
|
}
|
|
|
|
bool CacheEntry::CanRegister() const {
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
return mRegistration == NEVERREGISTERED;
|
|
}
|
|
|
|
void CacheEntry::SetRegistered(bool aRegistered) {
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
|
|
if (aRegistered) {
|
|
MOZ_ASSERT(mRegistration == NEVERREGISTERED);
|
|
mRegistration = REGISTERED;
|
|
} else {
|
|
MOZ_ASSERT(mRegistration == REGISTERED);
|
|
mRegistration = DEREGISTERED;
|
|
}
|
|
}
|
|
|
|
bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned) {
|
|
LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
if (mPinningKnown) {
|
|
LOG((" pinned=%d, caller=%d", (bool)mPinned, aPinned));
|
|
// Bypass when the pin status of this entry doesn't match the pin status
|
|
// caller wants to remove
|
|
return mPinned != aPinned;
|
|
}
|
|
|
|
LOG((" pinning unknown, caller=%d", aPinned));
|
|
// Oterwise, remember to doom after the status is determined for any
|
|
// callback opening the entry after this point...
|
|
Callback c(this, aPinned);
|
|
RememberCallback(c);
|
|
// ...and always bypass
|
|
return true;
|
|
}
|
|
|
|
bool CacheEntry::Purge(uint32_t aWhat) {
|
|
LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
|
|
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
|
|
switch (aWhat) {
|
|
case PURGE_DATA_ONLY_DISK_BACKED:
|
|
case PURGE_WHOLE_ONLY_DISK_BACKED:
|
|
// This is an in-memory only entry, don't purge it
|
|
if (!mUseDisk) {
|
|
LOG((" not using disk"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (mState == WRITING || mState == LOADING || mFrecency == 0) {
|
|
// In-progress (write or load) entries should (at least for consistency
|
|
// and from the logical point of view) stay in memory. Zero-frecency
|
|
// entries are those which have never been given to any consumer, those
|
|
// are actually very fresh and should not go just because frecency had not
|
|
// been set so far.
|
|
LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
|
|
// The file is used when there are open streams or chunks/metadata still
|
|
// waiting for write. In this case, this entry cannot be purged,
|
|
// otherwise reopenned entry would may not even find the data on disk -
|
|
// CacheFile is not shared and cannot be left orphan when its job is not
|
|
// done, hence keep the whole entry.
|
|
LOG((" file still under use"));
|
|
return false;
|
|
}
|
|
|
|
switch (aWhat) {
|
|
case PURGE_WHOLE_ONLY_DISK_BACKED:
|
|
case PURGE_WHOLE: {
|
|
if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
|
|
LOG((" not purging, still referenced"));
|
|
return false;
|
|
}
|
|
|
|
CacheStorageService::Self()->UnregisterEntry(this);
|
|
|
|
// Entry removed it self from control arrays, return true
|
|
return true;
|
|
}
|
|
|
|
case PURGE_DATA_ONLY_DISK_BACKED: {
|
|
NS_ENSURE_SUCCESS(mFileStatus, false);
|
|
|
|
mFile->ThrowMemoryCachedData();
|
|
|
|
// Entry has been left in control arrays, return false (not purged)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
LOG((" ?"));
|
|
return false;
|
|
}
|
|
|
|
void CacheEntry::PurgeAndDoom() {
|
|
LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
|
|
|
|
CacheStorageService::Self()->RemoveEntry(this);
|
|
DoomAlreadyRemoved();
|
|
}
|
|
|
|
void CacheEntry::DoomAlreadyRemoved() {
|
|
LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
|
|
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
RemoveForcedValidity();
|
|
|
|
mIsDoomed = true;
|
|
|
|
// Pretend pinning is know. This entry is now doomed for good, so don't
|
|
// bother with defering doom because of unknown pinning state any more.
|
|
mPinningKnown = true;
|
|
|
|
// This schedules dooming of the file, dooming is ensured to happen
|
|
// sooner than demand to open the same file made after this point
|
|
// so that we don't get this file for any newer opened entry(s).
|
|
DoomFile();
|
|
|
|
// Must force post here since may be indirectly called from
|
|
// InvokeCallbacks of this entry and we don't want reentrancy here.
|
|
BackgroundOp(Ops::CALLBACKS, true);
|
|
// Process immediately when on the management thread.
|
|
BackgroundOp(Ops::UNREGISTER);
|
|
}
|
|
|
|
void CacheEntry::DoomFile() {
|
|
nsresult rv = NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (NS_SUCCEEDED(mFileStatus)) {
|
|
if (mHandlesCount == 0 || (mHandlesCount == 1 && mWriter)) {
|
|
// We kill the file also when there is just reference from the writer,
|
|
// no one else could ever reach the written data. Obvisouly also
|
|
// when there is no reference at all (should we ever end up here
|
|
// in that case.)
|
|
// Tell the file to kill the handle, i.e. bypass any I/O operations
|
|
// on it except removing the file.
|
|
mFile->Kill();
|
|
}
|
|
|
|
// Always calls the callback asynchronously.
|
|
rv = mFile->Doom(mDoomCallback ? this : nullptr);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
LOG((" file doomed"));
|
|
return;
|
|
}
|
|
|
|
if (NS_ERROR_FILE_NOT_FOUND == rv) {
|
|
// File is set to be just memory-only, notify the callbacks
|
|
// and pretend dooming has succeeded. From point of view of
|
|
// the entry it actually did - the data is gone and cannot be
|
|
// reused.
|
|
rv = NS_OK;
|
|
}
|
|
}
|
|
|
|
// Always posts to the main thread.
|
|
OnFileDoomed(rv);
|
|
}
|
|
|
|
void CacheEntry::RemoveForcedValidity() {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
nsresult rv;
|
|
|
|
if (mIsDoomed) {
|
|
return;
|
|
}
|
|
|
|
nsAutoCString entryKey;
|
|
rv = HashingKey(entryKey);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey);
|
|
}
|
|
|
|
void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
|
|
if (mBackgroundOperations.Set(aOperations)) {
|
|
CacheStorageService::Self()->Dispatch(this);
|
|
}
|
|
|
|
LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
|
|
return;
|
|
}
|
|
|
|
{
|
|
mozilla::MutexAutoUnlock unlock(mLock);
|
|
|
|
MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
|
|
|
|
if (aOperations & Ops::FRECENCYUPDATE) {
|
|
++mUseCount;
|
|
|
|
#ifndef M_LN2
|
|
# define M_LN2 0.69314718055994530942
|
|
#endif
|
|
|
|
// Half-life is dynamic, in seconds.
|
|
static double half_life = CacheObserver::HalfLifeSeconds();
|
|
// Must convert from seconds to milliseconds since PR_Now() gives usecs.
|
|
static double const decay =
|
|
(M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
|
|
|
|
double now_decay = static_cast<double>(PR_Now()) * decay;
|
|
|
|
if (mFrecency == 0) {
|
|
mFrecency = now_decay;
|
|
} else {
|
|
// TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n +
|
|
// 1) but more precise.
|
|
mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
|
|
}
|
|
LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this,
|
|
mFrecency));
|
|
|
|
// Because CacheFile::Set*() are not thread-safe to use (uses
|
|
// WeakReference that is not thread-safe) we must post to the main
|
|
// thread...
|
|
NS_DispatchToMainThread(
|
|
NewRunnableMethod<double>("net::CacheEntry::StoreFrecency", this,
|
|
&CacheEntry::StoreFrecency, mFrecency));
|
|
}
|
|
|
|
if (aOperations & Ops::REGISTER) {
|
|
LOG(("CacheEntry REGISTER [this=%p]", this));
|
|
|
|
CacheStorageService::Self()->RegisterEntry(this);
|
|
}
|
|
|
|
if (aOperations & Ops::UNREGISTER) {
|
|
LOG(("CacheEntry UNREGISTER [this=%p]", this));
|
|
|
|
CacheStorageService::Self()->UnregisterEntry(this);
|
|
}
|
|
} // unlock
|
|
|
|
if (aOperations & Ops::CALLBACKS) {
|
|
LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
|
|
|
|
InvokeCallbacks();
|
|
}
|
|
}
|
|
|
|
void CacheEntry::StoreFrecency(double aFrecency) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_SUCCEEDED(mFileStatus)) {
|
|
mFile->SetFrecency(FRECENCY2INT(aFrecency));
|
|
}
|
|
}
|
|
|
|
// CacheOutputCloseListener
|
|
|
|
CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
|
|
: Runnable("net::CacheOutputCloseListener"), mEntry(aEntry) {}
|
|
|
|
void CacheOutputCloseListener::OnOutputClosed() {
|
|
// We need this class and to redispatch since this callback is invoked
|
|
// under the file's lock and to do the job we need to enter the entry's
|
|
// lock too. That would lead to potential deadlocks.
|
|
|
|
if (NS_IsMainThread()) {
|
|
// If we're already on the main thread, dispatch to the main thread instead
|
|
// of the sts. Always dispatching to the sts can cause problems late in
|
|
// shutdown, when threadpools may no longer be available (bug 1806332).
|
|
//
|
|
// This may also avoid some unnecessary thread-hops when invoking callbacks,
|
|
// which can require that they be called on the main thread.
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(do_AddRef(this)));
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIEventTarget> sts =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
MOZ_DIAGNOSTIC_ASSERT(sts);
|
|
MOZ_ALWAYS_SUCCEEDS(sts->Dispatch(do_AddRef(this)));
|
|
}
|
|
|
|
NS_IMETHODIMP CacheOutputCloseListener::Run() {
|
|
mEntry->OnOutputClosed();
|
|
return NS_OK;
|
|
}
|
|
|
|
// Memory reporting
|
|
|
|
size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
|
|
size_t n = 0;
|
|
|
|
MutexAutoLock lock(mLock);
|
|
n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
|
|
if (mFile) {
|
|
n += mFile->SizeOfIncludingThis(mallocSizeOf);
|
|
}
|
|
|
|
n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf);
|
|
n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
|
|
n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
|
|
|
|
// mDoomCallback is an arbitrary class that is probably reported elsewhere.
|
|
// mOutputStream is reported in mFile.
|
|
// mWriter is one of many handles we create, but (intentionally) not keep
|
|
// any reference to, so those unfortunately cannot be reported. Handles are
|
|
// small, though.
|
|
// mSecurityInfo doesn't impl nsISizeOf.
|
|
|
|
return n;
|
|
}
|
|
|
|
size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
|
|
return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
|
|
}
|
|
|
|
} // namespace mozilla::net
|