tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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