michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CacheLog.h" michael@0: #include "CacheEntry.h" michael@0: #include "CacheStorageService.h" michael@0: #include "CacheObserver.h" michael@0: #include "CacheFileUtils.h" michael@0: michael@0: #include "nsIInputStream.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "nsIURI.h" michael@0: #include "nsICacheEntryOpenCallback.h" michael@0: #include "nsICacheStorage.h" michael@0: #include "nsISerializable.h" michael@0: #include "nsIStreamTransportService.h" michael@0: #include "nsISizeOf.h" michael@0: michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsSerializationHelper.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: static uint32_t const ENTRY_WANTED = michael@0: nsICacheEntryOpenCallback::ENTRY_WANTED; michael@0: static uint32_t const RECHECK_AFTER_WRITE_FINISHED = michael@0: nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED; michael@0: static uint32_t const ENTRY_NEEDS_REVALIDATION = michael@0: nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION; michael@0: static uint32_t const ENTRY_NOT_WANTED = michael@0: nsICacheEntryOpenCallback::ENTRY_NOT_WANTED; michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry) michael@0: michael@0: // CacheEntryHandle michael@0: michael@0: CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) michael@0: : mEntry(aEntry) michael@0: { michael@0: MOZ_COUNT_CTOR(CacheEntryHandle); michael@0: michael@0: #ifdef DEBUG michael@0: if (!mEntry->HandlesCount()) { michael@0: // CacheEntry.mHandlesCount must go from zero to one only under michael@0: // the service lock. Can access CacheStorageService::Self() w/o a check michael@0: // since CacheEntry hrefs it. michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: } michael@0: #endif michael@0: michael@0: mEntry->AddHandleRef(); michael@0: michael@0: LOG(("New CacheEntryHandle %p for entry %p", this, aEntry)); michael@0: } michael@0: michael@0: CacheEntryHandle::~CacheEntryHandle() michael@0: { michael@0: mEntry->ReleaseHandleRef(); michael@0: mEntry->OnHandleClosed(this); michael@0: michael@0: MOZ_COUNT_DTOR(CacheEntryHandle); michael@0: } michael@0: michael@0: // CacheEntry::Callback michael@0: michael@0: CacheEntry::Callback::Callback(CacheEntry* aEntry, michael@0: nsICacheEntryOpenCallback *aCallback, michael@0: bool aReadOnly, bool aCheckOnAnyThread) michael@0: : mEntry(aEntry) michael@0: , mCallback(aCallback) michael@0: , mTargetThread(do_GetCurrentThread()) michael@0: , mReadOnly(aReadOnly) michael@0: , mCheckOnAnyThread(aCheckOnAnyThread) michael@0: , mRecheckAfterWrite(false) michael@0: , mNotWanted(false) michael@0: { michael@0: MOZ_COUNT_CTOR(CacheEntry::Callback); michael@0: michael@0: // The counter may go from zero to non-null only under the service lock michael@0: // but here we expect it to be already positive. michael@0: MOZ_ASSERT(mEntry->HandlesCount()); michael@0: mEntry->AddHandleRef(); michael@0: } michael@0: michael@0: CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat) michael@0: : mEntry(aThat.mEntry) michael@0: , mCallback(aThat.mCallback) michael@0: , mTargetThread(aThat.mTargetThread) michael@0: , mReadOnly(aThat.mReadOnly) michael@0: , mCheckOnAnyThread(aThat.mCheckOnAnyThread) michael@0: , mRecheckAfterWrite(aThat.mRecheckAfterWrite) michael@0: , mNotWanted(aThat.mNotWanted) michael@0: { michael@0: MOZ_COUNT_CTOR(CacheEntry::Callback); michael@0: michael@0: // The counter may go from zero to non-null only under the service lock michael@0: // but here we expect it to be already positive. michael@0: MOZ_ASSERT(mEntry->HandlesCount()); michael@0: mEntry->AddHandleRef(); michael@0: } michael@0: michael@0: CacheEntry::Callback::~Callback() michael@0: { michael@0: ProxyRelease(mCallback, mTargetThread); michael@0: michael@0: mEntry->ReleaseHandleRef(); michael@0: MOZ_COUNT_DTOR(CacheEntry::Callback); michael@0: } michael@0: michael@0: void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) michael@0: { michael@0: if (mEntry == aEntry) michael@0: return; michael@0: michael@0: // The counter may go from zero to non-null only under the service lock michael@0: // but here we expect it to be already positive. michael@0: MOZ_ASSERT(aEntry->HandlesCount()); michael@0: aEntry->AddHandleRef(); michael@0: mEntry->ReleaseHandleRef(); michael@0: mEntry = aEntry; michael@0: } michael@0: michael@0: nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const michael@0: { michael@0: if (!mCheckOnAnyThread) { michael@0: // Check we are on the target michael@0: return mTargetThread->IsOnCurrentThread(aOnCheckThread); michael@0: } michael@0: michael@0: // We can invoke check anywhere michael@0: *aOnCheckThread = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const michael@0: { michael@0: return mTargetThread->IsOnCurrentThread(aOnAvailThread); michael@0: } michael@0: michael@0: // CacheEntry michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheEntry, michael@0: nsICacheEntry, michael@0: nsIRunnable, michael@0: CacheFileListener) michael@0: michael@0: CacheEntry::CacheEntry(const nsACString& aStorageID, michael@0: nsIURI* aURI, michael@0: const nsACString& aEnhanceID, michael@0: bool aUseDisk) michael@0: : mFrecency(0) michael@0: , mSortingExpirationTime(uint32_t(-1)) michael@0: , mLock("CacheEntry") michael@0: , mFileStatus(NS_ERROR_NOT_INITIALIZED) michael@0: , mURI(aURI) michael@0: , mEnhanceID(aEnhanceID) michael@0: , mStorageID(aStorageID) michael@0: , mUseDisk(aUseDisk) michael@0: , mIsDoomed(false) michael@0: , mSecurityInfoLoaded(false) michael@0: , mPreventCallbacks(false) michael@0: , mHasData(false) michael@0: , mState(NOTLOADED) michael@0: , mRegistration(NEVERREGISTERED) michael@0: , mWriter(nullptr) michael@0: , mPredictedDataSize(0) michael@0: , mReleaseThread(NS_GetCurrentThread()) michael@0: { michael@0: MOZ_COUNT_CTOR(CacheEntry); michael@0: michael@0: mService = CacheStorageService::Self(); michael@0: michael@0: CacheStorageService::Self()->RecordMemoryOnlyEntry( michael@0: this, !aUseDisk, true /* overwrite */); michael@0: } michael@0: michael@0: CacheEntry::~CacheEntry() michael@0: { michael@0: ProxyRelease(mURI, mReleaseThread); michael@0: michael@0: LOG(("CacheEntry::~CacheEntry [this=%p]", this)); michael@0: MOZ_COUNT_DTOR(CacheEntry); michael@0: } michael@0: michael@0: #ifdef PR_LOG michael@0: michael@0: char const * CacheEntry::StateString(uint32_t aState) michael@0: { michael@0: switch (aState) { michael@0: case NOTLOADED: return "NOTLOADED"; michael@0: case LOADING: return "LOADING"; michael@0: case EMPTY: return "EMPTY"; michael@0: case WRITING: return "WRITING"; michael@0: case READY: return "READY"; michael@0: case REVALIDATING: return "REVALIDATING"; michael@0: } michael@0: michael@0: return "?"; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult) michael@0: { michael@0: return HashingKey(mStorageID, mEnhanceID, mURI, aResult); michael@0: } michael@0: michael@0: nsresult CacheEntry::HashingKey(nsACString &aResult) michael@0: { michael@0: return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult); michael@0: } michael@0: michael@0: // static michael@0: nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID, michael@0: nsCSubstring const& aEnhanceID, michael@0: nsIURI* aURI, michael@0: nsACString &aResult) michael@0: { michael@0: nsAutoCString spec; michael@0: nsresult rv = aURI->GetAsciiSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return HashingKey(aStorageID, aEnhanceID, spec, aResult); michael@0: } michael@0: michael@0: // static michael@0: nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID, michael@0: nsCSubstring const& aEnhanceID, michael@0: nsCSubstring const& aURISpec, michael@0: nsACString &aResult) michael@0: { michael@0: /** michael@0: * This key is used to salt hash that is a base for disk file name. michael@0: * Changing it will cause we will not be able to find files on disk. michael@0: */ michael@0: michael@0: aResult.Append(aStorageID); michael@0: michael@0: if (!aEnhanceID.IsEmpty()) { michael@0: CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID); michael@0: } michael@0: michael@0: // Appending directly michael@0: aResult.Append(':'); michael@0: aResult.Append(aURISpec); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags) michael@0: { michael@0: LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", michael@0: this, StateString(mState), aFlags, aCallback)); michael@0: michael@0: bool readonly = aFlags & nsICacheStorage::OPEN_READONLY; michael@0: bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE; michael@0: bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY; michael@0: bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED; michael@0: michael@0: MOZ_ASSERT(!readonly || !truncate, "Bad flags combination"); michael@0: MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry"); michael@0: michael@0: Callback callback(this, aCallback, readonly, multithread); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: RememberCallback(callback); michael@0: michael@0: // Load() opens the lock michael@0: if (Load(truncate, priority)) { michael@0: // Loading is in progress... michael@0: return; michael@0: } michael@0: michael@0: InvokeCallbacks(); michael@0: } michael@0: michael@0: bool CacheEntry::Load(bool aTruncate, bool aPriority) michael@0: { michael@0: LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate)); michael@0: michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: if (mState > LOADING) { michael@0: LOG((" already loaded")); michael@0: return false; michael@0: } michael@0: michael@0: if (mState == LOADING) { michael@0: LOG((" already loading")); michael@0: return true; michael@0: } michael@0: michael@0: mState = LOADING; michael@0: michael@0: MOZ_ASSERT(!mFile); michael@0: michael@0: bool directLoad = aTruncate || !mUseDisk; michael@0: if (directLoad) michael@0: mFileStatus = NS_OK; michael@0: else michael@0: mLoadStart = TimeStamp::Now(); michael@0: michael@0: mFile = new CacheFile(); michael@0: michael@0: BackgroundOp(Ops::REGISTER); michael@0: michael@0: { michael@0: mozilla::MutexAutoUnlock unlock(mLock); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsAutoCString fileKey; michael@0: rv = HashingKeyWithStorage(fileKey); michael@0: michael@0: LOG((" performing load, file=%p", mFile.get())); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mFile->Init(fileKey, michael@0: aTruncate, michael@0: !mUseDisk, michael@0: aPriority, michael@0: directLoad ? nullptr : this); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: mFileStatus = rv; michael@0: AsyncDoom(nullptr); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (directLoad) { michael@0: // Just fake the load has already been done as "new". michael@0: mState = EMPTY; michael@0: } michael@0: michael@0: return mState == LOADING; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) michael@0: { michael@0: LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]", michael@0: this, aResult, aIsNew)); michael@0: michael@0: MOZ_ASSERT(!mLoadStart.IsNull()); michael@0: michael@0: if (NS_SUCCEEDED(aResult)) { michael@0: if (aIsNew) { michael@0: mozilla::Telemetry::AccumulateTimeDelta( michael@0: mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS, michael@0: mLoadStart); michael@0: } michael@0: else { michael@0: mozilla::Telemetry::AccumulateTimeDelta( michael@0: mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS, michael@0: mLoadStart); michael@0: } michael@0: } michael@0: michael@0: // OnFileReady, that is the only code that can transit from LOADING michael@0: // to any follow-on state, can only be invoked ones on an entry, michael@0: // thus no need to lock. Until this moment there is no consumer that michael@0: // could manipulate the entry state. michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: MOZ_ASSERT(mState == LOADING); michael@0: michael@0: mState = (aIsNew || NS_FAILED(aResult)) michael@0: ? EMPTY michael@0: : READY; michael@0: michael@0: mFileStatus = aResult; michael@0: michael@0: if (mState == READY) { michael@0: mHasData = true; michael@0: michael@0: uint32_t frecency; michael@0: mFile->GetFrecency(&frecency); michael@0: // mFrecency is held in a double to increase computance precision. michael@0: // It is ok to persist frecency only as a uint32 with some math involved. michael@0: mFrecency = INT2FRECENCY(frecency); michael@0: } michael@0: michael@0: InvokeCallbacks(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) michael@0: { michael@0: if (mDoomCallback) { michael@0: nsRefPtr event = michael@0: new DoomCallbackRunnable(this, aResult); michael@0: NS_DispatchToMainThread(event); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed CacheEntry::ReopenTruncated(bool aMemoryOnly, michael@0: nsICacheEntryOpenCallback* aCallback) michael@0: { michael@0: LOG(("CacheEntry::ReopenTruncated [this=%p]", this)); michael@0: michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly michael@0: mPreventCallbacks = true; michael@0: michael@0: nsRefPtr handle; michael@0: nsRefPtr newEntry; michael@0: { michael@0: mozilla::MutexAutoUnlock unlock(mLock); michael@0: michael@0: // The following call dooms this entry (calls DoomAlreadyRemoved on us) michael@0: nsresult rv = CacheStorageService::Self()->AddStorageEntry( michael@0: GetStorageID(), GetURI(), GetEnhanceID(), michael@0: mUseDisk && !aMemoryOnly, michael@0: true, // always create michael@0: true, // truncate existing (this one) michael@0: getter_AddRefs(handle)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: newEntry = handle->Entry(); michael@0: LOG((" exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv)); michael@0: newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE); michael@0: } else { michael@0: LOG((" exchanged of entry %p failed, rv=0x%08x", this, rv)); michael@0: AsyncDoom(nullptr); michael@0: } michael@0: } michael@0: michael@0: mPreventCallbacks = false; michael@0: michael@0: if (!newEntry) michael@0: return nullptr; michael@0: michael@0: newEntry->TransferCallbacks(*this); michael@0: mCallbacks.Clear(); michael@0: michael@0: return handle.forget(); michael@0: } michael@0: michael@0: void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry) michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", michael@0: this, &aFromEntry)); michael@0: michael@0: if (!mCallbacks.Length()) michael@0: mCallbacks.SwapElements(aFromEntry.mCallbacks); michael@0: else michael@0: mCallbacks.AppendElements(aFromEntry.mCallbacks); michael@0: michael@0: uint32_t callbacksLength = mCallbacks.Length(); michael@0: if (callbacksLength) { michael@0: // Carry the entry reference (unfortunatelly, needs to be done manually...) michael@0: for (uint32_t i = 0; i < callbacksLength; ++i) michael@0: mCallbacks[i].ExchangeEntry(this); michael@0: michael@0: BackgroundOp(Ops::CALLBACKS, true); michael@0: } michael@0: } michael@0: michael@0: void CacheEntry::RememberCallback(Callback const& aCallback) michael@0: { michael@0: LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback.mCallback.get())); michael@0: michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: mCallbacks.AppendElement(aCallback); michael@0: } michael@0: michael@0: void CacheEntry::InvokeCallbacksLock() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: InvokeCallbacks(); michael@0: } michael@0: michael@0: void CacheEntry::InvokeCallbacks() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this)); michael@0: michael@0: // Invoke first all r/w callbacks, then all r/o callbacks. michael@0: if (InvokeCallbacks(false)) michael@0: InvokeCallbacks(true); michael@0: michael@0: LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this)); michael@0: } michael@0: michael@0: bool CacheEntry::InvokeCallbacks(bool aReadOnly) michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: uint32_t i = 0; michael@0: while (i < mCallbacks.Length()) { michael@0: if (mPreventCallbacks) { michael@0: LOG((" callbacks prevented!")); michael@0: return false; michael@0: } michael@0: michael@0: if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) { michael@0: LOG((" entry is being written/revalidated")); michael@0: return false; michael@0: } michael@0: michael@0: if (mCallbacks[i].mReadOnly != aReadOnly) { michael@0: // Callback is not r/w or r/o, go to another one in line michael@0: ++i; michael@0: continue; michael@0: } michael@0: michael@0: bool onCheckThread; michael@0: nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread); michael@0: michael@0: if (NS_SUCCEEDED(rv) && !onCheckThread) { michael@0: // Redispatch to the target thread michael@0: nsRefPtr > event = michael@0: NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock); michael@0: michael@0: rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: LOG((" re-dispatching to target thread")); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: Callback callback = mCallbacks[i]; michael@0: mCallbacks.RemoveElementAt(i); michael@0: michael@0: if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) { michael@0: // Callback didn't fire, put it back and go to another one in line. michael@0: // Only reason InvokeCallback returns false is that onCacheEntryCheck michael@0: // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other michael@0: // readers or potential writers would be unnecessarily kept from being michael@0: // invoked. michael@0: mCallbacks.InsertElementAt(i, callback); michael@0: ++i; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool CacheEntry::InvokeCallback(Callback & aCallback) michael@0: { michael@0: LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", michael@0: this, StateString(mState), aCallback.mCallback.get())); michael@0: michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: // When this entry is doomed we want to notify the callback any time michael@0: if (!mIsDoomed) { michael@0: // When we are here, the entry must be loaded from disk michael@0: MOZ_ASSERT(mState > LOADING); michael@0: michael@0: if (mState == WRITING || mState == REVALIDATING) { michael@0: // Prevent invoking other callbacks since one of them is now writing michael@0: // or revalidating this entry. No consumers should get this entry michael@0: // until metadata are filled with values downloaded from the server michael@0: // or the entry revalidated and output stream has been opened. michael@0: LOG((" entry is being written/revalidated, callback bypassed")); michael@0: return false; michael@0: } michael@0: michael@0: // mRecheckAfterWrite flag already set means the callback has already passed michael@0: // the onCacheEntryCheck call. Until the current write is not finished this michael@0: // callback will be bypassed. michael@0: if (!aCallback.mRecheckAfterWrite) { michael@0: michael@0: if (!aCallback.mReadOnly) { michael@0: if (mState == EMPTY) { michael@0: // Advance to writing state, we expect to invoke the callback and let michael@0: // it fill content of this entry. Must set and check the state here michael@0: // to prevent more then one michael@0: mState = WRITING; michael@0: LOG((" advancing to WRITING state")); michael@0: } michael@0: michael@0: if (!aCallback.mCallback) { michael@0: // We can be given no callback only in case of recreate, it is ok michael@0: // to advance to WRITING state since the caller of recreate is expected michael@0: // to write this entry now. michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (mState == READY) { michael@0: // Metadata present, validate the entry michael@0: uint32_t checkResult; michael@0: { michael@0: // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck michael@0: mozilla::MutexAutoUnlock unlock(mLock); michael@0: michael@0: nsresult rv = aCallback.mCallback->OnCacheEntryCheck( michael@0: this, nullptr, &checkResult); michael@0: LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult)); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: checkResult = ENTRY_NOT_WANTED; michael@0: } michael@0: michael@0: switch (checkResult) { michael@0: case ENTRY_WANTED: michael@0: // Nothing more to do here, the consumer is responsible to handle michael@0: // the result of OnCacheEntryCheck it self. michael@0: // Proceed to callback... michael@0: break; michael@0: michael@0: case RECHECK_AFTER_WRITE_FINISHED: michael@0: LOG((" consumer will check on the entry again after write is done")); michael@0: // The consumer wants the entry to complete first. michael@0: aCallback.mRecheckAfterWrite = true; michael@0: break; michael@0: michael@0: case ENTRY_NEEDS_REVALIDATION: michael@0: LOG((" will be holding callbacks until entry is revalidated")); michael@0: // State is READY now and from that state entry cannot transit to any other michael@0: // state then REVALIDATING for which cocurrency is not an issue. Potentially michael@0: // no need to lock here. michael@0: mState = REVALIDATING; michael@0: break; michael@0: michael@0: case ENTRY_NOT_WANTED: michael@0: LOG((" consumer not interested in the entry")); michael@0: // Do not give this entry to the consumer, it is not interested in us. michael@0: aCallback.mNotWanted = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aCallback.mCallback) { michael@0: if (!mIsDoomed && aCallback.mRecheckAfterWrite) { michael@0: // If we don't have data and the callback wants a complete entry, michael@0: // don't invoke now. michael@0: bool bypass = !mHasData; michael@0: if (!bypass) { michael@0: int64_t _unused; michael@0: bypass = !mFile->DataSize(&_unused); michael@0: } michael@0: michael@0: if (bypass) { michael@0: LOG((" bypassing, entry data still being written")); michael@0: return false; michael@0: } michael@0: michael@0: // Entry is complete now, do the check+avail call again michael@0: aCallback.mRecheckAfterWrite = false; michael@0: return InvokeCallback(aCallback); michael@0: } michael@0: michael@0: mozilla::MutexAutoUnlock unlock(mLock); michael@0: InvokeAvailableCallback(aCallback); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void CacheEntry::InvokeAvailableCallback(Callback const & aCallback) michael@0: { michael@0: LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]", michael@0: this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted)); michael@0: michael@0: nsresult rv; michael@0: michael@0: uint32_t const state = mState; michael@0: michael@0: // When we are here, the entry must be loaded from disk michael@0: MOZ_ASSERT(state > LOADING || mIsDoomed); michael@0: michael@0: bool onAvailThread; michael@0: rv = aCallback.OnAvailThread(&onAvailThread); michael@0: if (NS_FAILED(rv)) { michael@0: LOG((" target thread dead?")); michael@0: return; michael@0: } michael@0: michael@0: if (!onAvailThread) { michael@0: // Dispatch to the right thread michael@0: nsRefPtr event = michael@0: new AvailableCallbackRunnable(this, aCallback); michael@0: michael@0: rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: LOG((" redispatched, (rv = 0x%08x)", rv)); michael@0: return; michael@0: } michael@0: michael@0: if (mIsDoomed || aCallback.mNotWanted) { michael@0: LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND")); michael@0: aCallback.mCallback->OnCacheEntryAvailable( michael@0: nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND); michael@0: return; michael@0: } michael@0: michael@0: if (state == READY) { michael@0: LOG((" ready/has-meta, notifying OCEA with entry and NS_OK")); michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: BackgroundOp(Ops::FRECENCYUPDATE); michael@0: } michael@0: michael@0: nsRefPtr handle = NewHandle(); michael@0: aCallback.mCallback->OnCacheEntryAvailable( michael@0: handle, false, nullptr, NS_OK); michael@0: return; michael@0: } michael@0: michael@0: if (aCallback.mReadOnly) { michael@0: LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND")); michael@0: aCallback.mCallback->OnCacheEntryAvailable( michael@0: nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND); michael@0: return; michael@0: } michael@0: michael@0: // This is a new or potentially non-valid entry and needs to be fetched first. michael@0: // The CacheEntryHandle blocks other consumers until the channel michael@0: // either releases the entry or marks metadata as filled or whole entry valid, michael@0: // i.e. until MetaDataReady() or SetValid() on the entry is called respectively. michael@0: michael@0: // Consumer will be responsible to fill or validate the entry metadata and data. michael@0: michael@0: nsRefPtr handle = NewWriteHandle(); michael@0: rv = aCallback.mCallback->OnCacheEntryAvailable( michael@0: handle, state == WRITING, nullptr, NS_OK); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG((" writing/revalidating failed (0x%08x)", rv)); michael@0: michael@0: // Consumer given a new entry failed to take care of the entry. michael@0: OnHandleClosed(handle); michael@0: return; michael@0: } michael@0: michael@0: LOG((" writing/revalidating")); michael@0: } michael@0: michael@0: CacheEntryHandle* CacheEntry::NewHandle() michael@0: { michael@0: return new CacheEntryHandle(this); michael@0: } michael@0: michael@0: CacheEntryHandle* CacheEntry::NewWriteHandle() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: BackgroundOp(Ops::FRECENCYUPDATE); michael@0: return (mWriter = new CacheEntryHandle(this)); michael@0: } michael@0: michael@0: void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) michael@0: { michael@0: LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle)); michael@0: michael@0: nsCOMPtr outputStream; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (mWriter != aHandle) { michael@0: LOG((" not the writer")); michael@0: return; michael@0: } michael@0: michael@0: if (mOutputStream) { michael@0: // No one took our internal output stream, so there are no data michael@0: // and output stream has to be open symultaneously with input stream michael@0: // on this entry again. michael@0: mHasData = false; michael@0: } michael@0: michael@0: outputStream.swap(mOutputStream); michael@0: mWriter = nullptr; michael@0: michael@0: if (mState == WRITING) { michael@0: LOG((" reverting to state EMPTY - write failed")); michael@0: mState = EMPTY; michael@0: } michael@0: else if (mState == REVALIDATING) { michael@0: LOG((" reverting to state READY - reval failed")); michael@0: mState = READY; michael@0: } michael@0: michael@0: InvokeCallbacks(); michael@0: } michael@0: michael@0: if (outputStream) { michael@0: LOG((" abandoning phantom output stream")); michael@0: outputStream->Close(); michael@0: } michael@0: } michael@0: michael@0: void CacheEntry::OnOutputClosed() michael@0: { michael@0: // Called when the file's output stream is closed. Invoke any callbacks michael@0: // waiting for complete entry. michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: InvokeCallbacks(); michael@0: } michael@0: michael@0: bool CacheEntry::IsUsingDiskLocked() const michael@0: { michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: michael@0: return IsUsingDisk(); michael@0: } michael@0: michael@0: bool CacheEntry::SetUsingDisk(bool aUsingDisk) michael@0: { michael@0: // Called by the service when this entry is reopen to reflect michael@0: // demanded storage target. michael@0: michael@0: if (mState >= READY) { michael@0: // Don't modify after this entry has been filled. michael@0: return false; michael@0: } michael@0: michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: michael@0: bool changed = mUseDisk != aUsingDisk; michael@0: mUseDisk = aUsingDisk; michael@0: return changed; michael@0: } michael@0: michael@0: bool CacheEntry::IsReferenced() const michael@0: { michael@0: CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); michael@0: michael@0: // Increasing this counter from 0 to non-null and this check both happen only michael@0: // under the service lock. michael@0: return mHandlesCount > 0; michael@0: } michael@0: michael@0: bool CacheEntry::IsFileDoomed() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (NS_SUCCEEDED(mFileStatus)) { michael@0: return mFile->IsDoomed(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: uint32_t CacheEntry::GetMetadataMemoryConsumption() michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, 0); michael@0: michael@0: uint32_t size; michael@0: if (NS_FAILED(mFile->ElementsSize(&size))) michael@0: return 0; michael@0: michael@0: return size; michael@0: } michael@0: michael@0: // nsICacheEntry michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk) michael@0: { michael@0: // No need to sync when only reading. michael@0: // When consumer needs to be consistent with state of the memory storage entries michael@0: // table, then let it use GetUseDisk getter that must be called under the service lock. michael@0: *aPersistToDisk = mUseDisk; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey) michael@0: { michael@0: return mURI->GetAsciiSpec(aKey); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mFile->GetFetchCount(reinterpret_cast(aFetchCount)); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mFile->GetLastFetched(aLastFetched); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mFile->GetLastModified(aLastModified); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mFile->GetExpirationTime(aExpirationTime); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsresult rv = mFile->SetExpirationTime(aExpirationTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Aligned assignment, thus atomic. michael@0: mSortingExpirationTime = aExpirationTime; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval) michael@0: { michael@0: LOG(("CacheEntry::OpenInputStream [this=%p]", this)); michael@0: michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr stream; michael@0: rv = mFile->OpenInputStream(getter_AddRefs(stream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr seekable = michael@0: do_QueryInterface(stream, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (!mHasData) { michael@0: // So far output stream on this new entry not opened, do it now. michael@0: LOG((" creating phantom output stream")); michael@0: rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: stream.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval) michael@0: { michael@0: LOG(("CacheEntry::OpenOutputStream [this=%p]", this)); michael@0: michael@0: nsresult rv; michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: MOZ_ASSERT(mState > EMPTY); michael@0: michael@0: if (mOutputStream && !mIsDoomed) { michael@0: LOG((" giving phantom output stream")); michael@0: mOutputStream.forget(_retval); michael@0: } michael@0: else { michael@0: rv = OpenOutputStreamInternal(offset, _retval); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // Entry considered ready when writer opens output stream. michael@0: if (mState < READY) michael@0: mState = READY; michael@0: michael@0: // Invoke any pending readers now. michael@0: InvokeCallbacks(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval) michael@0: { michael@0: LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this)); michael@0: michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: if (mIsDoomed) { michael@0: LOG((" doomed...")); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: MOZ_ASSERT(mState > LOADING); michael@0: michael@0: nsresult rv; michael@0: michael@0: // No need to sync on mUseDisk here, we don't need to be consistent michael@0: // with content of the memory storage entries hash table. michael@0: if (!mUseDisk) { michael@0: rv = mFile->SetMemoryOnly(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsRefPtr listener = michael@0: new CacheOutputCloseListener(this); michael@0: michael@0: nsCOMPtr stream; michael@0: rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr seekable = michael@0: do_QueryInterface(stream, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Prevent opening output stream again. michael@0: mHasData = true; michael@0: michael@0: stream.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize) michael@0: { michael@0: *aPredictedDataSize = mPredictedDataSize; michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize) michael@0: { michael@0: mPredictedDataSize = aPredictedDataSize; michael@0: michael@0: if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) { michael@0: LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this)); michael@0: AsyncDoom(nullptr); michael@0: michael@0: return NS_ERROR_FILE_TOO_BIG; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo) michael@0: { michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: if (mSecurityInfoLoaded) { michael@0: NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsXPIDLCString info; michael@0: nsCOMPtr secInfo; michael@0: nsresult rv; michael@0: michael@0: rv = mFile->GetElement("security-info", getter_Copies(info)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (info) { michael@0: rv = NS_DeserializeObject(info, getter_AddRefs(secInfo)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: mSecurityInfo.swap(secInfo); michael@0: mSecurityInfoLoaded = true; michael@0: michael@0: NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo) michael@0: { michael@0: nsresult rv; michael@0: michael@0: NS_ENSURE_SUCCESS(mFileStatus, mFileStatus); michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: mSecurityInfo = aSecurityInfo; michael@0: mSecurityInfoLoaded = true; michael@0: } michael@0: michael@0: nsCOMPtr serializable = michael@0: do_QueryInterface(aSecurityInfo); michael@0: if (aSecurityInfo && !serializable) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsCString info; michael@0: if (serializable) { michael@0: rv = NS_SerializeToString(serializable, info); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize) michael@0: { michael@0: NS_ENSURE_ARG(aStorageDataSize); michael@0: michael@0: int64_t dataSize; michael@0: nsresult rv = GetDataSize(&dataSize); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback) michael@0: { michael@0: LOG(("CacheEntry::AsyncDoom [this=%p]", this)); michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (mIsDoomed || mDoomCallback) michael@0: return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state michael@0: michael@0: mIsDoomed = true; michael@0: mDoomCallback = aCallback; michael@0: } michael@0: michael@0: // This immediately removes the entry from the master hashtable and also michael@0: // immediately dooms the file. This way we make sure that any consumer michael@0: // after this point asking for the same entry won't get michael@0: // a) this entry michael@0: // b) a new entry with the same file michael@0: PurgeAndDoom(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mFile->GetElement(aKey, aRetval); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue) michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mFile->SetElement(aKey, aValue); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::MetaDataReady() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState))); michael@0: michael@0: MOZ_ASSERT(mState > EMPTY); michael@0: michael@0: if (mState == WRITING) michael@0: mState = READY; michael@0: michael@0: InvokeCallbacks(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::SetValid() michael@0: { michael@0: LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState))); michael@0: michael@0: nsCOMPtr outputStream; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: MOZ_ASSERT(mState > EMPTY); michael@0: michael@0: mState = READY; michael@0: mHasData = true; michael@0: michael@0: InvokeCallbacks(); michael@0: michael@0: outputStream.swap(mOutputStream); michael@0: } michael@0: michael@0: if (outputStream) { michael@0: LOG((" abandoning phantom output stream")); michael@0: outputStream->Close(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly, michael@0: nsICacheEntry **_retval) michael@0: { michael@0: LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState))); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: nsRefPtr handle = ReopenTruncated(aMemoryOnly, nullptr); michael@0: if (handle) { michael@0: handle.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: BackgroundOp(Ops::CALLBACKS, true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize) michael@0: { michael@0: LOG(("CacheEntry::GetDataSize [this=%p]", this)); michael@0: *aDataSize = 0; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (!mHasData) { michael@0: LOG((" write in progress (no data)")); michael@0: return NS_ERROR_IN_PROGRESS; michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(mFileStatus, mFileStatus); michael@0: michael@0: // mayhemer: TODO Problem with compression? michael@0: if (!mFile->DataSize(aDataSize)) { michael@0: LOG((" write in progress (stream active)")); michael@0: return NS_ERROR_IN_PROGRESS; michael@0: } michael@0: michael@0: LOG((" size=%lld", *aDataSize)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::MarkValid() michael@0: { michael@0: // NOT IMPLEMENTED ACTUALLY michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::MaybeMarkValid() michael@0: { michael@0: // NOT IMPLEMENTED ACTUALLY michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess) michael@0: { michael@0: *aWriteAccess = aWriteAllowed; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheEntry::Close() michael@0: { michael@0: // NOT IMPLEMENTED ACTUALLY michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIRunnable michael@0: michael@0: NS_IMETHODIMP CacheEntry::Run() michael@0: { michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: BackgroundOp(mBackgroundOperations.Grab()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Management methods michael@0: michael@0: double CacheEntry::GetFrecency() const michael@0: { michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: return mFrecency; michael@0: } michael@0: michael@0: uint32_t CacheEntry::GetExpirationTime() const michael@0: { michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: return mSortingExpirationTime; michael@0: } michael@0: michael@0: bool CacheEntry::IsRegistered() const michael@0: { michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: return mRegistration == REGISTERED; michael@0: } michael@0: michael@0: bool CacheEntry::CanRegister() const michael@0: { michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: return mRegistration == NEVERREGISTERED; michael@0: } michael@0: michael@0: void CacheEntry::SetRegistered(bool aRegistered) michael@0: { michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: michael@0: if (aRegistered) { michael@0: MOZ_ASSERT(mRegistration == NEVERREGISTERED); michael@0: mRegistration = REGISTERED; michael@0: } michael@0: else { michael@0: MOZ_ASSERT(mRegistration == REGISTERED); michael@0: mRegistration = DEREGISTERED; michael@0: } michael@0: } michael@0: michael@0: bool CacheEntry::Purge(uint32_t aWhat) michael@0: { michael@0: LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat)); michael@0: michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: michael@0: switch (aWhat) { michael@0: case PURGE_DATA_ONLY_DISK_BACKED: michael@0: case PURGE_WHOLE_ONLY_DISK_BACKED: michael@0: // This is an in-memory only entry, don't purge it michael@0: if (!mUseDisk) { michael@0: LOG((" not using disk")); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mState == WRITING || mState == LOADING || mFrecency == 0) { michael@0: // In-progress (write or load) entries should (at least for consistency and from michael@0: // the logical point of view) stay in memory. michael@0: // Zero-frecency entries are those which have never been given to any consumer, those michael@0: // are actually very fresh and should not go just because frecency had not been set michael@0: // so far. michael@0: LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency)); michael@0: return false; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) { michael@0: // The file is used when there are open streams or chunks/metadata still waiting for michael@0: // write. In this case, this entry cannot be purged, otherwise reopenned entry michael@0: // would may not even find the data on disk - CacheFile is not shared and cannot be michael@0: // left orphan when its job is not done, hence keep the whole entry. michael@0: LOG((" file still under use")); michael@0: return false; michael@0: } michael@0: michael@0: switch (aWhat) { michael@0: case PURGE_WHOLE_ONLY_DISK_BACKED: michael@0: case PURGE_WHOLE: michael@0: { michael@0: if (!CacheStorageService::Self()->RemoveEntry(this, true)) { michael@0: LOG((" not purging, still referenced")); michael@0: return false; michael@0: } michael@0: michael@0: CacheStorageService::Self()->UnregisterEntry(this); michael@0: michael@0: // Entry removed it self from control arrays, return true michael@0: return true; michael@0: } michael@0: michael@0: case PURGE_DATA_ONLY_DISK_BACKED: michael@0: { michael@0: NS_ENSURE_SUCCESS(mFileStatus, false); michael@0: michael@0: mFile->ThrowMemoryCachedData(); michael@0: michael@0: // Entry has been left in control arrays, return false (not purged) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: LOG((" ?")); michael@0: return false; michael@0: } michael@0: michael@0: void CacheEntry::PurgeAndDoom() michael@0: { michael@0: LOG(("CacheEntry::PurgeAndDoom [this=%p]", this)); michael@0: michael@0: CacheStorageService::Self()->RemoveEntry(this); michael@0: DoomAlreadyRemoved(); michael@0: } michael@0: michael@0: void CacheEntry::DoomAlreadyRemoved() michael@0: { michael@0: LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this)); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: mIsDoomed = true; michael@0: michael@0: // This schedules dooming of the file, dooming is ensured to happen michael@0: // sooner than demand to open the same file made after this point michael@0: // so that we don't get this file for any newer opened entry(s). michael@0: DoomFile(); michael@0: michael@0: // Must force post here since may be indirectly called from michael@0: // InvokeCallbacks of this entry and we don't want reentrancy here. michael@0: BackgroundOp(Ops::CALLBACKS, true); michael@0: // Process immediately when on the management thread. michael@0: BackgroundOp(Ops::UNREGISTER); michael@0: } michael@0: michael@0: void CacheEntry::DoomFile() michael@0: { michael@0: nsresult rv = NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (NS_SUCCEEDED(mFileStatus)) { michael@0: // Always calls the callback asynchronously. michael@0: rv = mFile->Doom(mDoomCallback ? this : nullptr); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: LOG((" file doomed")); michael@0: return; michael@0: } michael@0: michael@0: if (NS_ERROR_FILE_NOT_FOUND == rv) { michael@0: // File is set to be just memory-only, notify the callbacks michael@0: // and pretend dooming has succeeded. From point of view of michael@0: // the entry it actually did - the data is gone and cannot be michael@0: // reused. michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Always posts to the main thread. michael@0: OnFileDoomed(rv); michael@0: } michael@0: michael@0: void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: if (!CacheStorageService::IsOnManagementThread() || aForceAsync) { michael@0: if (mBackgroundOperations.Set(aOperations)) michael@0: CacheStorageService::Self()->Dispatch(this); michael@0: michael@0: LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations)); michael@0: return; michael@0: } michael@0: michael@0: mozilla::MutexAutoUnlock unlock(mLock); michael@0: michael@0: MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); michael@0: michael@0: if (aOperations & Ops::FRECENCYUPDATE) { michael@0: #ifndef M_LN2 michael@0: #define M_LN2 0.69314718055994530942 michael@0: #endif michael@0: michael@0: // Half-life is dynamic, in seconds. michael@0: static double half_life = CacheObserver::HalfLifeSeconds(); michael@0: // Must convert from seconds to milliseconds since PR_Now() gives usecs. michael@0: static double const decay = (M_LN2 / half_life) / static_cast(PR_USEC_PER_SEC); michael@0: michael@0: double now_decay = static_cast(PR_Now()) * decay; michael@0: michael@0: if (mFrecency == 0) { michael@0: mFrecency = now_decay; michael@0: } michael@0: else { michael@0: // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but michael@0: // more precise. michael@0: mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay; michael@0: } michael@0: LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency)); michael@0: michael@0: // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that michael@0: // is not thread-safe) we must post to the main thread... michael@0: nsRefPtr > event = michael@0: NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency); michael@0: NS_DispatchToMainThread(event); michael@0: } michael@0: michael@0: if (aOperations & Ops::REGISTER) { michael@0: LOG(("CacheEntry REGISTER [this=%p]", this)); michael@0: michael@0: CacheStorageService::Self()->RegisterEntry(this); michael@0: } michael@0: michael@0: if (aOperations & Ops::UNREGISTER) { michael@0: LOG(("CacheEntry UNREGISTER [this=%p]", this)); michael@0: michael@0: CacheStorageService::Self()->UnregisterEntry(this); michael@0: } michael@0: michael@0: if (aOperations & Ops::CALLBACKS) { michael@0: LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this)); michael@0: michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: InvokeCallbacks(); michael@0: } michael@0: } michael@0: michael@0: void CacheEntry::StoreFrecency() michael@0: { michael@0: // No need for thread safety over mFrecency, it will be rewriten michael@0: // correctly on following invocation if broken by concurrency. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mFile->SetFrecency(FRECENCY2INT(mFrecency)); michael@0: } michael@0: michael@0: // CacheOutputCloseListener michael@0: michael@0: CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry) michael@0: : mEntry(aEntry) michael@0: { michael@0: MOZ_COUNT_CTOR(CacheOutputCloseListener); michael@0: } michael@0: michael@0: CacheOutputCloseListener::~CacheOutputCloseListener() michael@0: { michael@0: MOZ_COUNT_DTOR(CacheOutputCloseListener); michael@0: } michael@0: michael@0: void CacheOutputCloseListener::OnOutputClosed() michael@0: { michael@0: // We need this class and to redispatch since this callback is invoked michael@0: // under the file's lock and to do the job we need to enter the entry's michael@0: // lock too. That would lead to potential deadlocks. michael@0: NS_DispatchToCurrentThread(this); michael@0: } michael@0: michael@0: NS_IMETHODIMP CacheOutputCloseListener::Run() michael@0: { michael@0: mEntry->OnOutputClosed(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Memory reporting michael@0: michael@0: size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: nsCOMPtr sizeOf; michael@0: michael@0: n += mCallbacks.SizeOfExcludingThis(mallocSizeOf); michael@0: if (mFile) { michael@0: n += mFile->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: sizeOf = do_QueryInterface(mURI); michael@0: if (sizeOf) { michael@0: n += sizeOf->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: michael@0: // mDoomCallback is an arbitrary class that is probably reported elsewhere. michael@0: // mOutputStream is reported in mFile. michael@0: // mWriter is one of many handles we create, but (intentionally) not keep michael@0: // any reference to, so those unfortunatelly cannot be reported. Handles are michael@0: // small, though. michael@0: // mSecurityInfo doesn't impl nsISizeOf. michael@0: michael@0: return n; michael@0: } michael@0: michael@0: size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla