1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/cache2/CacheEntry.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1584 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "CacheLog.h" 1.9 +#include "CacheEntry.h" 1.10 +#include "CacheStorageService.h" 1.11 +#include "CacheObserver.h" 1.12 +#include "CacheFileUtils.h" 1.13 + 1.14 +#include "nsIInputStream.h" 1.15 +#include "nsIOutputStream.h" 1.16 +#include "nsISeekableStream.h" 1.17 +#include "nsIURI.h" 1.18 +#include "nsICacheEntryOpenCallback.h" 1.19 +#include "nsICacheStorage.h" 1.20 +#include "nsISerializable.h" 1.21 +#include "nsIStreamTransportService.h" 1.22 +#include "nsISizeOf.h" 1.23 + 1.24 +#include "nsComponentManagerUtils.h" 1.25 +#include "nsServiceManagerUtils.h" 1.26 +#include "nsString.h" 1.27 +#include "nsProxyRelease.h" 1.28 +#include "nsSerializationHelper.h" 1.29 +#include "nsThreadUtils.h" 1.30 +#include "mozilla/Telemetry.h" 1.31 +#include <math.h> 1.32 +#include <algorithm> 1.33 + 1.34 +namespace mozilla { 1.35 +namespace net { 1.36 + 1.37 +static uint32_t const ENTRY_WANTED = 1.38 + nsICacheEntryOpenCallback::ENTRY_WANTED; 1.39 +static uint32_t const RECHECK_AFTER_WRITE_FINISHED = 1.40 + nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED; 1.41 +static uint32_t const ENTRY_NEEDS_REVALIDATION = 1.42 + nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION; 1.43 +static uint32_t const ENTRY_NOT_WANTED = 1.44 + nsICacheEntryOpenCallback::ENTRY_NOT_WANTED; 1.45 + 1.46 +NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry) 1.47 + 1.48 +// CacheEntryHandle 1.49 + 1.50 +CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) 1.51 +: mEntry(aEntry) 1.52 +{ 1.53 + MOZ_COUNT_CTOR(CacheEntryHandle); 1.54 + 1.55 +#ifdef DEBUG 1.56 + if (!mEntry->HandlesCount()) { 1.57 + // CacheEntry.mHandlesCount must go from zero to one only under 1.58 + // the service lock. Can access CacheStorageService::Self() w/o a check 1.59 + // since CacheEntry hrefs it. 1.60 + CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 1.61 + } 1.62 +#endif 1.63 + 1.64 + mEntry->AddHandleRef(); 1.65 + 1.66 + LOG(("New CacheEntryHandle %p for entry %p", this, aEntry)); 1.67 +} 1.68 + 1.69 +CacheEntryHandle::~CacheEntryHandle() 1.70 +{ 1.71 + mEntry->ReleaseHandleRef(); 1.72 + mEntry->OnHandleClosed(this); 1.73 + 1.74 + MOZ_COUNT_DTOR(CacheEntryHandle); 1.75 +} 1.76 + 1.77 +// CacheEntry::Callback 1.78 + 1.79 +CacheEntry::Callback::Callback(CacheEntry* aEntry, 1.80 + nsICacheEntryOpenCallback *aCallback, 1.81 + bool aReadOnly, bool aCheckOnAnyThread) 1.82 +: mEntry(aEntry) 1.83 +, mCallback(aCallback) 1.84 +, mTargetThread(do_GetCurrentThread()) 1.85 +, mReadOnly(aReadOnly) 1.86 +, mCheckOnAnyThread(aCheckOnAnyThread) 1.87 +, mRecheckAfterWrite(false) 1.88 +, mNotWanted(false) 1.89 +{ 1.90 + MOZ_COUNT_CTOR(CacheEntry::Callback); 1.91 + 1.92 + // The counter may go from zero to non-null only under the service lock 1.93 + // but here we expect it to be already positive. 1.94 + MOZ_ASSERT(mEntry->HandlesCount()); 1.95 + mEntry->AddHandleRef(); 1.96 +} 1.97 + 1.98 +CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat) 1.99 +: mEntry(aThat.mEntry) 1.100 +, mCallback(aThat.mCallback) 1.101 +, mTargetThread(aThat.mTargetThread) 1.102 +, mReadOnly(aThat.mReadOnly) 1.103 +, mCheckOnAnyThread(aThat.mCheckOnAnyThread) 1.104 +, mRecheckAfterWrite(aThat.mRecheckAfterWrite) 1.105 +, mNotWanted(aThat.mNotWanted) 1.106 +{ 1.107 + MOZ_COUNT_CTOR(CacheEntry::Callback); 1.108 + 1.109 + // The counter may go from zero to non-null only under the service lock 1.110 + // but here we expect it to be already positive. 1.111 + MOZ_ASSERT(mEntry->HandlesCount()); 1.112 + mEntry->AddHandleRef(); 1.113 +} 1.114 + 1.115 +CacheEntry::Callback::~Callback() 1.116 +{ 1.117 + ProxyRelease(mCallback, mTargetThread); 1.118 + 1.119 + mEntry->ReleaseHandleRef(); 1.120 + MOZ_COUNT_DTOR(CacheEntry::Callback); 1.121 +} 1.122 + 1.123 +void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) 1.124 +{ 1.125 + if (mEntry == aEntry) 1.126 + return; 1.127 + 1.128 + // The counter may go from zero to non-null only under the service lock 1.129 + // but here we expect it to be already positive. 1.130 + MOZ_ASSERT(aEntry->HandlesCount()); 1.131 + aEntry->AddHandleRef(); 1.132 + mEntry->ReleaseHandleRef(); 1.133 + mEntry = aEntry; 1.134 +} 1.135 + 1.136 +nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const 1.137 +{ 1.138 + if (!mCheckOnAnyThread) { 1.139 + // Check we are on the target 1.140 + return mTargetThread->IsOnCurrentThread(aOnCheckThread); 1.141 + } 1.142 + 1.143 + // We can invoke check anywhere 1.144 + *aOnCheckThread = true; 1.145 + return NS_OK; 1.146 +} 1.147 + 1.148 +nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const 1.149 +{ 1.150 + return mTargetThread->IsOnCurrentThread(aOnAvailThread); 1.151 +} 1.152 + 1.153 +// CacheEntry 1.154 + 1.155 +NS_IMPL_ISUPPORTS(CacheEntry, 1.156 + nsICacheEntry, 1.157 + nsIRunnable, 1.158 + CacheFileListener) 1.159 + 1.160 +CacheEntry::CacheEntry(const nsACString& aStorageID, 1.161 + nsIURI* aURI, 1.162 + const nsACString& aEnhanceID, 1.163 + bool aUseDisk) 1.164 +: mFrecency(0) 1.165 +, mSortingExpirationTime(uint32_t(-1)) 1.166 +, mLock("CacheEntry") 1.167 +, mFileStatus(NS_ERROR_NOT_INITIALIZED) 1.168 +, mURI(aURI) 1.169 +, mEnhanceID(aEnhanceID) 1.170 +, mStorageID(aStorageID) 1.171 +, mUseDisk(aUseDisk) 1.172 +, mIsDoomed(false) 1.173 +, mSecurityInfoLoaded(false) 1.174 +, mPreventCallbacks(false) 1.175 +, mHasData(false) 1.176 +, mState(NOTLOADED) 1.177 +, mRegistration(NEVERREGISTERED) 1.178 +, mWriter(nullptr) 1.179 +, mPredictedDataSize(0) 1.180 +, mReleaseThread(NS_GetCurrentThread()) 1.181 +{ 1.182 + MOZ_COUNT_CTOR(CacheEntry); 1.183 + 1.184 + mService = CacheStorageService::Self(); 1.185 + 1.186 + CacheStorageService::Self()->RecordMemoryOnlyEntry( 1.187 + this, !aUseDisk, true /* overwrite */); 1.188 +} 1.189 + 1.190 +CacheEntry::~CacheEntry() 1.191 +{ 1.192 + ProxyRelease(mURI, mReleaseThread); 1.193 + 1.194 + LOG(("CacheEntry::~CacheEntry [this=%p]", this)); 1.195 + MOZ_COUNT_DTOR(CacheEntry); 1.196 +} 1.197 + 1.198 +#ifdef PR_LOG 1.199 + 1.200 +char const * CacheEntry::StateString(uint32_t aState) 1.201 +{ 1.202 + switch (aState) { 1.203 + case NOTLOADED: return "NOTLOADED"; 1.204 + case LOADING: return "LOADING"; 1.205 + case EMPTY: return "EMPTY"; 1.206 + case WRITING: return "WRITING"; 1.207 + case READY: return "READY"; 1.208 + case REVALIDATING: return "REVALIDATING"; 1.209 + } 1.210 + 1.211 + return "?"; 1.212 +} 1.213 + 1.214 +#endif 1.215 + 1.216 +nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult) 1.217 +{ 1.218 + return HashingKey(mStorageID, mEnhanceID, mURI, aResult); 1.219 +} 1.220 + 1.221 +nsresult CacheEntry::HashingKey(nsACString &aResult) 1.222 +{ 1.223 + return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult); 1.224 +} 1.225 + 1.226 +// static 1.227 +nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID, 1.228 + nsCSubstring const& aEnhanceID, 1.229 + nsIURI* aURI, 1.230 + nsACString &aResult) 1.231 +{ 1.232 + nsAutoCString spec; 1.233 + nsresult rv = aURI->GetAsciiSpec(spec); 1.234 + NS_ENSURE_SUCCESS(rv, rv); 1.235 + 1.236 + return HashingKey(aStorageID, aEnhanceID, spec, aResult); 1.237 +} 1.238 + 1.239 +// static 1.240 +nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID, 1.241 + nsCSubstring const& aEnhanceID, 1.242 + nsCSubstring const& aURISpec, 1.243 + nsACString &aResult) 1.244 +{ 1.245 + /** 1.246 + * This key is used to salt hash that is a base for disk file name. 1.247 + * Changing it will cause we will not be able to find files on disk. 1.248 + */ 1.249 + 1.250 + aResult.Append(aStorageID); 1.251 + 1.252 + if (!aEnhanceID.IsEmpty()) { 1.253 + CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID); 1.254 + } 1.255 + 1.256 + // Appending directly 1.257 + aResult.Append(':'); 1.258 + aResult.Append(aURISpec); 1.259 + 1.260 + return NS_OK; 1.261 +} 1.262 + 1.263 +void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags) 1.264 +{ 1.265 + LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", 1.266 + this, StateString(mState), aFlags, aCallback)); 1.267 + 1.268 + bool readonly = aFlags & nsICacheStorage::OPEN_READONLY; 1.269 + bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE; 1.270 + bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY; 1.271 + bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED; 1.272 + 1.273 + MOZ_ASSERT(!readonly || !truncate, "Bad flags combination"); 1.274 + MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry"); 1.275 + 1.276 + Callback callback(this, aCallback, readonly, multithread); 1.277 + 1.278 + mozilla::MutexAutoLock lock(mLock); 1.279 + 1.280 + RememberCallback(callback); 1.281 + 1.282 + // Load() opens the lock 1.283 + if (Load(truncate, priority)) { 1.284 + // Loading is in progress... 1.285 + return; 1.286 + } 1.287 + 1.288 + InvokeCallbacks(); 1.289 +} 1.290 + 1.291 +bool CacheEntry::Load(bool aTruncate, bool aPriority) 1.292 +{ 1.293 + LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate)); 1.294 + 1.295 + mLock.AssertCurrentThreadOwns(); 1.296 + 1.297 + if (mState > LOADING) { 1.298 + LOG((" already loaded")); 1.299 + return false; 1.300 + } 1.301 + 1.302 + if (mState == LOADING) { 1.303 + LOG((" already loading")); 1.304 + return true; 1.305 + } 1.306 + 1.307 + mState = LOADING; 1.308 + 1.309 + MOZ_ASSERT(!mFile); 1.310 + 1.311 + bool directLoad = aTruncate || !mUseDisk; 1.312 + if (directLoad) 1.313 + mFileStatus = NS_OK; 1.314 + else 1.315 + mLoadStart = TimeStamp::Now(); 1.316 + 1.317 + mFile = new CacheFile(); 1.318 + 1.319 + BackgroundOp(Ops::REGISTER); 1.320 + 1.321 + { 1.322 + mozilla::MutexAutoUnlock unlock(mLock); 1.323 + 1.324 + nsresult rv; 1.325 + 1.326 + nsAutoCString fileKey; 1.327 + rv = HashingKeyWithStorage(fileKey); 1.328 + 1.329 + LOG((" performing load, file=%p", mFile.get())); 1.330 + if (NS_SUCCEEDED(rv)) { 1.331 + rv = mFile->Init(fileKey, 1.332 + aTruncate, 1.333 + !mUseDisk, 1.334 + aPriority, 1.335 + directLoad ? nullptr : this); 1.336 + } 1.337 + 1.338 + if (NS_FAILED(rv)) { 1.339 + mFileStatus = rv; 1.340 + AsyncDoom(nullptr); 1.341 + return false; 1.342 + } 1.343 + } 1.344 + 1.345 + if (directLoad) { 1.346 + // Just fake the load has already been done as "new". 1.347 + mState = EMPTY; 1.348 + } 1.349 + 1.350 + return mState == LOADING; 1.351 +} 1.352 + 1.353 +NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) 1.354 +{ 1.355 + LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]", 1.356 + this, aResult, aIsNew)); 1.357 + 1.358 + MOZ_ASSERT(!mLoadStart.IsNull()); 1.359 + 1.360 + if (NS_SUCCEEDED(aResult)) { 1.361 + if (aIsNew) { 1.362 + mozilla::Telemetry::AccumulateTimeDelta( 1.363 + mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS, 1.364 + mLoadStart); 1.365 + } 1.366 + else { 1.367 + mozilla::Telemetry::AccumulateTimeDelta( 1.368 + mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS, 1.369 + mLoadStart); 1.370 + } 1.371 + } 1.372 + 1.373 + // OnFileReady, that is the only code that can transit from LOADING 1.374 + // to any follow-on state, can only be invoked ones on an entry, 1.375 + // thus no need to lock. Until this moment there is no consumer that 1.376 + // could manipulate the entry state. 1.377 + mozilla::MutexAutoLock lock(mLock); 1.378 + 1.379 + MOZ_ASSERT(mState == LOADING); 1.380 + 1.381 + mState = (aIsNew || NS_FAILED(aResult)) 1.382 + ? EMPTY 1.383 + : READY; 1.384 + 1.385 + mFileStatus = aResult; 1.386 + 1.387 + if (mState == READY) { 1.388 + mHasData = true; 1.389 + 1.390 + uint32_t frecency; 1.391 + mFile->GetFrecency(&frecency); 1.392 + // mFrecency is held in a double to increase computance precision. 1.393 + // It is ok to persist frecency only as a uint32 with some math involved. 1.394 + mFrecency = INT2FRECENCY(frecency); 1.395 + } 1.396 + 1.397 + InvokeCallbacks(); 1.398 + return NS_OK; 1.399 +} 1.400 + 1.401 +NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) 1.402 +{ 1.403 + if (mDoomCallback) { 1.404 + nsRefPtr<DoomCallbackRunnable> event = 1.405 + new DoomCallbackRunnable(this, aResult); 1.406 + NS_DispatchToMainThread(event); 1.407 + } 1.408 + 1.409 + return NS_OK; 1.410 +} 1.411 + 1.412 +already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly, 1.413 + nsICacheEntryOpenCallback* aCallback) 1.414 +{ 1.415 + LOG(("CacheEntry::ReopenTruncated [this=%p]", this)); 1.416 + 1.417 + mLock.AssertCurrentThreadOwns(); 1.418 + 1.419 + // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly 1.420 + mPreventCallbacks = true; 1.421 + 1.422 + nsRefPtr<CacheEntryHandle> handle; 1.423 + nsRefPtr<CacheEntry> newEntry; 1.424 + { 1.425 + mozilla::MutexAutoUnlock unlock(mLock); 1.426 + 1.427 + // The following call dooms this entry (calls DoomAlreadyRemoved on us) 1.428 + nsresult rv = CacheStorageService::Self()->AddStorageEntry( 1.429 + GetStorageID(), GetURI(), GetEnhanceID(), 1.430 + mUseDisk && !aMemoryOnly, 1.431 + true, // always create 1.432 + true, // truncate existing (this one) 1.433 + getter_AddRefs(handle)); 1.434 + 1.435 + if (NS_SUCCEEDED(rv)) { 1.436 + newEntry = handle->Entry(); 1.437 + LOG((" exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv)); 1.438 + newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE); 1.439 + } else { 1.440 + LOG((" exchanged of entry %p failed, rv=0x%08x", this, rv)); 1.441 + AsyncDoom(nullptr); 1.442 + } 1.443 + } 1.444 + 1.445 + mPreventCallbacks = false; 1.446 + 1.447 + if (!newEntry) 1.448 + return nullptr; 1.449 + 1.450 + newEntry->TransferCallbacks(*this); 1.451 + mCallbacks.Clear(); 1.452 + 1.453 + return handle.forget(); 1.454 +} 1.455 + 1.456 +void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry) 1.457 +{ 1.458 + mozilla::MutexAutoLock lock(mLock); 1.459 + 1.460 + LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", 1.461 + this, &aFromEntry)); 1.462 + 1.463 + if (!mCallbacks.Length()) 1.464 + mCallbacks.SwapElements(aFromEntry.mCallbacks); 1.465 + else 1.466 + mCallbacks.AppendElements(aFromEntry.mCallbacks); 1.467 + 1.468 + uint32_t callbacksLength = mCallbacks.Length(); 1.469 + if (callbacksLength) { 1.470 + // Carry the entry reference (unfortunatelly, needs to be done manually...) 1.471 + for (uint32_t i = 0; i < callbacksLength; ++i) 1.472 + mCallbacks[i].ExchangeEntry(this); 1.473 + 1.474 + BackgroundOp(Ops::CALLBACKS, true); 1.475 + } 1.476 +} 1.477 + 1.478 +void CacheEntry::RememberCallback(Callback const& aCallback) 1.479 +{ 1.480 + LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback.mCallback.get())); 1.481 + 1.482 + mLock.AssertCurrentThreadOwns(); 1.483 + 1.484 + mCallbacks.AppendElement(aCallback); 1.485 +} 1.486 + 1.487 +void CacheEntry::InvokeCallbacksLock() 1.488 +{ 1.489 + mozilla::MutexAutoLock lock(mLock); 1.490 + InvokeCallbacks(); 1.491 +} 1.492 + 1.493 +void CacheEntry::InvokeCallbacks() 1.494 +{ 1.495 + mLock.AssertCurrentThreadOwns(); 1.496 + 1.497 + LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this)); 1.498 + 1.499 + // Invoke first all r/w callbacks, then all r/o callbacks. 1.500 + if (InvokeCallbacks(false)) 1.501 + InvokeCallbacks(true); 1.502 + 1.503 + LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this)); 1.504 +} 1.505 + 1.506 +bool CacheEntry::InvokeCallbacks(bool aReadOnly) 1.507 +{ 1.508 + mLock.AssertCurrentThreadOwns(); 1.509 + 1.510 + uint32_t i = 0; 1.511 + while (i < mCallbacks.Length()) { 1.512 + if (mPreventCallbacks) { 1.513 + LOG((" callbacks prevented!")); 1.514 + return false; 1.515 + } 1.516 + 1.517 + if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) { 1.518 + LOG((" entry is being written/revalidated")); 1.519 + return false; 1.520 + } 1.521 + 1.522 + if (mCallbacks[i].mReadOnly != aReadOnly) { 1.523 + // Callback is not r/w or r/o, go to another one in line 1.524 + ++i; 1.525 + continue; 1.526 + } 1.527 + 1.528 + bool onCheckThread; 1.529 + nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread); 1.530 + 1.531 + if (NS_SUCCEEDED(rv) && !onCheckThread) { 1.532 + // Redispatch to the target thread 1.533 + nsRefPtr<nsRunnableMethod<CacheEntry> > event = 1.534 + NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock); 1.535 + 1.536 + rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 1.537 + if (NS_SUCCEEDED(rv)) { 1.538 + LOG((" re-dispatching to target thread")); 1.539 + return false; 1.540 + } 1.541 + } 1.542 + 1.543 + Callback callback = mCallbacks[i]; 1.544 + mCallbacks.RemoveElementAt(i); 1.545 + 1.546 + if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) { 1.547 + // Callback didn't fire, put it back and go to another one in line. 1.548 + // Only reason InvokeCallback returns false is that onCacheEntryCheck 1.549 + // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other 1.550 + // readers or potential writers would be unnecessarily kept from being 1.551 + // invoked. 1.552 + mCallbacks.InsertElementAt(i, callback); 1.553 + ++i; 1.554 + } 1.555 + } 1.556 + 1.557 + return true; 1.558 +} 1.559 + 1.560 +bool CacheEntry::InvokeCallback(Callback & aCallback) 1.561 +{ 1.562 + LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", 1.563 + this, StateString(mState), aCallback.mCallback.get())); 1.564 + 1.565 + mLock.AssertCurrentThreadOwns(); 1.566 + 1.567 + // When this entry is doomed we want to notify the callback any time 1.568 + if (!mIsDoomed) { 1.569 + // When we are here, the entry must be loaded from disk 1.570 + MOZ_ASSERT(mState > LOADING); 1.571 + 1.572 + if (mState == WRITING || mState == REVALIDATING) { 1.573 + // Prevent invoking other callbacks since one of them is now writing 1.574 + // or revalidating this entry. No consumers should get this entry 1.575 + // until metadata are filled with values downloaded from the server 1.576 + // or the entry revalidated and output stream has been opened. 1.577 + LOG((" entry is being written/revalidated, callback bypassed")); 1.578 + return false; 1.579 + } 1.580 + 1.581 + // mRecheckAfterWrite flag already set means the callback has already passed 1.582 + // the onCacheEntryCheck call. Until the current write is not finished this 1.583 + // callback will be bypassed. 1.584 + if (!aCallback.mRecheckAfterWrite) { 1.585 + 1.586 + if (!aCallback.mReadOnly) { 1.587 + if (mState == EMPTY) { 1.588 + // Advance to writing state, we expect to invoke the callback and let 1.589 + // it fill content of this entry. Must set and check the state here 1.590 + // to prevent more then one 1.591 + mState = WRITING; 1.592 + LOG((" advancing to WRITING state")); 1.593 + } 1.594 + 1.595 + if (!aCallback.mCallback) { 1.596 + // We can be given no callback only in case of recreate, it is ok 1.597 + // to advance to WRITING state since the caller of recreate is expected 1.598 + // to write this entry now. 1.599 + return true; 1.600 + } 1.601 + } 1.602 + 1.603 + if (mState == READY) { 1.604 + // Metadata present, validate the entry 1.605 + uint32_t checkResult; 1.606 + { 1.607 + // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck 1.608 + mozilla::MutexAutoUnlock unlock(mLock); 1.609 + 1.610 + nsresult rv = aCallback.mCallback->OnCacheEntryCheck( 1.611 + this, nullptr, &checkResult); 1.612 + LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult)); 1.613 + 1.614 + if (NS_FAILED(rv)) 1.615 + checkResult = ENTRY_NOT_WANTED; 1.616 + } 1.617 + 1.618 + switch (checkResult) { 1.619 + case ENTRY_WANTED: 1.620 + // Nothing more to do here, the consumer is responsible to handle 1.621 + // the result of OnCacheEntryCheck it self. 1.622 + // Proceed to callback... 1.623 + break; 1.624 + 1.625 + case RECHECK_AFTER_WRITE_FINISHED: 1.626 + LOG((" consumer will check on the entry again after write is done")); 1.627 + // The consumer wants the entry to complete first. 1.628 + aCallback.mRecheckAfterWrite = true; 1.629 + break; 1.630 + 1.631 + case ENTRY_NEEDS_REVALIDATION: 1.632 + LOG((" will be holding callbacks until entry is revalidated")); 1.633 + // State is READY now and from that state entry cannot transit to any other 1.634 + // state then REVALIDATING for which cocurrency is not an issue. Potentially 1.635 + // no need to lock here. 1.636 + mState = REVALIDATING; 1.637 + break; 1.638 + 1.639 + case ENTRY_NOT_WANTED: 1.640 + LOG((" consumer not interested in the entry")); 1.641 + // Do not give this entry to the consumer, it is not interested in us. 1.642 + aCallback.mNotWanted = true; 1.643 + break; 1.644 + } 1.645 + } 1.646 + } 1.647 + } 1.648 + 1.649 + if (aCallback.mCallback) { 1.650 + if (!mIsDoomed && aCallback.mRecheckAfterWrite) { 1.651 + // If we don't have data and the callback wants a complete entry, 1.652 + // don't invoke now. 1.653 + bool bypass = !mHasData; 1.654 + if (!bypass) { 1.655 + int64_t _unused; 1.656 + bypass = !mFile->DataSize(&_unused); 1.657 + } 1.658 + 1.659 + if (bypass) { 1.660 + LOG((" bypassing, entry data still being written")); 1.661 + return false; 1.662 + } 1.663 + 1.664 + // Entry is complete now, do the check+avail call again 1.665 + aCallback.mRecheckAfterWrite = false; 1.666 + return InvokeCallback(aCallback); 1.667 + } 1.668 + 1.669 + mozilla::MutexAutoUnlock unlock(mLock); 1.670 + InvokeAvailableCallback(aCallback); 1.671 + } 1.672 + 1.673 + return true; 1.674 +} 1.675 + 1.676 +void CacheEntry::InvokeAvailableCallback(Callback const & aCallback) 1.677 +{ 1.678 + LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]", 1.679 + this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted)); 1.680 + 1.681 + nsresult rv; 1.682 + 1.683 + uint32_t const state = mState; 1.684 + 1.685 + // When we are here, the entry must be loaded from disk 1.686 + MOZ_ASSERT(state > LOADING || mIsDoomed); 1.687 + 1.688 + bool onAvailThread; 1.689 + rv = aCallback.OnAvailThread(&onAvailThread); 1.690 + if (NS_FAILED(rv)) { 1.691 + LOG((" target thread dead?")); 1.692 + return; 1.693 + } 1.694 + 1.695 + if (!onAvailThread) { 1.696 + // Dispatch to the right thread 1.697 + nsRefPtr<AvailableCallbackRunnable> event = 1.698 + new AvailableCallbackRunnable(this, aCallback); 1.699 + 1.700 + rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 1.701 + LOG((" redispatched, (rv = 0x%08x)", rv)); 1.702 + return; 1.703 + } 1.704 + 1.705 + if (mIsDoomed || aCallback.mNotWanted) { 1.706 + LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND")); 1.707 + aCallback.mCallback->OnCacheEntryAvailable( 1.708 + nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND); 1.709 + return; 1.710 + } 1.711 + 1.712 + if (state == READY) { 1.713 + LOG((" ready/has-meta, notifying OCEA with entry and NS_OK")); 1.714 + { 1.715 + mozilla::MutexAutoLock lock(mLock); 1.716 + BackgroundOp(Ops::FRECENCYUPDATE); 1.717 + } 1.718 + 1.719 + nsRefPtr<CacheEntryHandle> handle = NewHandle(); 1.720 + aCallback.mCallback->OnCacheEntryAvailable( 1.721 + handle, false, nullptr, NS_OK); 1.722 + return; 1.723 + } 1.724 + 1.725 + if (aCallback.mReadOnly) { 1.726 + LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND")); 1.727 + aCallback.mCallback->OnCacheEntryAvailable( 1.728 + nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND); 1.729 + return; 1.730 + } 1.731 + 1.732 + // This is a new or potentially non-valid entry and needs to be fetched first. 1.733 + // The CacheEntryHandle blocks other consumers until the channel 1.734 + // either releases the entry or marks metadata as filled or whole entry valid, 1.735 + // i.e. until MetaDataReady() or SetValid() on the entry is called respectively. 1.736 + 1.737 + // Consumer will be responsible to fill or validate the entry metadata and data. 1.738 + 1.739 + nsRefPtr<CacheEntryHandle> handle = NewWriteHandle(); 1.740 + rv = aCallback.mCallback->OnCacheEntryAvailable( 1.741 + handle, state == WRITING, nullptr, NS_OK); 1.742 + 1.743 + if (NS_FAILED(rv)) { 1.744 + LOG((" writing/revalidating failed (0x%08x)", rv)); 1.745 + 1.746 + // Consumer given a new entry failed to take care of the entry. 1.747 + OnHandleClosed(handle); 1.748 + return; 1.749 + } 1.750 + 1.751 + LOG((" writing/revalidating")); 1.752 +} 1.753 + 1.754 +CacheEntryHandle* CacheEntry::NewHandle() 1.755 +{ 1.756 + return new CacheEntryHandle(this); 1.757 +} 1.758 + 1.759 +CacheEntryHandle* CacheEntry::NewWriteHandle() 1.760 +{ 1.761 + mozilla::MutexAutoLock lock(mLock); 1.762 + 1.763 + BackgroundOp(Ops::FRECENCYUPDATE); 1.764 + return (mWriter = new CacheEntryHandle(this)); 1.765 +} 1.766 + 1.767 +void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) 1.768 +{ 1.769 + LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle)); 1.770 + 1.771 + nsCOMPtr<nsIOutputStream> outputStream; 1.772 + 1.773 + { 1.774 + mozilla::MutexAutoLock lock(mLock); 1.775 + 1.776 + if (mWriter != aHandle) { 1.777 + LOG((" not the writer")); 1.778 + return; 1.779 + } 1.780 + 1.781 + if (mOutputStream) { 1.782 + // No one took our internal output stream, so there are no data 1.783 + // and output stream has to be open symultaneously with input stream 1.784 + // on this entry again. 1.785 + mHasData = false; 1.786 + } 1.787 + 1.788 + outputStream.swap(mOutputStream); 1.789 + mWriter = nullptr; 1.790 + 1.791 + if (mState == WRITING) { 1.792 + LOG((" reverting to state EMPTY - write failed")); 1.793 + mState = EMPTY; 1.794 + } 1.795 + else if (mState == REVALIDATING) { 1.796 + LOG((" reverting to state READY - reval failed")); 1.797 + mState = READY; 1.798 + } 1.799 + 1.800 + InvokeCallbacks(); 1.801 + } 1.802 + 1.803 + if (outputStream) { 1.804 + LOG((" abandoning phantom output stream")); 1.805 + outputStream->Close(); 1.806 + } 1.807 +} 1.808 + 1.809 +void CacheEntry::OnOutputClosed() 1.810 +{ 1.811 + // Called when the file's output stream is closed. Invoke any callbacks 1.812 + // waiting for complete entry. 1.813 + 1.814 + mozilla::MutexAutoLock lock(mLock); 1.815 + InvokeCallbacks(); 1.816 +} 1.817 + 1.818 +bool CacheEntry::IsUsingDiskLocked() const 1.819 +{ 1.820 + CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 1.821 + 1.822 + return IsUsingDisk(); 1.823 +} 1.824 + 1.825 +bool CacheEntry::SetUsingDisk(bool aUsingDisk) 1.826 +{ 1.827 + // Called by the service when this entry is reopen to reflect 1.828 + // demanded storage target. 1.829 + 1.830 + if (mState >= READY) { 1.831 + // Don't modify after this entry has been filled. 1.832 + return false; 1.833 + } 1.834 + 1.835 + CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 1.836 + 1.837 + bool changed = mUseDisk != aUsingDisk; 1.838 + mUseDisk = aUsingDisk; 1.839 + return changed; 1.840 +} 1.841 + 1.842 +bool CacheEntry::IsReferenced() const 1.843 +{ 1.844 + CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 1.845 + 1.846 + // Increasing this counter from 0 to non-null and this check both happen only 1.847 + // under the service lock. 1.848 + return mHandlesCount > 0; 1.849 +} 1.850 + 1.851 +bool CacheEntry::IsFileDoomed() 1.852 +{ 1.853 + mozilla::MutexAutoLock lock(mLock); 1.854 + 1.855 + if (NS_SUCCEEDED(mFileStatus)) { 1.856 + return mFile->IsDoomed(); 1.857 + } 1.858 + 1.859 + return false; 1.860 +} 1.861 + 1.862 +uint32_t CacheEntry::GetMetadataMemoryConsumption() 1.863 +{ 1.864 + NS_ENSURE_SUCCESS(mFileStatus, 0); 1.865 + 1.866 + uint32_t size; 1.867 + if (NS_FAILED(mFile->ElementsSize(&size))) 1.868 + return 0; 1.869 + 1.870 + return size; 1.871 +} 1.872 + 1.873 +// nsICacheEntry 1.874 + 1.875 +NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk) 1.876 +{ 1.877 + // No need to sync when only reading. 1.878 + // When consumer needs to be consistent with state of the memory storage entries 1.879 + // table, then let it use GetUseDisk getter that must be called under the service lock. 1.880 + *aPersistToDisk = mUseDisk; 1.881 + return NS_OK; 1.882 +} 1.883 + 1.884 +NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey) 1.885 +{ 1.886 + return mURI->GetAsciiSpec(aKey); 1.887 +} 1.888 + 1.889 +NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount) 1.890 +{ 1.891 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.892 + 1.893 + return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount)); 1.894 +} 1.895 + 1.896 +NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched) 1.897 +{ 1.898 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.899 + 1.900 + return mFile->GetLastFetched(aLastFetched); 1.901 +} 1.902 + 1.903 +NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified) 1.904 +{ 1.905 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.906 + 1.907 + return mFile->GetLastModified(aLastModified); 1.908 +} 1.909 + 1.910 +NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime) 1.911 +{ 1.912 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.913 + 1.914 + return mFile->GetExpirationTime(aExpirationTime); 1.915 +} 1.916 + 1.917 +NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime) 1.918 +{ 1.919 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.920 + 1.921 + nsresult rv = mFile->SetExpirationTime(aExpirationTime); 1.922 + NS_ENSURE_SUCCESS(rv, rv); 1.923 + 1.924 + // Aligned assignment, thus atomic. 1.925 + mSortingExpirationTime = aExpirationTime; 1.926 + return NS_OK; 1.927 +} 1.928 + 1.929 +NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval) 1.930 +{ 1.931 + LOG(("CacheEntry::OpenInputStream [this=%p]", this)); 1.932 + 1.933 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.934 + 1.935 + nsresult rv; 1.936 + 1.937 + nsCOMPtr<nsIInputStream> stream; 1.938 + rv = mFile->OpenInputStream(getter_AddRefs(stream)); 1.939 + NS_ENSURE_SUCCESS(rv, rv); 1.940 + 1.941 + nsCOMPtr<nsISeekableStream> seekable = 1.942 + do_QueryInterface(stream, &rv); 1.943 + NS_ENSURE_SUCCESS(rv, rv); 1.944 + 1.945 + rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); 1.946 + NS_ENSURE_SUCCESS(rv, rv); 1.947 + 1.948 + mozilla::MutexAutoLock lock(mLock); 1.949 + 1.950 + if (!mHasData) { 1.951 + // So far output stream on this new entry not opened, do it now. 1.952 + LOG((" creating phantom output stream")); 1.953 + rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream)); 1.954 + NS_ENSURE_SUCCESS(rv, rv); 1.955 + } 1.956 + 1.957 + stream.forget(_retval); 1.958 + return NS_OK; 1.959 +} 1.960 + 1.961 +NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval) 1.962 +{ 1.963 + LOG(("CacheEntry::OpenOutputStream [this=%p]", this)); 1.964 + 1.965 + nsresult rv; 1.966 + 1.967 + mozilla::MutexAutoLock lock(mLock); 1.968 + 1.969 + MOZ_ASSERT(mState > EMPTY); 1.970 + 1.971 + if (mOutputStream && !mIsDoomed) { 1.972 + LOG((" giving phantom output stream")); 1.973 + mOutputStream.forget(_retval); 1.974 + } 1.975 + else { 1.976 + rv = OpenOutputStreamInternal(offset, _retval); 1.977 + if (NS_FAILED(rv)) return rv; 1.978 + } 1.979 + 1.980 + // Entry considered ready when writer opens output stream. 1.981 + if (mState < READY) 1.982 + mState = READY; 1.983 + 1.984 + // Invoke any pending readers now. 1.985 + InvokeCallbacks(); 1.986 + 1.987 + return NS_OK; 1.988 +} 1.989 + 1.990 +nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval) 1.991 +{ 1.992 + LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this)); 1.993 + 1.994 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.995 + 1.996 + mLock.AssertCurrentThreadOwns(); 1.997 + 1.998 + if (mIsDoomed) { 1.999 + LOG((" doomed...")); 1.1000 + return NS_ERROR_NOT_AVAILABLE; 1.1001 + } 1.1002 + 1.1003 + MOZ_ASSERT(mState > LOADING); 1.1004 + 1.1005 + nsresult rv; 1.1006 + 1.1007 + // No need to sync on mUseDisk here, we don't need to be consistent 1.1008 + // with content of the memory storage entries hash table. 1.1009 + if (!mUseDisk) { 1.1010 + rv = mFile->SetMemoryOnly(); 1.1011 + NS_ENSURE_SUCCESS(rv, rv); 1.1012 + } 1.1013 + 1.1014 + nsRefPtr<CacheOutputCloseListener> listener = 1.1015 + new CacheOutputCloseListener(this); 1.1016 + 1.1017 + nsCOMPtr<nsIOutputStream> stream; 1.1018 + rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream)); 1.1019 + NS_ENSURE_SUCCESS(rv, rv); 1.1020 + 1.1021 + nsCOMPtr<nsISeekableStream> seekable = 1.1022 + do_QueryInterface(stream, &rv); 1.1023 + NS_ENSURE_SUCCESS(rv, rv); 1.1024 + 1.1025 + rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); 1.1026 + NS_ENSURE_SUCCESS(rv, rv); 1.1027 + 1.1028 + // Prevent opening output stream again. 1.1029 + mHasData = true; 1.1030 + 1.1031 + stream.swap(*_retval); 1.1032 + return NS_OK; 1.1033 +} 1.1034 + 1.1035 +NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize) 1.1036 +{ 1.1037 + *aPredictedDataSize = mPredictedDataSize; 1.1038 + return NS_OK; 1.1039 +} 1.1040 +NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize) 1.1041 +{ 1.1042 + mPredictedDataSize = aPredictedDataSize; 1.1043 + 1.1044 + if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) { 1.1045 + LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this)); 1.1046 + AsyncDoom(nullptr); 1.1047 + 1.1048 + return NS_ERROR_FILE_TOO_BIG; 1.1049 + } 1.1050 + 1.1051 + return NS_OK; 1.1052 +} 1.1053 + 1.1054 +NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo) 1.1055 +{ 1.1056 + { 1.1057 + mozilla::MutexAutoLock lock(mLock); 1.1058 + if (mSecurityInfoLoaded) { 1.1059 + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); 1.1060 + return NS_OK; 1.1061 + } 1.1062 + } 1.1063 + 1.1064 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.1065 + 1.1066 + nsXPIDLCString info; 1.1067 + nsCOMPtr<nsISupports> secInfo; 1.1068 + nsresult rv; 1.1069 + 1.1070 + rv = mFile->GetElement("security-info", getter_Copies(info)); 1.1071 + NS_ENSURE_SUCCESS(rv, rv); 1.1072 + 1.1073 + if (info) { 1.1074 + rv = NS_DeserializeObject(info, getter_AddRefs(secInfo)); 1.1075 + NS_ENSURE_SUCCESS(rv, rv); 1.1076 + } 1.1077 + 1.1078 + { 1.1079 + mozilla::MutexAutoLock lock(mLock); 1.1080 + 1.1081 + mSecurityInfo.swap(secInfo); 1.1082 + mSecurityInfoLoaded = true; 1.1083 + 1.1084 + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); 1.1085 + } 1.1086 + 1.1087 + return NS_OK; 1.1088 +} 1.1089 +NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo) 1.1090 +{ 1.1091 + nsresult rv; 1.1092 + 1.1093 + NS_ENSURE_SUCCESS(mFileStatus, mFileStatus); 1.1094 + 1.1095 + { 1.1096 + mozilla::MutexAutoLock lock(mLock); 1.1097 + 1.1098 + mSecurityInfo = aSecurityInfo; 1.1099 + mSecurityInfoLoaded = true; 1.1100 + } 1.1101 + 1.1102 + nsCOMPtr<nsISerializable> serializable = 1.1103 + do_QueryInterface(aSecurityInfo); 1.1104 + if (aSecurityInfo && !serializable) 1.1105 + return NS_ERROR_UNEXPECTED; 1.1106 + 1.1107 + nsCString info; 1.1108 + if (serializable) { 1.1109 + rv = NS_SerializeToString(serializable, info); 1.1110 + NS_ENSURE_SUCCESS(rv, rv); 1.1111 + } 1.1112 + 1.1113 + rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr); 1.1114 + NS_ENSURE_SUCCESS(rv, rv); 1.1115 + 1.1116 + return NS_OK; 1.1117 +} 1.1118 + 1.1119 +NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize) 1.1120 +{ 1.1121 + NS_ENSURE_ARG(aStorageDataSize); 1.1122 + 1.1123 + int64_t dataSize; 1.1124 + nsresult rv = GetDataSize(&dataSize); 1.1125 + if (NS_FAILED(rv)) 1.1126 + return rv; 1.1127 + 1.1128 + *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize); 1.1129 + 1.1130 + return NS_OK; 1.1131 +} 1.1132 + 1.1133 +NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback) 1.1134 +{ 1.1135 + LOG(("CacheEntry::AsyncDoom [this=%p]", this)); 1.1136 + 1.1137 + { 1.1138 + mozilla::MutexAutoLock lock(mLock); 1.1139 + 1.1140 + if (mIsDoomed || mDoomCallback) 1.1141 + return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state 1.1142 + 1.1143 + mIsDoomed = true; 1.1144 + mDoomCallback = aCallback; 1.1145 + } 1.1146 + 1.1147 + // This immediately removes the entry from the master hashtable and also 1.1148 + // immediately dooms the file. This way we make sure that any consumer 1.1149 + // after this point asking for the same entry won't get 1.1150 + // a) this entry 1.1151 + // b) a new entry with the same file 1.1152 + PurgeAndDoom(); 1.1153 + 1.1154 + return NS_OK; 1.1155 +} 1.1156 + 1.1157 +NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval) 1.1158 +{ 1.1159 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.1160 + 1.1161 + return mFile->GetElement(aKey, aRetval); 1.1162 +} 1.1163 + 1.1164 +NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue) 1.1165 +{ 1.1166 + NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1.1167 + 1.1168 + return mFile->SetElement(aKey, aValue); 1.1169 +} 1.1170 + 1.1171 +NS_IMETHODIMP CacheEntry::MetaDataReady() 1.1172 +{ 1.1173 + mozilla::MutexAutoLock lock(mLock); 1.1174 + 1.1175 + LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState))); 1.1176 + 1.1177 + MOZ_ASSERT(mState > EMPTY); 1.1178 + 1.1179 + if (mState == WRITING) 1.1180 + mState = READY; 1.1181 + 1.1182 + InvokeCallbacks(); 1.1183 + 1.1184 + return NS_OK; 1.1185 +} 1.1186 + 1.1187 +NS_IMETHODIMP CacheEntry::SetValid() 1.1188 +{ 1.1189 + LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState))); 1.1190 + 1.1191 + nsCOMPtr<nsIOutputStream> outputStream; 1.1192 + 1.1193 + { 1.1194 + mozilla::MutexAutoLock lock(mLock); 1.1195 + 1.1196 + MOZ_ASSERT(mState > EMPTY); 1.1197 + 1.1198 + mState = READY; 1.1199 + mHasData = true; 1.1200 + 1.1201 + InvokeCallbacks(); 1.1202 + 1.1203 + outputStream.swap(mOutputStream); 1.1204 + } 1.1205 + 1.1206 + if (outputStream) { 1.1207 + LOG((" abandoning phantom output stream")); 1.1208 + outputStream->Close(); 1.1209 + } 1.1210 + 1.1211 + return NS_OK; 1.1212 +} 1.1213 + 1.1214 +NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly, 1.1215 + nsICacheEntry **_retval) 1.1216 +{ 1.1217 + LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState))); 1.1218 + 1.1219 + mozilla::MutexAutoLock lock(mLock); 1.1220 + 1.1221 + nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr); 1.1222 + if (handle) { 1.1223 + handle.forget(_retval); 1.1224 + return NS_OK; 1.1225 + } 1.1226 + 1.1227 + BackgroundOp(Ops::CALLBACKS, true); 1.1228 + return NS_OK; 1.1229 +} 1.1230 + 1.1231 +NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize) 1.1232 +{ 1.1233 + LOG(("CacheEntry::GetDataSize [this=%p]", this)); 1.1234 + *aDataSize = 0; 1.1235 + 1.1236 + { 1.1237 + mozilla::MutexAutoLock lock(mLock); 1.1238 + 1.1239 + if (!mHasData) { 1.1240 + LOG((" write in progress (no data)")); 1.1241 + return NS_ERROR_IN_PROGRESS; 1.1242 + } 1.1243 + } 1.1244 + 1.1245 + NS_ENSURE_SUCCESS(mFileStatus, mFileStatus); 1.1246 + 1.1247 + // mayhemer: TODO Problem with compression? 1.1248 + if (!mFile->DataSize(aDataSize)) { 1.1249 + LOG((" write in progress (stream active)")); 1.1250 + return NS_ERROR_IN_PROGRESS; 1.1251 + } 1.1252 + 1.1253 + LOG((" size=%lld", *aDataSize)); 1.1254 + return NS_OK; 1.1255 +} 1.1256 + 1.1257 +NS_IMETHODIMP CacheEntry::MarkValid() 1.1258 +{ 1.1259 + // NOT IMPLEMENTED ACTUALLY 1.1260 + return NS_OK; 1.1261 +} 1.1262 + 1.1263 +NS_IMETHODIMP CacheEntry::MaybeMarkValid() 1.1264 +{ 1.1265 + // NOT IMPLEMENTED ACTUALLY 1.1266 + return NS_OK; 1.1267 +} 1.1268 + 1.1269 +NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess) 1.1270 +{ 1.1271 + *aWriteAccess = aWriteAllowed; 1.1272 + return NS_OK; 1.1273 +} 1.1274 + 1.1275 +NS_IMETHODIMP CacheEntry::Close() 1.1276 +{ 1.1277 + // NOT IMPLEMENTED ACTUALLY 1.1278 + return NS_OK; 1.1279 +} 1.1280 + 1.1281 +// nsIRunnable 1.1282 + 1.1283 +NS_IMETHODIMP CacheEntry::Run() 1.1284 +{ 1.1285 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1286 + 1.1287 + mozilla::MutexAutoLock lock(mLock); 1.1288 + 1.1289 + BackgroundOp(mBackgroundOperations.Grab()); 1.1290 + return NS_OK; 1.1291 +} 1.1292 + 1.1293 +// Management methods 1.1294 + 1.1295 +double CacheEntry::GetFrecency() const 1.1296 +{ 1.1297 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1298 + return mFrecency; 1.1299 +} 1.1300 + 1.1301 +uint32_t CacheEntry::GetExpirationTime() const 1.1302 +{ 1.1303 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1304 + return mSortingExpirationTime; 1.1305 +} 1.1306 + 1.1307 +bool CacheEntry::IsRegistered() const 1.1308 +{ 1.1309 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1310 + return mRegistration == REGISTERED; 1.1311 +} 1.1312 + 1.1313 +bool CacheEntry::CanRegister() const 1.1314 +{ 1.1315 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1316 + return mRegistration == NEVERREGISTERED; 1.1317 +} 1.1318 + 1.1319 +void CacheEntry::SetRegistered(bool aRegistered) 1.1320 +{ 1.1321 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1322 + 1.1323 + if (aRegistered) { 1.1324 + MOZ_ASSERT(mRegistration == NEVERREGISTERED); 1.1325 + mRegistration = REGISTERED; 1.1326 + } 1.1327 + else { 1.1328 + MOZ_ASSERT(mRegistration == REGISTERED); 1.1329 + mRegistration = DEREGISTERED; 1.1330 + } 1.1331 +} 1.1332 + 1.1333 +bool CacheEntry::Purge(uint32_t aWhat) 1.1334 +{ 1.1335 + LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat)); 1.1336 + 1.1337 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1338 + 1.1339 + switch (aWhat) { 1.1340 + case PURGE_DATA_ONLY_DISK_BACKED: 1.1341 + case PURGE_WHOLE_ONLY_DISK_BACKED: 1.1342 + // This is an in-memory only entry, don't purge it 1.1343 + if (!mUseDisk) { 1.1344 + LOG((" not using disk")); 1.1345 + return false; 1.1346 + } 1.1347 + } 1.1348 + 1.1349 + if (mState == WRITING || mState == LOADING || mFrecency == 0) { 1.1350 + // In-progress (write or load) entries should (at least for consistency and from 1.1351 + // the logical point of view) stay in memory. 1.1352 + // Zero-frecency entries are those which have never been given to any consumer, those 1.1353 + // are actually very fresh and should not go just because frecency had not been set 1.1354 + // so far. 1.1355 + LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency)); 1.1356 + return false; 1.1357 + } 1.1358 + 1.1359 + if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) { 1.1360 + // The file is used when there are open streams or chunks/metadata still waiting for 1.1361 + // write. In this case, this entry cannot be purged, otherwise reopenned entry 1.1362 + // would may not even find the data on disk - CacheFile is not shared and cannot be 1.1363 + // left orphan when its job is not done, hence keep the whole entry. 1.1364 + LOG((" file still under use")); 1.1365 + return false; 1.1366 + } 1.1367 + 1.1368 + switch (aWhat) { 1.1369 + case PURGE_WHOLE_ONLY_DISK_BACKED: 1.1370 + case PURGE_WHOLE: 1.1371 + { 1.1372 + if (!CacheStorageService::Self()->RemoveEntry(this, true)) { 1.1373 + LOG((" not purging, still referenced")); 1.1374 + return false; 1.1375 + } 1.1376 + 1.1377 + CacheStorageService::Self()->UnregisterEntry(this); 1.1378 + 1.1379 + // Entry removed it self from control arrays, return true 1.1380 + return true; 1.1381 + } 1.1382 + 1.1383 + case PURGE_DATA_ONLY_DISK_BACKED: 1.1384 + { 1.1385 + NS_ENSURE_SUCCESS(mFileStatus, false); 1.1386 + 1.1387 + mFile->ThrowMemoryCachedData(); 1.1388 + 1.1389 + // Entry has been left in control arrays, return false (not purged) 1.1390 + return false; 1.1391 + } 1.1392 + } 1.1393 + 1.1394 + LOG((" ?")); 1.1395 + return false; 1.1396 +} 1.1397 + 1.1398 +void CacheEntry::PurgeAndDoom() 1.1399 +{ 1.1400 + LOG(("CacheEntry::PurgeAndDoom [this=%p]", this)); 1.1401 + 1.1402 + CacheStorageService::Self()->RemoveEntry(this); 1.1403 + DoomAlreadyRemoved(); 1.1404 +} 1.1405 + 1.1406 +void CacheEntry::DoomAlreadyRemoved() 1.1407 +{ 1.1408 + LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this)); 1.1409 + 1.1410 + mozilla::MutexAutoLock lock(mLock); 1.1411 + 1.1412 + mIsDoomed = true; 1.1413 + 1.1414 + // This schedules dooming of the file, dooming is ensured to happen 1.1415 + // sooner than demand to open the same file made after this point 1.1416 + // so that we don't get this file for any newer opened entry(s). 1.1417 + DoomFile(); 1.1418 + 1.1419 + // Must force post here since may be indirectly called from 1.1420 + // InvokeCallbacks of this entry and we don't want reentrancy here. 1.1421 + BackgroundOp(Ops::CALLBACKS, true); 1.1422 + // Process immediately when on the management thread. 1.1423 + BackgroundOp(Ops::UNREGISTER); 1.1424 +} 1.1425 + 1.1426 +void CacheEntry::DoomFile() 1.1427 +{ 1.1428 + nsresult rv = NS_ERROR_NOT_AVAILABLE; 1.1429 + 1.1430 + if (NS_SUCCEEDED(mFileStatus)) { 1.1431 + // Always calls the callback asynchronously. 1.1432 + rv = mFile->Doom(mDoomCallback ? this : nullptr); 1.1433 + if (NS_SUCCEEDED(rv)) { 1.1434 + LOG((" file doomed")); 1.1435 + return; 1.1436 + } 1.1437 + 1.1438 + if (NS_ERROR_FILE_NOT_FOUND == rv) { 1.1439 + // File is set to be just memory-only, notify the callbacks 1.1440 + // and pretend dooming has succeeded. From point of view of 1.1441 + // the entry it actually did - the data is gone and cannot be 1.1442 + // reused. 1.1443 + rv = NS_OK; 1.1444 + } 1.1445 + } 1.1446 + 1.1447 + // Always posts to the main thread. 1.1448 + OnFileDoomed(rv); 1.1449 +} 1.1450 + 1.1451 +void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) 1.1452 +{ 1.1453 + mLock.AssertCurrentThreadOwns(); 1.1454 + 1.1455 + if (!CacheStorageService::IsOnManagementThread() || aForceAsync) { 1.1456 + if (mBackgroundOperations.Set(aOperations)) 1.1457 + CacheStorageService::Self()->Dispatch(this); 1.1458 + 1.1459 + LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations)); 1.1460 + return; 1.1461 + } 1.1462 + 1.1463 + mozilla::MutexAutoUnlock unlock(mLock); 1.1464 + 1.1465 + MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1.1466 + 1.1467 + if (aOperations & Ops::FRECENCYUPDATE) { 1.1468 + #ifndef M_LN2 1.1469 + #define M_LN2 0.69314718055994530942 1.1470 + #endif 1.1471 + 1.1472 + // Half-life is dynamic, in seconds. 1.1473 + static double half_life = CacheObserver::HalfLifeSeconds(); 1.1474 + // Must convert from seconds to milliseconds since PR_Now() gives usecs. 1.1475 + static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC); 1.1476 + 1.1477 + double now_decay = static_cast<double>(PR_Now()) * decay; 1.1478 + 1.1479 + if (mFrecency == 0) { 1.1480 + mFrecency = now_decay; 1.1481 + } 1.1482 + else { 1.1483 + // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but 1.1484 + // more precise. 1.1485 + mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay; 1.1486 + } 1.1487 + LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency)); 1.1488 + 1.1489 + // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that 1.1490 + // is not thread-safe) we must post to the main thread... 1.1491 + nsRefPtr<nsRunnableMethod<CacheEntry> > event = 1.1492 + NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency); 1.1493 + NS_DispatchToMainThread(event); 1.1494 + } 1.1495 + 1.1496 + if (aOperations & Ops::REGISTER) { 1.1497 + LOG(("CacheEntry REGISTER [this=%p]", this)); 1.1498 + 1.1499 + CacheStorageService::Self()->RegisterEntry(this); 1.1500 + } 1.1501 + 1.1502 + if (aOperations & Ops::UNREGISTER) { 1.1503 + LOG(("CacheEntry UNREGISTER [this=%p]", this)); 1.1504 + 1.1505 + CacheStorageService::Self()->UnregisterEntry(this); 1.1506 + } 1.1507 + 1.1508 + if (aOperations & Ops::CALLBACKS) { 1.1509 + LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this)); 1.1510 + 1.1511 + mozilla::MutexAutoLock lock(mLock); 1.1512 + InvokeCallbacks(); 1.1513 + } 1.1514 +} 1.1515 + 1.1516 +void CacheEntry::StoreFrecency() 1.1517 +{ 1.1518 + // No need for thread safety over mFrecency, it will be rewriten 1.1519 + // correctly on following invocation if broken by concurrency. 1.1520 + MOZ_ASSERT(NS_IsMainThread()); 1.1521 + mFile->SetFrecency(FRECENCY2INT(mFrecency)); 1.1522 +} 1.1523 + 1.1524 +// CacheOutputCloseListener 1.1525 + 1.1526 +CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry) 1.1527 +: mEntry(aEntry) 1.1528 +{ 1.1529 + MOZ_COUNT_CTOR(CacheOutputCloseListener); 1.1530 +} 1.1531 + 1.1532 +CacheOutputCloseListener::~CacheOutputCloseListener() 1.1533 +{ 1.1534 + MOZ_COUNT_DTOR(CacheOutputCloseListener); 1.1535 +} 1.1536 + 1.1537 +void CacheOutputCloseListener::OnOutputClosed() 1.1538 +{ 1.1539 + // We need this class and to redispatch since this callback is invoked 1.1540 + // under the file's lock and to do the job we need to enter the entry's 1.1541 + // lock too. That would lead to potential deadlocks. 1.1542 + NS_DispatchToCurrentThread(this); 1.1543 +} 1.1544 + 1.1545 +NS_IMETHODIMP CacheOutputCloseListener::Run() 1.1546 +{ 1.1547 + mEntry->OnOutputClosed(); 1.1548 + return NS_OK; 1.1549 +} 1.1550 + 1.1551 +// Memory reporting 1.1552 + 1.1553 +size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const 1.1554 +{ 1.1555 + size_t n = 0; 1.1556 + nsCOMPtr<nsISizeOf> sizeOf; 1.1557 + 1.1558 + n += mCallbacks.SizeOfExcludingThis(mallocSizeOf); 1.1559 + if (mFile) { 1.1560 + n += mFile->SizeOfIncludingThis(mallocSizeOf); 1.1561 + } 1.1562 + 1.1563 + sizeOf = do_QueryInterface(mURI); 1.1564 + if (sizeOf) { 1.1565 + n += sizeOf->SizeOfIncludingThis(mallocSizeOf); 1.1566 + } 1.1567 + 1.1568 + n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf); 1.1569 + n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf); 1.1570 + 1.1571 + // mDoomCallback is an arbitrary class that is probably reported elsewhere. 1.1572 + // mOutputStream is reported in mFile. 1.1573 + // mWriter is one of many handles we create, but (intentionally) not keep 1.1574 + // any reference to, so those unfortunatelly cannot be reported. Handles are 1.1575 + // small, though. 1.1576 + // mSecurityInfo doesn't impl nsISizeOf. 1.1577 + 1.1578 + return n; 1.1579 +} 1.1580 + 1.1581 +size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const 1.1582 +{ 1.1583 + return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); 1.1584 +} 1.1585 + 1.1586 +} // net 1.1587 +} // mozilla