CacheEntry.cpp (59531B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include <algorithm> 6 #include <math.h> 7 8 #include "CacheEntry.h" 9 10 #include "CacheFileUtils.h" 11 #include "CacheIndex.h" 12 #include "CacheLog.h" 13 #include "CacheObserver.h" 14 #include "CacheStorageService.h" 15 #include "mozilla/IntegerPrintfMacros.h" 16 #include "mozilla/Telemetry.h" 17 #include "mozilla/psm/TransportSecurityInfo.h" 18 #include "nsComponentManagerUtils.h" 19 #include "nsIAsyncOutputStream.h" 20 #include "nsICacheEntryOpenCallback.h" 21 #include "nsICacheStorage.h" 22 #include "nsIInputStream.h" 23 #include "nsIOutputStream.h" 24 #include "nsISeekableStream.h" 25 #include "nsIURI.h" 26 #include "nsNetCID.h" 27 #include "nsProxyRelease.h" 28 #include "nsServiceManagerUtils.h" 29 #include "nsString.h" 30 #include "nsThreadUtils.h" 31 32 namespace mozilla::net { 33 34 static uint32_t const ENTRY_WANTED = nsICacheEntryOpenCallback::ENTRY_WANTED; 35 static uint32_t const RECHECK_AFTER_WRITE_FINISHED = 36 nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED; 37 static uint32_t const ENTRY_NEEDS_REVALIDATION = 38 nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION; 39 static uint32_t const ENTRY_NOT_WANTED = 40 nsICacheEntryOpenCallback::ENTRY_NOT_WANTED; 41 42 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry) 43 44 // CacheEntryHandle 45 46 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry) : mEntry(aEntry) { 47 #ifdef DEBUG 48 if (!mEntry->HandlesCount()) { 49 // CacheEntry.mHandlesCount must go from zero to one only under 50 // the service lock. Can access CacheStorageService::Self() w/o a check 51 // since CacheEntry hrefs it. 52 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 53 } 54 #endif 55 56 mEntry->AddHandleRef(); 57 58 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry)); 59 } 60 61 NS_IMETHODIMP CacheEntryHandle::Dismiss() { 62 LOG(("CacheEntryHandle::Dismiss %p", this)); 63 64 if (mClosed.compareExchange(false, true)) { 65 mEntry->OnHandleClosed(this); 66 return NS_OK; 67 } 68 69 LOG((" already dropped")); 70 return NS_ERROR_UNEXPECTED; 71 } 72 73 CacheEntryHandle::~CacheEntryHandle() { 74 mEntry->ReleaseHandleRef(); 75 Dismiss(); 76 77 LOG(("CacheEntryHandle::~CacheEntryHandle %p", this)); 78 } 79 80 // CacheEntry::Callback 81 82 CacheEntry::Callback::Callback(CacheEntry* aEntry, 83 nsICacheEntryOpenCallback* aCallback, 84 bool aReadOnly, bool aCheckOnAnyThread, 85 bool aSecret) 86 : mEntry(aEntry), 87 mCallback(aCallback), 88 mTarget(GetCurrentSerialEventTarget()), 89 mReadOnly(aReadOnly), 90 mRevalidating(false), 91 mCheckOnAnyThread(aCheckOnAnyThread), 92 mRecheckAfterWrite(false), 93 mNotWanted(false), 94 mSecret(aSecret), 95 mDoomWhenFoundPinned(false), 96 mDoomWhenFoundNonPinned(false) { 97 MOZ_COUNT_CTOR(CacheEntry::Callback); 98 99 // The counter may go from zero to non-null only under the service lock 100 // but here we expect it to be already positive. 101 MOZ_ASSERT(mEntry->HandlesCount()); 102 mEntry->AddHandleRef(); 103 } 104 105 CacheEntry::Callback::Callback(CacheEntry* aEntry, 106 bool aDoomWhenFoundInPinStatus) 107 : mEntry(aEntry), 108 mReadOnly(false), 109 mRevalidating(false), 110 mCheckOnAnyThread(true), 111 mRecheckAfterWrite(false), 112 mNotWanted(false), 113 mSecret(false), 114 mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus), 115 mDoomWhenFoundNonPinned(!aDoomWhenFoundInPinStatus) { 116 MOZ_COUNT_CTOR(CacheEntry::Callback); 117 MOZ_ASSERT(mEntry->HandlesCount()); 118 mEntry->AddHandleRef(); 119 } 120 121 CacheEntry::Callback::Callback(CacheEntry::Callback const& aThat) 122 : mEntry(aThat.mEntry), 123 mCallback(aThat.mCallback), 124 mTarget(aThat.mTarget), 125 mReadOnly(aThat.mReadOnly), 126 mRevalidating(aThat.mRevalidating), 127 mCheckOnAnyThread(aThat.mCheckOnAnyThread), 128 mRecheckAfterWrite(aThat.mRecheckAfterWrite), 129 mNotWanted(aThat.mNotWanted), 130 mSecret(aThat.mSecret), 131 mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned), 132 mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned) { 133 MOZ_COUNT_CTOR(CacheEntry::Callback); 134 135 // The counter may go from zero to non-null only under the service lock 136 // but here we expect it to be already positive. 137 MOZ_ASSERT(mEntry->HandlesCount()); 138 mEntry->AddHandleRef(); 139 } 140 141 CacheEntry::Callback::~Callback() { 142 ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget); 143 144 mEntry->ReleaseHandleRef(); 145 MOZ_COUNT_DTOR(CacheEntry::Callback); 146 } 147 148 // We have locks on both this and aEntry 149 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) { 150 aEntry->mLock.AssertCurrentThreadOwns(); 151 mEntry->mLock.AssertCurrentThreadOwns(); 152 if (mEntry == aEntry) return; 153 154 // The counter may go from zero to non-null only under the service lock 155 // but here we expect it to be already positive. 156 MOZ_ASSERT(aEntry->HandlesCount()); 157 aEntry->AddHandleRef(); 158 mEntry->ReleaseHandleRef(); 159 mEntry = aEntry; 160 } 161 162 // This is called on entries in another entry's mCallback array, under the lock 163 // of that other entry. No other threads can access this entry at this time. 164 bool CacheEntry::Callback::DeferDoom(bool* aDoom) const 165 MOZ_NO_THREAD_SAFETY_ANALYSIS { 166 MOZ_ASSERT(mEntry->mPinningKnown); 167 168 if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) || 169 MOZ_UNLIKELY(mDoomWhenFoundPinned)) { 170 *aDoom = 171 (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && 172 MOZ_LIKELY(!mEntry->mPinned)) || 173 (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned)); 174 175 return true; 176 } 177 178 return false; 179 } 180 181 nsresult CacheEntry::Callback::OnCheckThread(bool* aOnCheckThread) const { 182 if (!mCheckOnAnyThread) { 183 // Check we are on the target 184 return mTarget->IsOnCurrentThread(aOnCheckThread); 185 } 186 187 // We can invoke check anywhere 188 *aOnCheckThread = true; 189 return NS_OK; 190 } 191 192 nsresult CacheEntry::Callback::OnAvailThread(bool* aOnAvailThread) const { 193 return mTarget->IsOnCurrentThread(aOnAvailThread); 194 } 195 196 // CacheEntry 197 198 NS_IMPL_ISUPPORTS(CacheEntry, nsIRunnable, CacheFileListener) 199 200 /* static */ 201 uint64_t CacheEntry::GetNextId() { 202 static Atomic<uint64_t, Relaxed> id(0); 203 return ++id; 204 } 205 206 CacheEntry::CacheEntry(const nsACString& aStorageID, const nsACString& aURI, 207 const nsACString& aEnhanceID, bool aUseDisk, 208 bool aSkipSizeCheck, bool aPin) 209 : mURI(aURI), 210 mEnhanceID(aEnhanceID), 211 mStorageID(aStorageID), 212 mUseDisk(aUseDisk), 213 mSkipSizeCheck(aSkipSizeCheck), 214 mPinned(aPin), 215 mSecurityInfoLoaded(false), 216 mPreventCallbacks(false), 217 mHasData(false), 218 mPinningKnown(false), 219 mBypassWriterLock(false), 220 mCacheEntryId(GetNextId()) { 221 LOG(("CacheEntry::CacheEntry [this=%p]", this)); 222 223 mService = CacheStorageService::Self(); 224 225 CacheStorageService::Self()->RecordMemoryOnlyEntry(this, !aUseDisk, 226 true /* overwrite */); 227 } 228 229 CacheEntry::~CacheEntry() { LOG(("CacheEntry::~CacheEntry [this=%p]", this)); } 230 231 char const* CacheEntry::StateString(uint32_t aState) { 232 switch (aState) { 233 case NOTLOADED: 234 return "NOTLOADED"; 235 case LOADING: 236 return "LOADING"; 237 case EMPTY: 238 return "EMPTY"; 239 case WRITING: 240 return "WRITING"; 241 case READY: 242 return "READY"; 243 case REVALIDATING: 244 return "REVALIDATING"; 245 } 246 247 return "?"; 248 } 249 250 nsresult CacheEntry::HashingKeyWithStorage(nsACString& aResult) const { 251 return HashingKey(mStorageID, mEnhanceID, mURI, aResult); 252 } 253 254 nsresult CacheEntry::HashingKey(nsACString& aResult) const { 255 return HashingKey(""_ns, mEnhanceID, mURI, aResult); 256 } 257 258 // static 259 nsresult CacheEntry::HashingKey(const nsACString& aStorageID, 260 const nsACString& aEnhanceID, nsIURI* aURI, 261 nsACString& aResult) { 262 nsAutoCString spec; 263 nsresult rv = aURI->GetAsciiSpec(spec); 264 NS_ENSURE_SUCCESS(rv, rv); 265 266 return HashingKey(aStorageID, aEnhanceID, spec, aResult); 267 } 268 269 // The hash key (which is also the filename) is: 270 // A[~B]:C 271 // Where A is the storage ID ([O<oa>,][a,][p,]), B is the optional 'id', 272 // and C is the URI 'oa' are the OriginAttributes in suffix form 273 // (i.e. |^key=value&key2=value2|) 274 275 // static 276 nsresult CacheEntry::HashingKey(const nsACString& aStorageID, 277 const nsACString& aEnhanceID, 278 const nsACString& aURISpec, 279 nsACString& aResult) { 280 /** 281 * This key is used to salt hash that is a base for disk file name. 282 * Changing it will cause we will not be able to find files on disk. 283 */ 284 285 aResult.Assign(aStorageID); 286 287 if (!aEnhanceID.IsEmpty()) { 288 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID); 289 } 290 291 // Appending directly 292 aResult.Append(':'); 293 aResult.Append(aURISpec); 294 295 return NS_OK; 296 } 297 298 nsresult CacheEntry::SetDictionary(DictionaryCacheEntry* aDict) { 299 mDict = aDict; 300 mFile->SetDictionary(aDict); 301 return NS_OK; 302 } 303 304 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, 305 uint32_t aFlags) { 306 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY; 307 bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY; 308 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE; 309 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY; 310 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED; 311 bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY; 312 313 if (MOZ_LOG_TEST(gCache2Log, LogLevel::Debug)) { 314 MutexAutoLock lock(mLock); 315 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", 316 this, StateString(mState), aFlags, aCallback)); 317 } 318 #ifdef DEBUG 319 { 320 // yes, if logging is on in DEBUG we'll take the lock twice in a row 321 MutexAutoLock lock(mLock); 322 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination"); 323 MOZ_ASSERT(!(truncate && mState > LOADING), 324 "Must not call truncate on already loaded entry"); 325 } 326 #endif 327 328 Callback callback(this, aCallback, readonly, multithread, secret); 329 330 if (!Open(callback, truncate, priority, bypassIfBusy)) { 331 // We get here when the callback wants to bypass cache when it's busy. 332 LOG((" writing or revalidating, callback wants to bypass cache")); 333 callback.mNotWanted = true; 334 InvokeAvailableCallback(callback); 335 } 336 } 337 338 bool CacheEntry::Open(Callback& aCallback, bool aTruncate, bool aPriority, 339 bool aBypassIfBusy) { 340 mozilla::MutexAutoLock lock(mLock); 341 342 // Check state under the lock 343 if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) { 344 return false; 345 } 346 347 RememberCallback(aCallback); 348 349 // Load() opens the lock 350 if (Load(aTruncate, aPriority)) { 351 // Loading is in progress... 352 return true; 353 } 354 355 InvokeCallbacks(); 356 357 return true; 358 } 359 360 bool CacheEntry::Load(bool aTruncate, bool aPriority) MOZ_REQUIRES(mLock) { 361 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate)); 362 363 mLock.AssertCurrentThreadOwns(); 364 365 if (mState > LOADING) { 366 LOG((" already loaded")); 367 return false; 368 } 369 370 if (mState == LOADING) { 371 LOG((" already loading")); 372 return true; 373 } 374 375 mState = LOADING; 376 377 MOZ_ASSERT(!mFile); 378 379 nsresult rv; 380 381 nsAutoCString fileKey; 382 rv = HashingKeyWithStorage(fileKey); 383 384 bool reportMiss = false; 385 386 // Check the index under two conditions for two states and take appropriate 387 // action: 388 // 1. When this is a disk entry and not told to truncate, check there is a 389 // disk file. 390 // If not, set the 'truncate' flag to true so that this entry will open 391 // instantly as a new one. 392 // 2. When this is a memory-only entry, check there is a disk file. 393 // If there is or could be, doom that file. 394 if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) { 395 // Check the index right now to know we have or have not the entry 396 // as soon as possible. 397 CacheIndex::EntryStatus status; 398 if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) { 399 switch (status) { 400 case CacheIndex::DOES_NOT_EXIST: 401 // Doesn't apply to memory-only entries, Load() is called only once 402 // for them and never again for their session lifetime. 403 if (!aTruncate && mUseDisk) { 404 LOG( 405 (" entry doesn't exist according information from the index, " 406 "truncating")); 407 reportMiss = true; 408 aTruncate = true; 409 } 410 break; 411 case CacheIndex::EXISTS: 412 case CacheIndex::DO_NOT_KNOW: 413 if (!mUseDisk) { 414 LOG( 415 (" entry open as memory-only, but there is a file, status=%d, " 416 "dooming it", 417 status)); 418 CacheFileIOManager::DoomFileByKey(fileKey, nullptr); 419 } 420 break; 421 } 422 } 423 } 424 425 mFile = new CacheFile(); 426 427 BackgroundOp(Ops::REGISTER); 428 429 bool directLoad = aTruncate || !mUseDisk; 430 if (directLoad) { 431 // mLoadStart will be used to calculate telemetry of life-time of this 432 // entry. Low resulution is then enough. 433 mLoadStart = TimeStamp::NowLoRes(); 434 mPinningKnown = true; 435 } else { 436 mLoadStart = TimeStamp::Now(); 437 } 438 439 { 440 mozilla::MutexAutoUnlock unlock(mLock); 441 442 if (reportMiss) { 443 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord( 444 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart); 445 } 446 447 LOG((" performing load, file=%p", mFile.get())); 448 if (NS_SUCCEEDED(rv)) { 449 rv = mFile->Init(fileKey, aTruncate, !mUseDisk, mSkipSizeCheck, aPriority, 450 mPinned, directLoad ? nullptr : this); 451 } 452 453 if (NS_FAILED(rv)) { 454 mFileStatus = rv; 455 AsyncDoom(nullptr); 456 return false; 457 } 458 } 459 460 if (directLoad) { 461 // Just fake the load has already been done as "new". 462 mFileStatus = NS_OK; 463 mState = EMPTY; 464 } 465 466 return mState == LOADING; 467 } 468 469 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew) { 470 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]", this, 471 static_cast<uint32_t>(aResult), aIsNew)); 472 473 MOZ_ASSERT(!mLoadStart.IsNull()); 474 475 if (NS_SUCCEEDED(aResult)) { 476 if (aIsNew) { 477 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord( 478 CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart); 479 } else { 480 CacheFileUtils::DetailedCacheHitTelemetry::AddRecord( 481 CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart); 482 } 483 } 484 485 // OnFileReady, that is the only code that can transit from LOADING 486 // to any follow-on state and can only be invoked ones on an entry. 487 // Until this moment there is no consumer that could manipulate 488 // the entry state. 489 490 mozilla::MutexAutoLock lock(mLock); 491 492 MOZ_ASSERT(mState == LOADING); 493 494 mState = (aIsNew || NS_FAILED(aResult)) ? EMPTY : READY; 495 496 mFileStatus = aResult; 497 498 mPinned = mFile->IsPinned(); 499 500 mPinningKnown = true; 501 LOG((" pinning=%d", (bool)mPinned)); 502 503 if (mState == READY) { 504 mHasData = true; 505 506 uint32_t frecency; 507 mFile->GetFrecency(&frecency); 508 // mFrecency is held in a double to increase computance precision. 509 // It is ok to persist frecency only as a uint32 with some math involved. 510 mFrecency = INT2FRECENCY(frecency); 511 } 512 513 InvokeCallbacks(); 514 515 return NS_OK; 516 } 517 518 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult) { 519 if (mDoomCallback) { 520 RefPtr<DoomCallbackRunnable> event = 521 new DoomCallbackRunnable(this, aResult); 522 NS_DispatchToMainThread(event); 523 } 524 525 return NS_OK; 526 } 527 528 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated( 529 bool aMemoryOnly, nsICacheEntryOpenCallback* aCallback) 530 MOZ_REQUIRES(mLock) { 531 LOG(("CacheEntry::ReopenTruncated [this=%p]", this)); 532 533 mLock.AssertCurrentThreadOwns(); 534 535 // Hold callbacks invocation, AddStorageEntry would invoke from doom 536 // prematurely 537 mPreventCallbacks = true; 538 539 RefPtr<CacheEntryHandle> handle; 540 RefPtr<CacheEntry> newEntry; 541 { 542 if (mPinned) { 543 MOZ_ASSERT(mUseDisk); 544 // We want to pin even no-store entries (the case we recreate a disk entry 545 // as a memory-only entry.) 546 aMemoryOnly = false; 547 } 548 549 mozilla::MutexAutoUnlock unlock(mLock); 550 551 // The following call dooms this entry (calls DoomAlreadyRemoved on us) 552 nsresult rv = CacheStorageService::Self()->AddStorageEntry( 553 GetStorageID(), GetURI(), GetEnhanceID(), mUseDisk && !aMemoryOnly, 554 mSkipSizeCheck, mPinned, 555 nsICacheStorage::OPEN_TRUNCATE, // truncate existing (this one) 556 getter_AddRefs(handle)); 557 558 if (NS_SUCCEEDED(rv)) { 559 newEntry = handle->Entry(); 560 LOG((" exchanged entry %p by entry %p, rv=0x%08" PRIx32, this, 561 newEntry.get(), static_cast<uint32_t>(rv))); 562 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE); 563 } else { 564 LOG((" exchanged of entry %p failed, rv=0x%08" PRIx32, this, 565 static_cast<uint32_t>(rv))); 566 AsyncDoom(nullptr); 567 } 568 } 569 570 mPreventCallbacks = false; 571 572 if (!newEntry) return nullptr; 573 574 newEntry->TransferCallbacks(*this); 575 mCallbacks.Clear(); 576 577 // Must return a new write handle, since the consumer is expected to 578 // write to this newly recreated entry. The |handle| is only a common 579 // reference counter and doesn't revert entry state back when write 580 // fails and also doesn't update the entry frecency. Not updating 581 // frecency causes entries to not be purged from our memory pools. 582 RefPtr<CacheEntryHandle> writeHandle = newEntry->NewWriteHandle(); 583 return writeHandle.forget(); 584 } 585 586 void CacheEntry::TransferCallbacks(CacheEntry& aFromEntry) { 587 mozilla::MutexAutoLock lock(mLock); 588 aFromEntry.mLock.AssertCurrentThreadOwns(); 589 590 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]", this, &aFromEntry)); 591 592 if (!mCallbacks.Length()) { 593 mCallbacks.SwapElements(aFromEntry.mCallbacks); 594 } else { 595 mCallbacks.AppendElements(aFromEntry.mCallbacks); 596 } 597 598 uint32_t callbacksLength = mCallbacks.Length(); 599 if (callbacksLength) { 600 // Carry the entry reference (unfortunately, needs to be done manually...) 601 for (uint32_t i = 0; i < callbacksLength; ++i) { 602 mCallbacks[i].ExchangeEntry(this); 603 } 604 605 BackgroundOp(Ops::CALLBACKS, true); 606 } 607 } 608 609 void CacheEntry::RememberCallback(Callback& aCallback) { 610 mLock.AssertCurrentThreadOwns(); 611 612 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]", this, 613 aCallback.mCallback.get(), StateString(mState))); 614 615 mCallbacks.AppendElement(aCallback); 616 } 617 618 void CacheEntry::InvokeCallbacksLock() { 619 mozilla::MutexAutoLock lock(mLock); 620 InvokeCallbacks(); 621 } 622 623 void CacheEntry::InvokeCallbacks() { 624 mLock.AssertCurrentThreadOwns(); 625 626 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this)); 627 628 // Invoke first all r/w callbacks, then all r/o callbacks. 629 if (InvokeCallbacks(false)) InvokeCallbacks(true); 630 631 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this)); 632 } 633 634 bool CacheEntry::InvokeCallbacks(bool aReadOnly) MOZ_REQUIRES(mLock) { 635 mLock.AssertCurrentThreadOwns(); 636 637 RefPtr<CacheEntryHandle> recreatedHandle; 638 639 uint32_t i = 0; 640 while (i < mCallbacks.Length()) { 641 if (mPreventCallbacks) { 642 LOG((" callbacks prevented!")); 643 return false; 644 } 645 646 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) { 647 if (!mBypassWriterLock) { 648 LOG((" entry is being written/revalidated")); 649 return false; 650 } 651 LOG((" entry is being written/revalidated but bypassing writer lock")); 652 } 653 654 bool recreate; 655 if (mCallbacks[i].DeferDoom(&recreate)) { 656 mCallbacks.RemoveElementAt(i); 657 if (!recreate) { 658 continue; 659 } 660 661 LOG((" defer doom marker callback hit positive, recreating")); 662 recreatedHandle = ReopenTruncated(!mUseDisk, nullptr); 663 break; 664 } 665 666 if (mCallbacks[i].mReadOnly != aReadOnly) { 667 // Callback is not r/w or r/o, go to another one in line 668 ++i; 669 continue; 670 } 671 672 bool onCheckThread; 673 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread); 674 675 if (NS_SUCCEEDED(rv) && !onCheckThread) { 676 // Redispatch to the target thread 677 rv = mCallbacks[i].mTarget->Dispatch( 678 NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock", this, 679 &CacheEntry::InvokeCallbacksLock), 680 nsIEventTarget::DISPATCH_NORMAL); 681 if (NS_SUCCEEDED(rv)) { 682 LOG((" re-dispatching to target thread")); 683 return false; 684 } 685 } 686 687 Callback callback = mCallbacks[i]; 688 mCallbacks.RemoveElementAt(i); 689 690 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) { 691 // Callback didn't fire, put it back and go to another one in line. 692 // Only reason InvokeCallback returns false is that onCacheEntryCheck 693 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other 694 // readers or potential writers would be unnecessarily kept from being 695 // invoked. 696 size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i)); 697 mCallbacks.InsertElementAt(pos, callback); 698 ++i; 699 } 700 } 701 702 if (recreatedHandle) { 703 // Must be released outside of the lock, enters InvokeCallback on the new 704 // entry 705 mozilla::MutexAutoUnlock unlock(mLock); 706 recreatedHandle = nullptr; 707 } 708 709 return true; 710 } 711 712 bool CacheEntry::InvokeCallback(Callback& aCallback) MOZ_REQUIRES(mLock) { 713 mLock.AssertCurrentThreadOwns(); 714 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this, 715 StateString(mState), aCallback.mCallback.get())); 716 717 // When this entry is doomed we want to notify the callback any time 718 if (!mIsDoomed) { 719 // When we are here, the entry must be loaded from disk 720 MOZ_ASSERT(mState > LOADING); 721 722 if (mState == WRITING || mState == REVALIDATING) { 723 if (!mBypassWriterLock) { 724 // Prevent invoking other callbacks since one of them is now writing 725 // or revalidating this entry. No consumers should get this entry 726 // until metadata are filled with values downloaded from the server 727 // or the entry revalidated and output stream has been opened. 728 LOG((" entry is being written/revalidated, callback bypassed")); 729 return false; 730 } 731 LOG((" entry is being written/revalidated but bypassing writer lock")); 732 } 733 734 // mRecheckAfterWrite flag already set means the callback has already passed 735 // the onCacheEntryCheck call. Until the current write is not finished this 736 // callback will be bypassed. 737 if (!aCallback.mRecheckAfterWrite) { 738 if (!aCallback.mReadOnly) { 739 if (mState == EMPTY) { 740 // Advance to writing state, we expect to invoke the callback and let 741 // it fill content of this entry. Must set and check the state here 742 // to prevent more then one 743 mState = WRITING; 744 LOG((" advancing to WRITING state")); 745 } 746 747 if (!aCallback.mCallback) { 748 // We can be given no callback only in case of recreate, it is ok 749 // to advance to WRITING state since the caller of recreate is 750 // expected to write this entry now. 751 return true; 752 } 753 } 754 755 if (mState == READY) { 756 // Metadata present, validate the entry 757 uint32_t checkResult; 758 { 759 // mayhemer: TODO check and solve any potential races of concurent 760 // OnCacheEntryCheck 761 mozilla::MutexAutoUnlock unlock(mLock); 762 763 RefPtr<CacheEntryHandle> handle = NewHandle(); 764 765 nsresult rv = 766 aCallback.mCallback->OnCacheEntryCheck(handle, &checkResult); 767 LOG((" OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32, 768 static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult))); 769 770 if (NS_FAILED(rv)) checkResult = ENTRY_NOT_WANTED; 771 } 772 773 aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION; 774 775 switch (checkResult) { 776 case ENTRY_WANTED: 777 // Nothing more to do here, the consumer is responsible to handle 778 // the result of OnCacheEntryCheck it self. 779 // Proceed to callback... 780 break; 781 782 case RECHECK_AFTER_WRITE_FINISHED: 783 LOG( 784 (" consumer will check on the entry again after write is " 785 "done")); 786 // The consumer wants the entry to complete first. 787 aCallback.mRecheckAfterWrite = true; 788 break; 789 790 case ENTRY_NEEDS_REVALIDATION: 791 LOG((" will be holding callbacks until entry is revalidated")); 792 // State is READY now and from that state entry cannot transit to 793 // any other state then REVALIDATING for which cocurrency is not an 794 // issue. Potentially no need to lock here. 795 mState = REVALIDATING; 796 break; 797 798 case ENTRY_NOT_WANTED: 799 LOG((" consumer not interested in the entry")); 800 // Do not give this entry to the consumer, it is not interested in 801 // us. 802 aCallback.mNotWanted = true; 803 break; 804 } 805 } 806 } 807 } 808 809 if (aCallback.mCallback) { 810 if (!mIsDoomed && aCallback.mRecheckAfterWrite) { 811 // If we don't have data and the callback wants a complete entry, 812 // don't invoke now. 813 bool bypass = !mHasData; 814 if (!bypass && NS_SUCCEEDED(mFileStatus)) { 815 int64_t _unused; 816 bypass = !mFile->DataSize(&_unused); 817 } 818 819 if (bypass) { 820 LOG((" bypassing, entry data still being written")); 821 return false; 822 } 823 824 // Entry is complete now, do the check+avail call again 825 aCallback.mRecheckAfterWrite = false; 826 return InvokeCallback(aCallback); 827 } 828 829 mozilla::MutexAutoUnlock unlock(mLock); 830 InvokeAvailableCallback(aCallback); 831 } 832 833 return true; 834 } 835 836 void CacheEntry::InvokeAvailableCallback(Callback const& aCallback) { 837 nsresult rv; 838 uint32_t state; 839 { 840 mozilla::MutexAutoLock lock(mLock); 841 state = mState; 842 LOG( 843 ("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, " 844 "r/o=%d, " 845 "n/w=%d]", 846 this, StateString(mState), aCallback.mCallback.get(), 847 aCallback.mReadOnly, aCallback.mNotWanted)); 848 849 // When we are here, the entry must be loaded from disk 850 MOZ_ASSERT(state > LOADING || mIsDoomed); 851 } 852 853 bool onAvailThread; 854 rv = aCallback.OnAvailThread(&onAvailThread); 855 if (NS_FAILED(rv)) { 856 LOG((" target thread dead?")); 857 return; 858 } 859 860 if (!onAvailThread) { 861 // Dispatch to the right thread 862 RefPtr<AvailableCallbackRunnable> event = 863 new AvailableCallbackRunnable(this, aCallback); 864 865 rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 866 LOG((" redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv))); 867 return; 868 } 869 870 if (mIsDoomed || aCallback.mNotWanted) { 871 LOG( 872 (" doomed or not wanted, notifying OCEA with " 873 "NS_ERROR_CACHE_KEY_NOT_FOUND")); 874 aCallback.mCallback->OnCacheEntryAvailable(nullptr, false, 875 NS_ERROR_CACHE_KEY_NOT_FOUND); 876 return; 877 } 878 879 if (state == READY) { 880 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK")); 881 882 if (!aCallback.mSecret) { 883 mozilla::MutexAutoLock lock(mLock); 884 BackgroundOp(Ops::FRECENCYUPDATE); 885 } 886 887 OnFetched(aCallback); 888 889 RefPtr<CacheEntryHandle> handle = NewHandle(); 890 aCallback.mCallback->OnCacheEntryAvailable(handle, false, NS_OK); 891 return; 892 } 893 894 // R/O callbacks may do revalidation, let them fall through 895 if (aCallback.mReadOnly && !aCallback.mRevalidating) { 896 LOG( 897 (" r/o and not ready, notifying OCEA with " 898 "NS_ERROR_CACHE_KEY_NOT_FOUND")); 899 aCallback.mCallback->OnCacheEntryAvailable(nullptr, false, 900 NS_ERROR_CACHE_KEY_NOT_FOUND); 901 return; 902 } 903 904 // This is a new or potentially non-valid entry and needs to be fetched first. 905 // The CacheEntryHandle blocks other consumers until the channel 906 // either releases the entry or marks metadata as filled or whole entry valid, 907 // i.e. until MetaDataReady() or SetValid() on the entry is called 908 // respectively. 909 910 // Consumer will be responsible to fill or validate the entry metadata and 911 // data. 912 913 OnFetched(aCallback); 914 915 RefPtr<CacheEntryHandle> handle = NewWriteHandle(); 916 rv = aCallback.mCallback->OnCacheEntryAvailable(handle, state == WRITING, 917 NS_OK); 918 919 if (NS_FAILED(rv)) { 920 LOG((" writing/revalidating failed (0x%08" PRIx32 ")", 921 static_cast<uint32_t>(rv))); 922 923 // Consumer given a new entry failed to take care of the entry. 924 OnHandleClosed(handle); 925 return; 926 } 927 928 LOG((" writing/revalidating")); 929 } 930 931 void CacheEntry::OnFetched(Callback const& aCallback) { 932 if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) { 933 // Let the last-fetched and fetch-count properties be updated. 934 mFile->OnFetched(); 935 } 936 } 937 938 CacheEntryHandle* CacheEntry::NewHandle() { return new CacheEntryHandle(this); } 939 940 CacheEntryHandle* CacheEntry::NewWriteHandle() { 941 mozilla::MutexAutoLock lock(mLock); 942 943 // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be 944 // used only along with OPEN_READONLY, but there is no need to enforce that. 945 BackgroundOp(Ops::FRECENCYUPDATE); 946 947 return (mWriter = NewHandle()); 948 } 949 950 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle) { 951 mozilla::MutexAutoLock lock(mLock); 952 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, 953 StateString(mState), aHandle)); 954 955 if (mIsDoomed && NS_SUCCEEDED(mFileStatus) && 956 // Note: mHandlesCount is dropped before this method is called 957 (mHandlesCount == 0 || 958 (mHandlesCount == 1 && mWriter && mWriter != aHandle))) { 959 // This entry is no longer referenced from outside and is doomed. 960 // We can do this also when there is just reference from the writer, 961 // no one else could ever reach the written data. 962 // Tell the file to kill the handle, i.e. bypass any I/O operations 963 // on it except removing the file. 964 mFile->Kill(); 965 } 966 967 if (mWriter != aHandle) { 968 LOG((" not the writer")); 969 return; 970 } 971 972 if (mOutputStream) { 973 LOG((" abandoning phantom output stream")); 974 // No one took our internal output stream, so there are no data 975 // and output stream has to be open symultaneously with input stream 976 // on this entry again. 977 mHasData = false; 978 // This asynchronously ends up invoking callbacks on this entry 979 // through OnOutputClosed() call. 980 mOutputStream->Close(); 981 mOutputStream = nullptr; 982 } else { 983 // We must always redispatch, otherwise there is a risk of stack 984 // overflow. This code can recurse deeply. It won't execute sooner 985 // than we release mLock. 986 BackgroundOp(Ops::CALLBACKS, true); 987 } 988 989 mWriter = nullptr; 990 991 // Reset bypass flag when writer is cleared 992 if (mBypassWriterLock) { 993 mBypassWriterLock = false; 994 LOG((" reset bypass writer lock flag due to writer cleared")); 995 } 996 997 if (mState == WRITING) { 998 LOG((" reverting to state EMPTY - write failed")); 999 mState = EMPTY; 1000 } else if (mState == REVALIDATING) { 1001 LOG((" reverting to state READY - reval failed")); 1002 mState = READY; 1003 } 1004 1005 if (mState == READY && !mHasData) { 1006 // We may get to this state when following steps happen: 1007 // 1. a new entry is given to a consumer 1008 // 2. the consumer calls MetaDataReady(), we transit to READY 1009 // 3. abandons the entry w/o opening the output stream, mHasData left false 1010 // 1011 // In this case any following consumer will get a ready entry (with 1012 // metadata) but in state like the entry data write was still happening (was 1013 // in progress) and will indefinitely wait for the entry data or even the 1014 // entry itself when RECHECK_AFTER_WRITE is returned from onCacheEntryCheck. 1015 LOG( 1016 (" we are in READY state, pretend we have data regardless it" 1017 " has actully been never touched")); 1018 mHasData = true; 1019 } 1020 } 1021 1022 void CacheEntry::OnOutputClosed() { 1023 // Called when the file's output stream is closed. Invoke any callbacks 1024 // waiting for complete entry. 1025 1026 mozilla::MutexAutoLock lock(mLock); 1027 InvokeCallbacks(); 1028 } 1029 1030 bool CacheEntry::IsReferenced() const { 1031 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); 1032 1033 // Increasing this counter from 0 to non-null and this check both happen only 1034 // under the service lock. 1035 return mHandlesCount > 0; 1036 } 1037 1038 void CacheEntry::SetBypassWriterLock(bool aBypass) { 1039 mozilla::MutexAutoLock lock(mLock); 1040 LOG(("CacheEntry::SetBypassWriterLock [this=%p, bypass=%d]", this, aBypass)); 1041 mBypassWriterLock = aBypass; 1042 1043 if (aBypass) { 1044 // Invoke callbacks that were blocked by writer state 1045 InvokeCallbacks(); 1046 } 1047 } 1048 1049 bool CacheEntry::IsFileDoomed() { 1050 if (NS_SUCCEEDED(mFileStatus)) { 1051 return mFile->IsDoomed(); 1052 } 1053 1054 return false; 1055 } 1056 1057 uint32_t CacheEntry::GetMetadataMemoryConsumption() { 1058 NS_ENSURE_SUCCESS(mFileStatus, 0); 1059 1060 uint32_t size; 1061 if (NS_FAILED(mFile->ElementsSize(&size))) return 0; 1062 1063 return size; 1064 } 1065 1066 // nsICacheEntry 1067 1068 nsresult CacheEntry::GetPersistent(bool* aPersistToDisk) { 1069 // No need to sync when only reading. 1070 // When consumer needs to be consistent with state of the memory storage 1071 // entries table, then let it use GetUseDisk getter that must be called under 1072 // the service lock. 1073 *aPersistToDisk = mUseDisk; 1074 return NS_OK; 1075 } 1076 1077 nsresult CacheEntry::GetKey(nsACString& aKey) { 1078 aKey.Assign(mURI); 1079 return NS_OK; 1080 } 1081 1082 nsresult CacheEntry::GetCacheEntryId(uint64_t* aCacheEntryId) { 1083 *aCacheEntryId = mCacheEntryId; 1084 return NS_OK; 1085 } 1086 1087 nsresult CacheEntry::GetFetchCount(uint32_t* aFetchCount) { 1088 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1089 1090 return mFile->GetFetchCount(aFetchCount); 1091 } 1092 1093 nsresult CacheEntry::GetLastFetched(uint32_t* aLastFetched) { 1094 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1095 1096 return mFile->GetLastFetched(aLastFetched); 1097 } 1098 1099 nsresult CacheEntry::GetLastModified(uint32_t* aLastModified) { 1100 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1101 1102 return mFile->GetLastModified(aLastModified); 1103 } 1104 1105 nsresult CacheEntry::GetExpirationTime(uint32_t* aExpirationTime) { 1106 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1107 1108 return mFile->GetExpirationTime(aExpirationTime); 1109 } 1110 1111 nsresult CacheEntry::GetOnStartTime(uint64_t* aTime) { 1112 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1113 return mFile->GetOnStartTime(aTime); 1114 } 1115 1116 nsresult CacheEntry::GetOnStopTime(uint64_t* aTime) { 1117 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1118 return mFile->GetOnStopTime(aTime); 1119 } 1120 1121 nsresult CacheEntry::GetReadyOrRevalidating(bool* aReady) { 1122 mozilla::MutexAutoLock lock(mLock); 1123 *aReady = (mState == READY || mState == REVALIDATING); 1124 return NS_OK; 1125 } 1126 1127 nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime, 1128 uint64_t aOnStopTime) { 1129 if (NS_SUCCEEDED(mFileStatus)) { 1130 return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime); 1131 } 1132 return NS_ERROR_NOT_AVAILABLE; 1133 } 1134 1135 nsresult CacheEntry::SetContentType(uint8_t aContentType) { 1136 NS_ENSURE_ARG_MAX(aContentType, nsICacheEntry::CONTENT_TYPE_LAST - 1); 1137 1138 if (NS_SUCCEEDED(mFileStatus)) { 1139 return mFile->SetContentType(aContentType); 1140 } 1141 return NS_ERROR_NOT_AVAILABLE; 1142 } 1143 1144 nsresult CacheEntry::GetIsForcedValid(bool* aIsForcedValid) { 1145 NS_ENSURE_ARG(aIsForcedValid); 1146 1147 #ifdef DEBUG 1148 { 1149 mozilla::MutexAutoLock lock(mLock); 1150 MOZ_ASSERT(mState > LOADING); 1151 } 1152 #endif 1153 if (mPinned) { 1154 *aIsForcedValid = true; 1155 return NS_OK; 1156 } 1157 1158 nsAutoCString key; 1159 nsresult rv = HashingKey(key); 1160 if (NS_FAILED(rv)) { 1161 return rv; 1162 } 1163 1164 *aIsForcedValid = 1165 CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key); 1166 LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, 1167 *aIsForcedValid)); 1168 1169 return NS_OK; 1170 } 1171 1172 nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture) { 1173 LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, 1174 aSecondsToTheFuture)); 1175 1176 nsAutoCString key; 1177 nsresult rv = HashingKey(key); 1178 if (NS_FAILED(rv)) { 1179 return rv; 1180 } 1181 1182 CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key, 1183 aSecondsToTheFuture); 1184 1185 return NS_OK; 1186 } 1187 1188 nsresult CacheEntry::MarkForcedValidUse() { 1189 LOG(("CacheEntry::MarkForcedValidUse [this=%p, ]", this)); 1190 1191 nsAutoCString key; 1192 nsresult rv = HashingKey(key); 1193 if (NS_FAILED(rv)) { 1194 return rv; 1195 } 1196 1197 CacheStorageService::Self()->MarkForcedValidEntryUse(mStorageID, key); 1198 return NS_OK; 1199 } 1200 1201 nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime) { 1202 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1203 1204 nsresult rv = mFile->SetExpirationTime(aExpirationTime); 1205 NS_ENSURE_SUCCESS(rv, rv); 1206 1207 // Aligned assignment, thus atomic. 1208 mSortingExpirationTime = aExpirationTime; 1209 return NS_OK; 1210 } 1211 1212 nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream** _retval) { 1213 LOG(("CacheEntry::OpenInputStream [this=%p]", this)); 1214 return OpenInputStreamInternal(offset, nullptr, _retval); 1215 } 1216 1217 nsresult CacheEntry::OpenAlternativeInputStream(const nsACString& type, 1218 nsIInputStream** _retval) { 1219 LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this, 1220 PromiseFlatCString(type).get())); 1221 return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval); 1222 } 1223 1224 nsresult CacheEntry::OpenInputStreamInternal(int64_t offset, 1225 const char* aAltDataType, 1226 nsIInputStream** _retval) { 1227 LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this)); 1228 1229 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1230 1231 nsresult rv; 1232 1233 RefPtr<CacheEntryHandle> selfHandle = NewHandle(); 1234 1235 nsCOMPtr<nsIInputStream> stream; 1236 if (aAltDataType) { 1237 rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType, 1238 getter_AddRefs(stream)); 1239 if (NS_FAILED(rv)) { 1240 // Failure of this method may be legal when the alternative data requested 1241 // is not available or of a different type. Console error logs are 1242 // ensured by CacheFile::OpenAlternativeInputStream. 1243 return rv; 1244 } 1245 } else { 1246 rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream)); 1247 NS_ENSURE_SUCCESS(rv, rv); 1248 } 1249 1250 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv); 1251 NS_ENSURE_SUCCESS(rv, rv); 1252 1253 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); 1254 NS_ENSURE_SUCCESS(rv, rv); 1255 1256 mozilla::MutexAutoLock lock(mLock); 1257 1258 if (!mHasData) { 1259 // So far output stream on this new entry not opened, do it now. 1260 LOG((" creating phantom output stream")); 1261 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream)); 1262 NS_ENSURE_SUCCESS(rv, rv); 1263 } 1264 1265 stream.forget(_retval); 1266 return NS_OK; 1267 } 1268 1269 nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize, 1270 nsIOutputStream** _retval) { 1271 LOG(("CacheEntry::OpenOutputStream [this=%p]", this)); 1272 1273 nsresult rv; 1274 1275 mozilla::MutexAutoLock lock(mLock); 1276 1277 MOZ_ASSERT(mState > EMPTY); 1278 1279 if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) { 1280 LOG((" entry would exceed size limit")); 1281 return NS_ERROR_FILE_TOO_BIG; 1282 } 1283 1284 if (mOutputStream && !mIsDoomed) { 1285 LOG((" giving phantom output stream")); 1286 mOutputStream.forget(_retval); 1287 } else { 1288 rv = OpenOutputStreamInternal(offset, _retval); 1289 if (NS_FAILED(rv)) return rv; 1290 } 1291 1292 // Entry considered ready when writer opens output stream. 1293 if (mState < READY) mState = READY; 1294 1295 // Invoke any pending readers now. 1296 InvokeCallbacks(); 1297 1298 return NS_OK; 1299 } 1300 1301 nsresult CacheEntry::OpenAlternativeOutputStream( 1302 const nsACString& type, int64_t predictedSize, 1303 nsIAsyncOutputStream** _retval) { 1304 LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this, 1305 PromiseFlatCString(type).get())); 1306 1307 nsresult rv; 1308 1309 if (type.IsEmpty()) { 1310 // The empty string is reserved to mean no alt-data available. 1311 return NS_ERROR_INVALID_ARG; 1312 } 1313 1314 mozilla::MutexAutoLock lock(mLock); 1315 1316 if (!mHasData || mState < READY || mOutputStream || mIsDoomed) { 1317 LOG((" entry not in state to write alt-data")); 1318 return NS_ERROR_NOT_AVAILABLE; 1319 } 1320 1321 if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) { 1322 LOG((" entry would exceed size limit")); 1323 return NS_ERROR_FILE_TOO_BIG; 1324 } 1325 1326 nsCOMPtr<nsIAsyncOutputStream> stream; 1327 rv = mFile->OpenAlternativeOutputStream( 1328 nullptr, PromiseFlatCString(type).get(), getter_AddRefs(stream)); 1329 NS_ENSURE_SUCCESS(rv, rv); 1330 1331 stream.swap(*_retval); 1332 return NS_OK; 1333 } 1334 1335 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, 1336 nsIOutputStream** _retval) { 1337 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this)); 1338 1339 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1340 1341 mLock.AssertCurrentThreadOwns(); 1342 1343 if (mIsDoomed) { 1344 LOG((" doomed...")); 1345 return NS_ERROR_NOT_AVAILABLE; 1346 } 1347 1348 MOZ_ASSERT(mState > LOADING); 1349 1350 nsresult rv; 1351 1352 // No need to sync on mUseDisk here, we don't need to be consistent 1353 // with content of the memory storage entries hash table. 1354 if (!mUseDisk) { 1355 rv = mFile->SetMemoryOnly(); 1356 NS_ENSURE_SUCCESS(rv, rv); 1357 } 1358 1359 RefPtr<CacheOutputCloseListener> listener = 1360 new CacheOutputCloseListener(this); 1361 1362 nsCOMPtr<nsIOutputStream> stream; 1363 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream)); 1364 NS_ENSURE_SUCCESS(rv, rv); 1365 1366 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream, &rv); 1367 NS_ENSURE_SUCCESS(rv, rv); 1368 1369 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); 1370 NS_ENSURE_SUCCESS(rv, rv); 1371 1372 // Prevent opening output stream again. 1373 mHasData = true; 1374 1375 stream.swap(*_retval); 1376 return NS_OK; 1377 } 1378 1379 nsresult CacheEntry::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { 1380 { 1381 mozilla::MutexAutoLock lock(mLock); 1382 if (mSecurityInfoLoaded) { 1383 *aSecurityInfo = do_AddRef(mSecurityInfo).take(); 1384 return NS_OK; 1385 } 1386 } 1387 1388 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1389 1390 nsCString info; 1391 nsresult rv = mFile->GetElement("security-info", getter_Copies(info)); 1392 NS_ENSURE_SUCCESS(rv, rv); 1393 nsCOMPtr<nsITransportSecurityInfo> securityInfo; 1394 if (!info.IsVoid()) { 1395 rv = mozilla::psm::TransportSecurityInfo::Read( 1396 info, getter_AddRefs(securityInfo)); 1397 NS_ENSURE_SUCCESS(rv, rv); 1398 } 1399 if (!securityInfo) { 1400 return NS_ERROR_NOT_AVAILABLE; 1401 } 1402 1403 { 1404 mozilla::MutexAutoLock lock(mLock); 1405 1406 mSecurityInfo.swap(securityInfo); 1407 mSecurityInfoLoaded = true; 1408 1409 *aSecurityInfo = do_AddRef(mSecurityInfo).take(); 1410 } 1411 1412 return NS_OK; 1413 } 1414 1415 nsresult CacheEntry::SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) { 1416 nsresult rv; 1417 1418 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus); 1419 1420 { 1421 mozilla::MutexAutoLock lock(mLock); 1422 1423 mSecurityInfo = aSecurityInfo; 1424 mSecurityInfoLoaded = true; 1425 } 1426 1427 nsCString info; 1428 if (aSecurityInfo) { 1429 rv = aSecurityInfo->ToString(info); 1430 NS_ENSURE_SUCCESS(rv, rv); 1431 } 1432 1433 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr); 1434 NS_ENSURE_SUCCESS(rv, rv); 1435 1436 return NS_OK; 1437 } 1438 1439 nsresult CacheEntry::GetStorageDataSize(uint32_t* aStorageDataSize) { 1440 NS_ENSURE_ARG(aStorageDataSize); 1441 1442 int64_t dataSize; 1443 nsresult rv = GetDataSize(&dataSize); 1444 if (NS_FAILED(rv)) return rv; 1445 1446 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize); 1447 1448 return NS_OK; 1449 } 1450 1451 nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback* aCallback) { 1452 LOG(("CacheEntry::AsyncDoom [this=%p]", this)); 1453 1454 { 1455 mozilla::MutexAutoLock lock(mLock); 1456 1457 if (mIsDoomed || mDoomCallback) { 1458 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state 1459 } 1460 1461 RemoveForcedValidity(); 1462 1463 mIsDoomed = true; 1464 mDoomCallback = aCallback; 1465 } 1466 1467 // This immediately removes the entry from the master hashtable and also 1468 // immediately dooms the file. This way we make sure that any consumer 1469 // after this point asking for the same entry won't get 1470 // a) this entry 1471 // b) a new entry with the same file 1472 PurgeAndDoom(); 1473 1474 return NS_OK; 1475 } 1476 1477 nsresult CacheEntry::GetMetaDataElement(const char* aKey, char** aRetval) { 1478 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1479 1480 return mFile->GetElement(aKey, aRetval); 1481 } 1482 1483 nsresult CacheEntry::SetMetaDataElement(const char* aKey, const char* aValue) { 1484 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1485 1486 return mFile->SetElement(aKey, aValue); 1487 } 1488 1489 nsresult CacheEntry::GetIsEmpty(bool* aEmpty) { 1490 *aEmpty = GetMetadataMemoryConsumption() == 0; 1491 return NS_OK; 1492 } 1493 1494 nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) { 1495 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); 1496 1497 return mFile->VisitMetaData(aVisitor); 1498 } 1499 1500 nsresult CacheEntry::MetaDataReady() { 1501 mozilla::MutexAutoLock lock(mLock); 1502 1503 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, 1504 StateString(mState))); 1505 1506 MOZ_ASSERT(mState > EMPTY); 1507 1508 if (mState == WRITING) { 1509 mState = READY; 1510 1511 // Reset bypass flag when transitioning to READY state 1512 if (mBypassWriterLock) { 1513 mBypassWriterLock = false; 1514 LOG((" reset bypass writer lock flag due to state transition to READY")); 1515 } 1516 } 1517 1518 InvokeCallbacks(); 1519 1520 return NS_OK; 1521 } 1522 1523 nsresult CacheEntry::SetValid() { 1524 nsCOMPtr<nsIOutputStream> outputStream; 1525 1526 { 1527 mozilla::MutexAutoLock lock(mLock); 1528 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, 1529 StateString(mState))); 1530 1531 MOZ_ASSERT(mState > EMPTY); 1532 1533 mState = READY; 1534 mHasData = true; 1535 1536 // Reset bypass flag when transitioning to READY state 1537 if (mBypassWriterLock) { 1538 mBypassWriterLock = false; 1539 LOG((" reset bypass writer lock flag due to state transition to READY")); 1540 } 1541 1542 InvokeCallbacks(); 1543 1544 outputStream.swap(mOutputStream); 1545 } 1546 1547 if (outputStream) { 1548 LOG((" abandoning phantom output stream")); 1549 outputStream->Close(); 1550 } 1551 1552 return NS_OK; 1553 } 1554 1555 nsresult CacheEntry::Recreate(bool aMemoryOnly, nsICacheEntry** _retval) { 1556 mozilla::MutexAutoLock lock(mLock); 1557 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState))); 1558 1559 RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr); 1560 if (handle) { 1561 handle.forget(_retval); 1562 return NS_OK; 1563 } 1564 1565 BackgroundOp(Ops::CALLBACKS, true); 1566 return NS_ERROR_NOT_AVAILABLE; 1567 } 1568 1569 nsresult CacheEntry::GetDataSize(int64_t* aDataSize) { 1570 LOG(("CacheEntry::GetDataSize [this=%p]", this)); 1571 *aDataSize = 0; 1572 1573 { 1574 mozilla::MutexAutoLock lock(mLock); 1575 1576 if (!mHasData) { 1577 LOG((" write in progress (no data)")); 1578 return NS_ERROR_IN_PROGRESS; 1579 } 1580 } 1581 1582 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus); 1583 1584 // mayhemer: TODO Problem with compression? 1585 if (!mFile->DataSize(aDataSize)) { 1586 LOG((" write in progress (stream active)")); 1587 return NS_ERROR_IN_PROGRESS; 1588 } 1589 1590 LOG((" size=%" PRId64, *aDataSize)); 1591 return NS_OK; 1592 } 1593 1594 nsresult CacheEntry::GetAltDataSize(int64_t* aDataSize) { 1595 LOG(("CacheEntry::GetAltDataSize [this=%p]", this)); 1596 if (NS_FAILED(mFileStatus)) { 1597 return mFileStatus; 1598 } 1599 return mFile->GetAltDataSize(aDataSize); 1600 } 1601 1602 nsresult CacheEntry::GetAltDataType(nsACString& aType) { 1603 LOG(("CacheEntry::GetAltDataType [this=%p]", this)); 1604 if (NS_FAILED(mFileStatus)) { 1605 return mFileStatus; 1606 } 1607 return mFile->GetAltDataType(aType); 1608 } 1609 1610 nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) { 1611 if (NS_FAILED(mFileStatus)) { 1612 return NS_ERROR_NOT_AVAILABLE; 1613 } 1614 1615 return mFile->GetDiskStorageSizeInKB(aDiskStorageSize); 1616 } 1617 1618 nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo) { 1619 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID); 1620 if (!info) { 1621 return NS_ERROR_FAILURE; 1622 } 1623 1624 info.forget(aInfo); 1625 1626 return NS_OK; 1627 } 1628 1629 // nsIRunnable 1630 1631 NS_IMETHODIMP CacheEntry::Run() { 1632 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1633 1634 mozilla::MutexAutoLock lock(mLock); 1635 1636 BackgroundOp(mBackgroundOperations.Grab()); 1637 return NS_OK; 1638 } 1639 1640 // Management methods 1641 1642 double CacheEntry::GetFrecency() const { 1643 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1644 return mFrecency; 1645 } 1646 1647 uint32_t CacheEntry::GetExpirationTime() const { 1648 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1649 return mSortingExpirationTime; 1650 } 1651 1652 bool CacheEntry::IsRegistered() const { 1653 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1654 return mRegistration == REGISTERED; 1655 } 1656 1657 bool CacheEntry::CanRegister() const { 1658 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1659 return mRegistration == NEVERREGISTERED; 1660 } 1661 1662 void CacheEntry::SetRegistered(bool aRegistered) { 1663 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1664 1665 if (aRegistered) { 1666 MOZ_ASSERT(mRegistration == NEVERREGISTERED); 1667 mRegistration = REGISTERED; 1668 } else { 1669 MOZ_ASSERT(mRegistration == REGISTERED); 1670 mRegistration = DEREGISTERED; 1671 } 1672 } 1673 1674 bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned) { 1675 LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this)); 1676 1677 mozilla::MutexAutoLock lock(mLock); 1678 if (mPinningKnown) { 1679 LOG((" pinned=%d, caller=%d", (bool)mPinned, aPinned)); 1680 // Bypass when the pin status of this entry doesn't match the pin status 1681 // caller wants to remove 1682 return mPinned != aPinned; 1683 } 1684 1685 LOG((" pinning unknown, caller=%d", aPinned)); 1686 // Oterwise, remember to doom after the status is determined for any 1687 // callback opening the entry after this point... 1688 Callback c(this, aPinned); 1689 RememberCallback(c); 1690 // ...and always bypass 1691 return true; 1692 } 1693 1694 bool CacheEntry::Purge(uint32_t aWhat) { 1695 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat)); 1696 1697 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1698 1699 switch (aWhat) { 1700 case PURGE_DATA_ONLY_DISK_BACKED: 1701 case PURGE_WHOLE_ONLY_DISK_BACKED: 1702 // This is an in-memory only entry, don't purge it 1703 if (!mUseDisk) { 1704 LOG((" not using disk")); 1705 return false; 1706 } 1707 } 1708 1709 { 1710 mozilla::MutexAutoLock lock(mLock); 1711 1712 if (mState == WRITING || mState == LOADING || mFrecency == 0) { 1713 // In-progress (write or load) entries should (at least for consistency 1714 // and from the logical point of view) stay in memory. Zero-frecency 1715 // entries are those which have never been given to any consumer, those 1716 // are actually very fresh and should not go just because frecency had not 1717 // been set so far. 1718 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency)); 1719 return false; 1720 } 1721 } 1722 1723 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) { 1724 // The file is used when there are open streams or chunks/metadata still 1725 // waiting for write. In this case, this entry cannot be purged, 1726 // otherwise reopenned entry would may not even find the data on disk - 1727 // CacheFile is not shared and cannot be left orphan when its job is not 1728 // done, hence keep the whole entry. 1729 LOG((" file still under use")); 1730 return false; 1731 } 1732 1733 switch (aWhat) { 1734 case PURGE_WHOLE_ONLY_DISK_BACKED: 1735 case PURGE_WHOLE: { 1736 if (!CacheStorageService::Self()->RemoveEntry(this, true)) { 1737 LOG((" not purging, still referenced")); 1738 return false; 1739 } 1740 1741 CacheStorageService::Self()->UnregisterEntry(this); 1742 1743 // Entry removed it self from control arrays, return true 1744 return true; 1745 } 1746 1747 case PURGE_DATA_ONLY_DISK_BACKED: { 1748 NS_ENSURE_SUCCESS(mFileStatus, false); 1749 1750 mFile->ThrowMemoryCachedData(); 1751 1752 // Entry has been left in control arrays, return false (not purged) 1753 return false; 1754 } 1755 } 1756 1757 LOG((" ?")); 1758 return false; 1759 } 1760 1761 void CacheEntry::PurgeAndDoom() { 1762 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this)); 1763 1764 CacheStorageService::Self()->RemoveEntry(this); 1765 DoomAlreadyRemoved(); 1766 } 1767 1768 void CacheEntry::DoomAlreadyRemoved() { 1769 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this)); 1770 1771 mozilla::MutexAutoLock lock(mLock); 1772 1773 RemoveForcedValidity(); 1774 1775 mIsDoomed = true; 1776 1777 // Remove from DictionaryCache immediately, to ensure the removal is 1778 // synchronous 1779 LOG(("DoomAlreadyRemoved [entry=%p removed]", this)); 1780 if (mEnhanceID.EqualsLiteral("dict:")) { 1781 DictionaryCache::RemoveOriginFor(mURI); 1782 } else { 1783 DictionaryCache::RemoveDictionaryFor(mURI); 1784 } 1785 1786 // Pretend pinning is know. This entry is now doomed for good, so don't 1787 // bother with defering doom because of unknown pinning state any more. 1788 mPinningKnown = true; 1789 1790 // This schedules dooming of the file, dooming is ensured to happen 1791 // sooner than demand to open the same file made after this point 1792 // so that we don't get this file for any newer opened entry(s). 1793 DoomFile(); 1794 1795 // Must force post here since may be indirectly called from 1796 // InvokeCallbacks of this entry and we don't want reentrancy here. 1797 BackgroundOp(Ops::CALLBACKS, true); 1798 // Process immediately when on the management thread. 1799 BackgroundOp(Ops::UNREGISTER); 1800 } 1801 1802 void CacheEntry::DoomFile() { 1803 nsresult rv = NS_ERROR_NOT_AVAILABLE; 1804 1805 if (NS_SUCCEEDED(mFileStatus)) { 1806 if (mHandlesCount == 0 || (mHandlesCount == 1 && mWriter)) { 1807 // We kill the file also when there is just reference from the writer, 1808 // no one else could ever reach the written data. Obvisouly also 1809 // when there is no reference at all (should we ever end up here 1810 // in that case.) 1811 // Tell the file to kill the handle, i.e. bypass any I/O operations 1812 // on it except removing the file. 1813 mFile->Kill(); 1814 } 1815 1816 // Always calls the callback asynchronously. 1817 rv = mFile->Doom(mDoomCallback ? this : nullptr); 1818 if (NS_SUCCEEDED(rv)) { 1819 LOG((" file doomed")); 1820 return; 1821 } 1822 1823 if (NS_ERROR_FILE_NOT_FOUND == rv) { 1824 // File is set to be just memory-only, notify the callbacks 1825 // and pretend dooming has succeeded. From point of view of 1826 // the entry it actually did - the data is gone and cannot be 1827 // reused. 1828 rv = NS_OK; 1829 } 1830 } 1831 1832 // Always posts to the main thread. 1833 OnFileDoomed(rv); 1834 } 1835 1836 void CacheEntry::RemoveForcedValidity() { 1837 mLock.AssertCurrentThreadOwns(); 1838 1839 nsresult rv; 1840 1841 if (mIsDoomed) { 1842 return; 1843 } 1844 1845 nsAutoCString entryKey; 1846 rv = HashingKey(entryKey); 1847 if (NS_WARN_IF(NS_FAILED(rv))) { 1848 return; 1849 } 1850 1851 CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey); 1852 } 1853 1854 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) 1855 MOZ_REQUIRES(mLock) { 1856 mLock.AssertCurrentThreadOwns(); 1857 1858 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) { 1859 if (mBackgroundOperations.Set(aOperations)) { 1860 CacheStorageService::Self()->Dispatch(this); 1861 } 1862 1863 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations)); 1864 return; 1865 } 1866 1867 { 1868 mozilla::MutexAutoUnlock unlock(mLock); 1869 1870 MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); 1871 1872 if (aOperations & Ops::FRECENCYUPDATE) { 1873 ++mUseCount; 1874 1875 #ifndef M_LN2 1876 # define M_LN2 0.69314718055994530942 1877 #endif 1878 1879 // Half-life is dynamic, in seconds. 1880 static double half_life = CacheObserver::HalfLifeSeconds(); 1881 // Must convert from seconds to milliseconds since PR_Now() gives usecs. 1882 static double const decay = 1883 (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC); 1884 1885 double now_decay = static_cast<double>(PR_Now()) * decay; 1886 1887 if (mFrecency == 0) { 1888 mFrecency = now_decay; 1889 } else { 1890 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1891 // 1) but more precise. 1892 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay; 1893 } 1894 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, 1895 mFrecency)); 1896 1897 // Because CacheFile::Set*() are not thread-safe to use (uses 1898 // WeakReference that is not thread-safe) we must post to the main 1899 // thread... 1900 NS_DispatchToMainThread( 1901 NewRunnableMethod<double>("net::CacheEntry::StoreFrecency", this, 1902 &CacheEntry::StoreFrecency, mFrecency)); 1903 } 1904 1905 if (aOperations & Ops::REGISTER) { 1906 LOG(("CacheEntry REGISTER [this=%p]", this)); 1907 1908 CacheStorageService::Self()->RegisterEntry(this); 1909 } 1910 1911 if (aOperations & Ops::UNREGISTER) { 1912 LOG(("CacheEntry UNREGISTER [this=%p]", this)); 1913 1914 CacheStorageService::Self()->UnregisterEntry(this); 1915 } 1916 } // unlock 1917 1918 if (aOperations & Ops::CALLBACKS) { 1919 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this)); 1920 1921 InvokeCallbacks(); 1922 } 1923 } 1924 1925 void CacheEntry::StoreFrecency(double aFrecency) { 1926 MOZ_ASSERT(NS_IsMainThread()); 1927 1928 if (NS_SUCCEEDED(mFileStatus)) { 1929 mFile->SetFrecency(FRECENCY2INT(aFrecency)); 1930 } 1931 } 1932 1933 // CacheOutputCloseListener 1934 1935 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry) 1936 : Runnable("net::CacheOutputCloseListener"), mEntry(aEntry) {} 1937 1938 void CacheOutputCloseListener::OnOutputClosed() { 1939 // We need this class and to redispatch since this callback is invoked 1940 // under the file's lock and to do the job we need to enter the entry's 1941 // lock too. That would lead to potential deadlocks. 1942 // This function may be reached while XPCOM is already shutting down, 1943 // and we might be unable to obtain the main thread or the sts. #1826661 1944 1945 if (NS_IsMainThread()) { 1946 // If we're already on the main thread, dispatch to the main thread instead 1947 // of the sts. Always dispatching to the sts can cause problems late in 1948 // shutdown, when threadpools may no longer be available (bug 1806332). 1949 // 1950 // This may also avoid some unnecessary thread-hops when invoking callbacks, 1951 // which can require that they be called on the main thread. 1952 1953 nsCOMPtr<nsIThread> thread; 1954 nsresult rv = NS_GetMainThread(getter_AddRefs(thread)); 1955 if (NS_SUCCEEDED(rv)) { 1956 MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(do_AddRef(this))); 1957 } 1958 return; 1959 } 1960 1961 nsCOMPtr<nsIEventTarget> sts = 1962 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 1963 MOZ_DIAGNOSTIC_ASSERT(sts); 1964 if (sts) { 1965 MOZ_ALWAYS_SUCCEEDS(sts->Dispatch(do_AddRef(this))); 1966 } 1967 } 1968 1969 NS_IMETHODIMP CacheOutputCloseListener::Run() { 1970 mEntry->OnOutputClosed(); 1971 return NS_OK; 1972 } 1973 1974 // Memory reporting 1975 1976 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 1977 size_t n = 0; 1978 1979 MutexAutoLock lock(mLock); 1980 n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf); 1981 if (mFile) { 1982 n += mFile->SizeOfIncludingThis(mallocSizeOf); 1983 } 1984 1985 n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf); 1986 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf); 1987 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf); 1988 1989 // mDoomCallback is an arbitrary class that is probably reported elsewhere. 1990 // mOutputStream is reported in mFile. 1991 // mWriter is one of many handles we create, but (intentionally) not keep 1992 // any reference to, so those unfortunately cannot be reported. Handles are 1993 // small, though. 1994 // mSecurityInfo doesn't implement memory reporting. 1995 1996 return n; 1997 } 1998 1999 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { 2000 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); 2001 } 2002 2003 } // namespace mozilla::net