void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags) { LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", this, StateString(mState), aFlags, aCallback)); 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; MOZ_ASSERT(!readonly || !truncate, "Bad flags combination"); MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry"); 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::InvokeCallback(Callback & aCallback) { LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this, StateString(mState), aCallback.mCallback.get())); mLock.AssertCurrentThreadOwns(); // 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); nsresult rv = aCallback.mCallback->OnCacheEntryCheck( this, nullptr, &checkResult); LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, 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; }
bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly) { LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this, StateString(mState), aCallback)); mLock.AssertCurrentThreadOwns(); bool notWanted = false; 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; } if (!aReadOnly) { 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) { // 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); nsresult rv = aCallback->OnCacheEntryCheck(this, nullptr, &checkResult); LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult)); if (NS_FAILED(rv)) checkResult = ENTRY_WANTED; } 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 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. notWanted = true; break; } } } } if (aCallback) { mozilla::MutexAutoUnlock unlock(mLock); InvokeAvailableCallback(aCallback, aReadOnly, notWanted); } return true; }