/* 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/. */
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));
}
// 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();
}
// 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();
}
// 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);
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;
}
charconst* 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";
}
// 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);
}
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);
}
}
// 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);
}
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.
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);
}
// 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));
// 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();
}
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);
}
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;
}
// 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")); returnfalse;
}
// 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. returntrue;
}
}
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);
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")); returnfalse;
}
// Entry is complete now, do the check+avail call again
aCallback.mRecheckAfterWrite = false; return InvokeCallback(aCallback);
}
// 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.
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();
}
}
// 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);
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;
} elseif (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.
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;
}
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);
}
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);
}
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);
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();
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;
}
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 returntrue;
}
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")); returnfalse;
}
}
{
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)); returnfalse;
}
}
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")); returnfalse;
}
switch (aWhat) { case PURGE_WHOLE_ONLY_DISK_BACKED: case PURGE_WHOLE: { if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
LOG((" not purging, still referenced")); returnfalse;
}
// 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);
}
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);
}
if (!CacheStorageService::IsOnManagementThread() || aForceAsync) { if (mBackgroundOperations.Set(aOperations)) {
CacheStorageService::Self()->Dispatch(this);
}
LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations)); return;
}
// Half-life is dynamic, in seconds. staticdouble half_life = CacheObserver::HalfLifeSeconds(); // Must convert from seconds to milliseconds since PR_Now() gives usecs. staticdoubleconst decay =
(M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
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));
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. // This function may be reached while XPCOM is already shutting down, // and we might be unable to obtain the main thread or the sts. #1826661
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.
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.
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.