netwerk/cache2/CacheEntry.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/. */
     5 #include "CacheLog.h"
     6 #include "CacheEntry.h"
     7 #include "CacheStorageService.h"
     8 #include "CacheObserver.h"
     9 #include "CacheFileUtils.h"
    11 #include "nsIInputStream.h"
    12 #include "nsIOutputStream.h"
    13 #include "nsISeekableStream.h"
    14 #include "nsIURI.h"
    15 #include "nsICacheEntryOpenCallback.h"
    16 #include "nsICacheStorage.h"
    17 #include "nsISerializable.h"
    18 #include "nsIStreamTransportService.h"
    19 #include "nsISizeOf.h"
    21 #include "nsComponentManagerUtils.h"
    22 #include "nsServiceManagerUtils.h"
    23 #include "nsString.h"
    24 #include "nsProxyRelease.h"
    25 #include "nsSerializationHelper.h"
    26 #include "nsThreadUtils.h"
    27 #include "mozilla/Telemetry.h"
    28 #include <math.h>
    29 #include <algorithm>
    31 namespace mozilla {
    32 namespace net {
    34 static uint32_t const ENTRY_WANTED =
    35   nsICacheEntryOpenCallback::ENTRY_WANTED;
    36 static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
    37   nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
    38 static uint32_t const ENTRY_NEEDS_REVALIDATION =
    39   nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
    40 static uint32_t const ENTRY_NOT_WANTED =
    41   nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
    43 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
    45 // CacheEntryHandle
    47 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry)
    48 : mEntry(aEntry)
    49 {
    50   MOZ_COUNT_CTOR(CacheEntryHandle);
    52 #ifdef DEBUG
    53   if (!mEntry->HandlesCount()) {
    54     // CacheEntry.mHandlesCount must go from zero to one only under
    55     // the service lock. Can access CacheStorageService::Self() w/o a check
    56     // since CacheEntry hrefs it.
    57     CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
    58   }
    59 #endif
    61   mEntry->AddHandleRef();
    63   LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
    64 }
    66 CacheEntryHandle::~CacheEntryHandle()
    67 {
    68   mEntry->ReleaseHandleRef();
    69   mEntry->OnHandleClosed(this);
    71   MOZ_COUNT_DTOR(CacheEntryHandle);
    72 }
    74 // CacheEntry::Callback
    76 CacheEntry::Callback::Callback(CacheEntry* aEntry,
    77                                nsICacheEntryOpenCallback *aCallback,
    78                                bool aReadOnly, bool aCheckOnAnyThread)
    79 : mEntry(aEntry)
    80 , mCallback(aCallback)
    81 , mTargetThread(do_GetCurrentThread())
    82 , mReadOnly(aReadOnly)
    83 , mCheckOnAnyThread(aCheckOnAnyThread)
    84 , mRecheckAfterWrite(false)
    85 , mNotWanted(false)
    86 {
    87   MOZ_COUNT_CTOR(CacheEntry::Callback);
    89   // The counter may go from zero to non-null only under the service lock
    90   // but here we expect it to be already positive.
    91   MOZ_ASSERT(mEntry->HandlesCount());
    92   mEntry->AddHandleRef();
    93 }
    95 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
    96 : mEntry(aThat.mEntry)
    97 , mCallback(aThat.mCallback)
    98 , mTargetThread(aThat.mTargetThread)
    99 , mReadOnly(aThat.mReadOnly)
   100 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
   101 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
   102 , mNotWanted(aThat.mNotWanted)
   103 {
   104   MOZ_COUNT_CTOR(CacheEntry::Callback);
   106   // The counter may go from zero to non-null only under the service lock
   107   // but here we expect it to be already positive.
   108   MOZ_ASSERT(mEntry->HandlesCount());
   109   mEntry->AddHandleRef();
   110 }
   112 CacheEntry::Callback::~Callback()
   113 {
   114   ProxyRelease(mCallback, mTargetThread);
   116   mEntry->ReleaseHandleRef();
   117   MOZ_COUNT_DTOR(CacheEntry::Callback);
   118 }
   120 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
   121 {
   122   if (mEntry == aEntry)
   123     return;
   125   // The counter may go from zero to non-null only under the service lock
   126   // but here we expect it to be already positive.
   127   MOZ_ASSERT(aEntry->HandlesCount());
   128   aEntry->AddHandleRef();
   129   mEntry->ReleaseHandleRef();
   130   mEntry = aEntry;
   131 }
   133 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
   134 {
   135   if (!mCheckOnAnyThread) {
   136     // Check we are on the target
   137     return mTargetThread->IsOnCurrentThread(aOnCheckThread);
   138   }
   140   // We can invoke check anywhere
   141   *aOnCheckThread = true;
   142   return NS_OK;
   143 }
   145 nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
   146 {
   147   return mTargetThread->IsOnCurrentThread(aOnAvailThread);
   148 }
   150 // CacheEntry
   152 NS_IMPL_ISUPPORTS(CacheEntry,
   153                   nsICacheEntry,
   154                   nsIRunnable,
   155                   CacheFileListener)
   157 CacheEntry::CacheEntry(const nsACString& aStorageID,
   158                        nsIURI* aURI,
   159                        const nsACString& aEnhanceID,
   160                        bool aUseDisk)
   161 : mFrecency(0)
   162 , mSortingExpirationTime(uint32_t(-1))
   163 , mLock("CacheEntry")
   164 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
   165 , mURI(aURI)
   166 , mEnhanceID(aEnhanceID)
   167 , mStorageID(aStorageID)
   168 , mUseDisk(aUseDisk)
   169 , mIsDoomed(false)
   170 , mSecurityInfoLoaded(false)
   171 , mPreventCallbacks(false)
   172 , mHasData(false)
   173 , mState(NOTLOADED)
   174 , mRegistration(NEVERREGISTERED)
   175 , mWriter(nullptr)
   176 , mPredictedDataSize(0)
   177 , mReleaseThread(NS_GetCurrentThread())
   178 {
   179   MOZ_COUNT_CTOR(CacheEntry);
   181   mService = CacheStorageService::Self();
   183   CacheStorageService::Self()->RecordMemoryOnlyEntry(
   184     this, !aUseDisk, true /* overwrite */);
   185 }
   187 CacheEntry::~CacheEntry()
   188 {
   189   ProxyRelease(mURI, mReleaseThread);
   191   LOG(("CacheEntry::~CacheEntry [this=%p]", this));
   192   MOZ_COUNT_DTOR(CacheEntry);
   193 }
   195 #ifdef PR_LOG
   197 char const * CacheEntry::StateString(uint32_t aState)
   198 {
   199   switch (aState) {
   200   case NOTLOADED:     return "NOTLOADED";
   201   case LOADING:       return "LOADING";
   202   case EMPTY:         return "EMPTY";
   203   case WRITING:       return "WRITING";
   204   case READY:         return "READY";
   205   case REVALIDATING:  return "REVALIDATING";
   206   }
   208   return "?";
   209 }
   211 #endif
   213 nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult)
   214 {
   215   return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
   216 }
   218 nsresult CacheEntry::HashingKey(nsACString &aResult)
   219 {
   220   return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
   221 }
   223 // static
   224 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
   225                                 nsCSubstring const& aEnhanceID,
   226                                 nsIURI* aURI,
   227                                 nsACString &aResult)
   228 {
   229   nsAutoCString spec;
   230   nsresult rv = aURI->GetAsciiSpec(spec);
   231   NS_ENSURE_SUCCESS(rv, rv);
   233   return HashingKey(aStorageID, aEnhanceID, spec, aResult);
   234 }
   236 // static
   237 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
   238                                 nsCSubstring const& aEnhanceID,
   239                                 nsCSubstring const& aURISpec,
   240                                 nsACString &aResult)
   241 {
   242   /**
   243    * This key is used to salt hash that is a base for disk file name.
   244    * Changing it will cause we will not be able to find files on disk.
   245    */
   247   aResult.Append(aStorageID);
   249   if (!aEnhanceID.IsEmpty()) {
   250     CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
   251   }
   253   // Appending directly
   254   aResult.Append(':');
   255   aResult.Append(aURISpec);
   257   return NS_OK;
   258 }
   260 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
   261 {
   262   LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
   263     this, StateString(mState), aFlags, aCallback));
   265   bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
   266   bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
   267   bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
   268   bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
   270   MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
   271   MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
   273   Callback callback(this, aCallback, readonly, multithread);
   275   mozilla::MutexAutoLock lock(mLock);
   277   RememberCallback(callback);
   279   // Load() opens the lock
   280   if (Load(truncate, priority)) {
   281     // Loading is in progress...
   282     return;
   283   }
   285   InvokeCallbacks();
   286 }
   288 bool CacheEntry::Load(bool aTruncate, bool aPriority)
   289 {
   290   LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
   292   mLock.AssertCurrentThreadOwns();
   294   if (mState > LOADING) {
   295     LOG(("  already loaded"));
   296     return false;
   297   }
   299   if (mState == LOADING) {
   300     LOG(("  already loading"));
   301     return true;
   302   }
   304   mState = LOADING;
   306   MOZ_ASSERT(!mFile);
   308   bool directLoad = aTruncate || !mUseDisk;
   309   if (directLoad)
   310     mFileStatus = NS_OK;
   311   else
   312     mLoadStart = TimeStamp::Now();
   314   mFile = new CacheFile();
   316   BackgroundOp(Ops::REGISTER);
   318   {
   319     mozilla::MutexAutoUnlock unlock(mLock);
   321     nsresult rv;
   323     nsAutoCString fileKey;
   324     rv = HashingKeyWithStorage(fileKey);
   326     LOG(("  performing load, file=%p", mFile.get()));
   327     if (NS_SUCCEEDED(rv)) {
   328       rv = mFile->Init(fileKey,
   329                        aTruncate,
   330                        !mUseDisk,
   331                        aPriority,
   332                        directLoad ? nullptr : this);
   333     }
   335     if (NS_FAILED(rv)) {
   336       mFileStatus = rv;
   337       AsyncDoom(nullptr);
   338       return false;
   339     }
   340   }
   342   if (directLoad) {
   343     // Just fake the load has already been done as "new".
   344     mState = EMPTY;
   345   }
   347   return mState == LOADING;
   348 }
   350 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
   351 {
   352   LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]",
   353       this, aResult, aIsNew));
   355   MOZ_ASSERT(!mLoadStart.IsNull());
   357   if (NS_SUCCEEDED(aResult)) {
   358     if (aIsNew) {
   359       mozilla::Telemetry::AccumulateTimeDelta(
   360         mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
   361         mLoadStart);
   362     }
   363     else {
   364       mozilla::Telemetry::AccumulateTimeDelta(
   365         mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
   366         mLoadStart);
   367     }
   368   }
   370   // OnFileReady, that is the only code that can transit from LOADING
   371   // to any follow-on state, can only be invoked ones on an entry,
   372   // thus no need to lock.  Until this moment there is no consumer that
   373   // could manipulate the entry state.
   374   mozilla::MutexAutoLock lock(mLock);
   376   MOZ_ASSERT(mState == LOADING);
   378   mState = (aIsNew || NS_FAILED(aResult))
   379     ? EMPTY
   380     : READY;
   382   mFileStatus = aResult;
   384   if (mState == READY) {
   385     mHasData = true;
   387     uint32_t frecency;
   388     mFile->GetFrecency(&frecency);
   389     // mFrecency is held in a double to increase computance precision.
   390     // It is ok to persist frecency only as a uint32 with some math involved.
   391     mFrecency = INT2FRECENCY(frecency);
   392   }
   394   InvokeCallbacks();
   395   return NS_OK;
   396 }
   398 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
   399 {
   400   if (mDoomCallback) {
   401     nsRefPtr<DoomCallbackRunnable> event =
   402       new DoomCallbackRunnable(this, aResult);
   403     NS_DispatchToMainThread(event);
   404   }
   406   return NS_OK;
   407 }
   409 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
   410                                                                nsICacheEntryOpenCallback* aCallback)
   411 {
   412   LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
   414   mLock.AssertCurrentThreadOwns();
   416   // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
   417   mPreventCallbacks = true;
   419   nsRefPtr<CacheEntryHandle> handle;
   420   nsRefPtr<CacheEntry> newEntry;
   421   {
   422     mozilla::MutexAutoUnlock unlock(mLock);
   424     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
   425     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
   426       GetStorageID(), GetURI(), GetEnhanceID(),
   427       mUseDisk && !aMemoryOnly,
   428       true, // always create
   429       true, // truncate existing (this one)
   430       getter_AddRefs(handle));
   432     if (NS_SUCCEEDED(rv)) {
   433       newEntry = handle->Entry();
   434       LOG(("  exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
   435       newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
   436     } else {
   437       LOG(("  exchanged of entry %p failed, rv=0x%08x", this, rv));
   438       AsyncDoom(nullptr);
   439     }
   440   }
   442   mPreventCallbacks = false;
   444   if (!newEntry)
   445     return nullptr;
   447   newEntry->TransferCallbacks(*this);
   448   mCallbacks.Clear();
   450   return handle.forget();
   451 }
   453 void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
   454 {
   455   mozilla::MutexAutoLock lock(mLock);
   457   LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
   458     this, &aFromEntry));
   460   if (!mCallbacks.Length())
   461     mCallbacks.SwapElements(aFromEntry.mCallbacks);
   462   else
   463     mCallbacks.AppendElements(aFromEntry.mCallbacks);
   465   uint32_t callbacksLength = mCallbacks.Length();
   466   if (callbacksLength) {
   467     // Carry the entry reference (unfortunatelly, needs to be done manually...)
   468     for (uint32_t i = 0; i < callbacksLength; ++i)
   469       mCallbacks[i].ExchangeEntry(this);
   471     BackgroundOp(Ops::CALLBACKS, true);
   472   }
   473 }
   475 void CacheEntry::RememberCallback(Callback const& aCallback)
   476 {
   477   LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback.mCallback.get()));
   479   mLock.AssertCurrentThreadOwns();
   481   mCallbacks.AppendElement(aCallback);
   482 }
   484 void CacheEntry::InvokeCallbacksLock()
   485 {
   486   mozilla::MutexAutoLock lock(mLock);
   487   InvokeCallbacks();
   488 }
   490 void CacheEntry::InvokeCallbacks()
   491 {
   492   mLock.AssertCurrentThreadOwns();
   494   LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
   496   // Invoke first all r/w callbacks, then all r/o callbacks.
   497   if (InvokeCallbacks(false))
   498     InvokeCallbacks(true);
   500   LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
   501 }
   503 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
   504 {
   505   mLock.AssertCurrentThreadOwns();
   507   uint32_t i = 0;
   508   while (i < mCallbacks.Length()) {
   509     if (mPreventCallbacks) {
   510       LOG(("  callbacks prevented!"));
   511       return false;
   512     }
   514     if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
   515       LOG(("  entry is being written/revalidated"));
   516       return false;
   517     }
   519     if (mCallbacks[i].mReadOnly != aReadOnly) {
   520       // Callback is not r/w or r/o, go to another one in line
   521       ++i;
   522       continue;
   523     }
   525     bool onCheckThread;
   526     nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
   528     if (NS_SUCCEEDED(rv) && !onCheckThread) {
   529       // Redispatch to the target thread
   530       nsRefPtr<nsRunnableMethod<CacheEntry> > event =
   531         NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock);
   533       rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   534       if (NS_SUCCEEDED(rv)) {
   535         LOG(("  re-dispatching to target thread"));
   536         return false;
   537       }
   538     }
   540     Callback callback = mCallbacks[i];
   541     mCallbacks.RemoveElementAt(i);
   543     if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
   544       // Callback didn't fire, put it back and go to another one in line.
   545       // Only reason InvokeCallback returns false is that onCacheEntryCheck
   546       // returns RECHECK_AFTER_WRITE_FINISHED.  If we would stop the loop, other
   547       // readers or potential writers would be unnecessarily kept from being
   548       // invoked.
   549       mCallbacks.InsertElementAt(i, callback);
   550       ++i;
   551     }
   552   }
   554   return true;
   555 }
   557 bool CacheEntry::InvokeCallback(Callback & aCallback)
   558 {
   559   LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
   560     this, StateString(mState), aCallback.mCallback.get()));
   562   mLock.AssertCurrentThreadOwns();
   564   // When this entry is doomed we want to notify the callback any time
   565   if (!mIsDoomed) {
   566     // When we are here, the entry must be loaded from disk
   567     MOZ_ASSERT(mState > LOADING);
   569     if (mState == WRITING || mState == REVALIDATING) {
   570       // Prevent invoking other callbacks since one of them is now writing
   571       // or revalidating this entry.  No consumers should get this entry
   572       // until metadata are filled with values downloaded from the server
   573       // or the entry revalidated and output stream has been opened.
   574       LOG(("  entry is being written/revalidated, callback bypassed"));
   575       return false;
   576     }
   578     // mRecheckAfterWrite flag already set means the callback has already passed
   579     // the onCacheEntryCheck call. Until the current write is not finished this
   580     // callback will be bypassed.
   581     if (!aCallback.mRecheckAfterWrite) {
   583       if (!aCallback.mReadOnly) {
   584         if (mState == EMPTY) {
   585           // Advance to writing state, we expect to invoke the callback and let
   586           // it fill content of this entry.  Must set and check the state here
   587           // to prevent more then one
   588           mState = WRITING;
   589           LOG(("  advancing to WRITING state"));
   590         }
   592         if (!aCallback.mCallback) {
   593           // We can be given no callback only in case of recreate, it is ok
   594           // to advance to WRITING state since the caller of recreate is expected
   595           // to write this entry now.
   596           return true;
   597         }
   598       }
   600       if (mState == READY) {
   601         // Metadata present, validate the entry
   602         uint32_t checkResult;
   603         {
   604           // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
   605           mozilla::MutexAutoUnlock unlock(mLock);
   607           nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
   608             this, nullptr, &checkResult);
   609           LOG(("  OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
   611           if (NS_FAILED(rv))
   612             checkResult = ENTRY_NOT_WANTED;
   613         }
   615         switch (checkResult) {
   616         case ENTRY_WANTED:
   617           // Nothing more to do here, the consumer is responsible to handle
   618           // the result of OnCacheEntryCheck it self.
   619           // Proceed to callback...
   620           break;
   622         case RECHECK_AFTER_WRITE_FINISHED:
   623           LOG(("  consumer will check on the entry again after write is done"));
   624           // The consumer wants the entry to complete first.
   625           aCallback.mRecheckAfterWrite = true;
   626           break;
   628         case ENTRY_NEEDS_REVALIDATION:
   629           LOG(("  will be holding callbacks until entry is revalidated"));
   630           // State is READY now and from that state entry cannot transit to any other
   631           // state then REVALIDATING for which cocurrency is not an issue.  Potentially
   632           // no need to lock here.
   633           mState = REVALIDATING;
   634           break;
   636         case ENTRY_NOT_WANTED:
   637           LOG(("  consumer not interested in the entry"));
   638           // Do not give this entry to the consumer, it is not interested in us.
   639           aCallback.mNotWanted = true;
   640           break;
   641         }
   642       }
   643     }
   644   }
   646   if (aCallback.mCallback) {
   647     if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
   648       // If we don't have data and the callback wants a complete entry,
   649       // don't invoke now.
   650       bool bypass = !mHasData;
   651       if (!bypass) {
   652         int64_t _unused;
   653         bypass = !mFile->DataSize(&_unused);
   654       }
   656       if (bypass) {
   657         LOG(("  bypassing, entry data still being written"));
   658         return false;
   659       }
   661       // Entry is complete now, do the check+avail call again
   662       aCallback.mRecheckAfterWrite = false;
   663       return InvokeCallback(aCallback);
   664     }
   666     mozilla::MutexAutoUnlock unlock(mLock);
   667     InvokeAvailableCallback(aCallback);
   668   }
   670   return true;
   671 }
   673 void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
   674 {
   675   LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
   676     this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
   678   nsresult rv;
   680   uint32_t const state = mState;
   682   // When we are here, the entry must be loaded from disk
   683   MOZ_ASSERT(state > LOADING || mIsDoomed);
   685   bool onAvailThread;
   686   rv = aCallback.OnAvailThread(&onAvailThread);
   687   if (NS_FAILED(rv)) {
   688     LOG(("  target thread dead?"));
   689     return;
   690   }
   692   if (!onAvailThread) {
   693     // Dispatch to the right thread
   694     nsRefPtr<AvailableCallbackRunnable> event =
   695       new AvailableCallbackRunnable(this, aCallback);
   697     rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   698     LOG(("  redispatched, (rv = 0x%08x)", rv));
   699     return;
   700   }
   702   if (mIsDoomed || aCallback.mNotWanted) {
   703     LOG(("  doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
   704     aCallback.mCallback->OnCacheEntryAvailable(
   705       nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
   706     return;
   707   }
   709   if (state == READY) {
   710     LOG(("  ready/has-meta, notifying OCEA with entry and NS_OK"));
   711     {
   712       mozilla::MutexAutoLock lock(mLock);
   713       BackgroundOp(Ops::FRECENCYUPDATE);
   714     }
   716     nsRefPtr<CacheEntryHandle> handle = NewHandle();
   717     aCallback.mCallback->OnCacheEntryAvailable(
   718       handle, false, nullptr, NS_OK);
   719     return;
   720   }
   722   if (aCallback.mReadOnly) {
   723     LOG(("  r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
   724     aCallback.mCallback->OnCacheEntryAvailable(
   725       nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
   726     return;
   727   }
   729   // This is a new or potentially non-valid entry and needs to be fetched first.
   730   // The CacheEntryHandle blocks other consumers until the channel
   731   // either releases the entry or marks metadata as filled or whole entry valid,
   732   // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
   734   // Consumer will be responsible to fill or validate the entry metadata and data.
   736   nsRefPtr<CacheEntryHandle> handle = NewWriteHandle();
   737   rv = aCallback.mCallback->OnCacheEntryAvailable(
   738     handle, state == WRITING, nullptr, NS_OK);
   740   if (NS_FAILED(rv)) {
   741     LOG(("  writing/revalidating failed (0x%08x)", rv));
   743     // Consumer given a new entry failed to take care of the entry.
   744     OnHandleClosed(handle);
   745     return;
   746   }
   748   LOG(("  writing/revalidating"));
   749 }
   751 CacheEntryHandle* CacheEntry::NewHandle()
   752 {
   753   return new CacheEntryHandle(this);
   754 }
   756 CacheEntryHandle* CacheEntry::NewWriteHandle()
   757 {
   758   mozilla::MutexAutoLock lock(mLock);
   760   BackgroundOp(Ops::FRECENCYUPDATE);
   761   return (mWriter = new CacheEntryHandle(this));
   762 }
   764 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
   765 {
   766   LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
   768   nsCOMPtr<nsIOutputStream> outputStream;
   770   {
   771     mozilla::MutexAutoLock lock(mLock);
   773     if (mWriter != aHandle) {
   774       LOG(("  not the writer"));
   775       return;
   776     }
   778     if (mOutputStream) {
   779       // No one took our internal output stream, so there are no data
   780       // and output stream has to be open symultaneously with input stream
   781       // on this entry again.
   782       mHasData = false;
   783     }
   785     outputStream.swap(mOutputStream);
   786     mWriter = nullptr;
   788     if (mState == WRITING) {
   789       LOG(("  reverting to state EMPTY - write failed"));
   790       mState = EMPTY;
   791     }
   792     else if (mState == REVALIDATING) {
   793       LOG(("  reverting to state READY - reval failed"));
   794       mState = READY;
   795     }
   797     InvokeCallbacks();
   798   }
   800   if (outputStream) {
   801     LOG(("  abandoning phantom output stream"));
   802     outputStream->Close();
   803   }
   804 }
   806 void CacheEntry::OnOutputClosed()
   807 {
   808   // Called when the file's output stream is closed.  Invoke any callbacks
   809   // waiting for complete entry.
   811   mozilla::MutexAutoLock lock(mLock);
   812   InvokeCallbacks();
   813 }
   815 bool CacheEntry::IsUsingDiskLocked() const
   816 {
   817   CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
   819   return IsUsingDisk();
   820 }
   822 bool CacheEntry::SetUsingDisk(bool aUsingDisk)
   823 {
   824   // Called by the service when this entry is reopen to reflect
   825   // demanded storage target.
   827   if (mState >= READY) {
   828     // Don't modify after this entry has been filled.
   829     return false;
   830   }
   832   CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
   834   bool changed = mUseDisk != aUsingDisk;
   835   mUseDisk = aUsingDisk;
   836   return changed;
   837 }
   839 bool CacheEntry::IsReferenced() const
   840 {
   841   CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
   843   // Increasing this counter from 0 to non-null and this check both happen only
   844   // under the service lock.
   845   return mHandlesCount > 0;
   846 }
   848 bool CacheEntry::IsFileDoomed()
   849 {
   850   mozilla::MutexAutoLock lock(mLock);
   852   if (NS_SUCCEEDED(mFileStatus)) {
   853     return mFile->IsDoomed();
   854   }
   856   return false;
   857 }
   859 uint32_t CacheEntry::GetMetadataMemoryConsumption()
   860 {
   861   NS_ENSURE_SUCCESS(mFileStatus, 0);
   863   uint32_t size;
   864   if (NS_FAILED(mFile->ElementsSize(&size)))
   865     return 0;
   867   return size;
   868 }
   870 // nsICacheEntry
   872 NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk)
   873 {
   874   // No need to sync when only reading.
   875   // When consumer needs to be consistent with state of the memory storage entries
   876   // table, then let it use GetUseDisk getter that must be called under the service lock.
   877   *aPersistToDisk = mUseDisk;
   878   return NS_OK;
   879 }
   881 NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
   882 {
   883   return mURI->GetAsciiSpec(aKey);
   884 }
   886 NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
   887 {
   888   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   890   return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
   891 }
   893 NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
   894 {
   895   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   897   return mFile->GetLastFetched(aLastFetched);
   898 }
   900 NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
   901 {
   902   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   904   return mFile->GetLastModified(aLastModified);
   905 }
   907 NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
   908 {
   909   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   911   return mFile->GetExpirationTime(aExpirationTime);
   912 }
   914 NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
   915 {
   916   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   918   nsresult rv = mFile->SetExpirationTime(aExpirationTime);
   919   NS_ENSURE_SUCCESS(rv, rv);
   921   // Aligned assignment, thus atomic.
   922   mSortingExpirationTime = aExpirationTime;
   923   return NS_OK;
   924 }
   926 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
   927 {
   928   LOG(("CacheEntry::OpenInputStream [this=%p]", this));
   930   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   932   nsresult rv;
   934   nsCOMPtr<nsIInputStream> stream;
   935   rv = mFile->OpenInputStream(getter_AddRefs(stream));
   936   NS_ENSURE_SUCCESS(rv, rv);
   938   nsCOMPtr<nsISeekableStream> seekable =
   939     do_QueryInterface(stream, &rv);
   940   NS_ENSURE_SUCCESS(rv, rv);
   942   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
   943   NS_ENSURE_SUCCESS(rv, rv);
   945   mozilla::MutexAutoLock lock(mLock);
   947   if (!mHasData) {
   948     // So far output stream on this new entry not opened, do it now.
   949     LOG(("  creating phantom output stream"));
   950     rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
   951     NS_ENSURE_SUCCESS(rv, rv);
   952   }
   954   stream.forget(_retval);
   955   return NS_OK;
   956 }
   958 NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
   959 {
   960   LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
   962   nsresult rv;
   964   mozilla::MutexAutoLock lock(mLock);
   966   MOZ_ASSERT(mState > EMPTY);
   968   if (mOutputStream && !mIsDoomed) {
   969     LOG(("  giving phantom output stream"));
   970     mOutputStream.forget(_retval);
   971   }
   972   else {
   973     rv = OpenOutputStreamInternal(offset, _retval);
   974     if (NS_FAILED(rv)) return rv;
   975   }
   977   // Entry considered ready when writer opens output stream.
   978   if (mState < READY)
   979     mState = READY;
   981   // Invoke any pending readers now.
   982   InvokeCallbacks();
   984   return NS_OK;
   985 }
   987 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
   988 {
   989   LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
   991   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
   993   mLock.AssertCurrentThreadOwns();
   995   if (mIsDoomed) {
   996     LOG(("  doomed..."));
   997     return NS_ERROR_NOT_AVAILABLE;
   998   }
  1000   MOZ_ASSERT(mState > LOADING);
  1002   nsresult rv;
  1004   // No need to sync on mUseDisk here, we don't need to be consistent
  1005   // with content of the memory storage entries hash table.
  1006   if (!mUseDisk) {
  1007     rv = mFile->SetMemoryOnly();
  1008     NS_ENSURE_SUCCESS(rv, rv);
  1011   nsRefPtr<CacheOutputCloseListener> listener =
  1012     new CacheOutputCloseListener(this);
  1014   nsCOMPtr<nsIOutputStream> stream;
  1015   rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
  1016   NS_ENSURE_SUCCESS(rv, rv);
  1018   nsCOMPtr<nsISeekableStream> seekable =
  1019     do_QueryInterface(stream, &rv);
  1020   NS_ENSURE_SUCCESS(rv, rv);
  1022   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
  1023   NS_ENSURE_SUCCESS(rv, rv);
  1025   // Prevent opening output stream again.
  1026   mHasData = true;
  1028   stream.swap(*_retval);
  1029   return NS_OK;
  1032 NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
  1034   *aPredictedDataSize = mPredictedDataSize;
  1035   return NS_OK;
  1037 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
  1039   mPredictedDataSize = aPredictedDataSize;
  1041   if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
  1042     LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
  1043     AsyncDoom(nullptr);
  1045     return NS_ERROR_FILE_TOO_BIG;
  1048   return NS_OK;
  1051 NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
  1054     mozilla::MutexAutoLock lock(mLock);
  1055     if (mSecurityInfoLoaded) {
  1056       NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
  1057       return NS_OK;
  1061   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
  1063   nsXPIDLCString info;
  1064   nsCOMPtr<nsISupports> secInfo;
  1065   nsresult rv;
  1067   rv = mFile->GetElement("security-info", getter_Copies(info));
  1068   NS_ENSURE_SUCCESS(rv, rv);
  1070   if (info) {
  1071     rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
  1072     NS_ENSURE_SUCCESS(rv, rv);
  1076     mozilla::MutexAutoLock lock(mLock);
  1078     mSecurityInfo.swap(secInfo);
  1079     mSecurityInfoLoaded = true;
  1081     NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
  1084   return NS_OK;
  1086 NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
  1088   nsresult rv;
  1090   NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
  1093     mozilla::MutexAutoLock lock(mLock);
  1095     mSecurityInfo = aSecurityInfo;
  1096     mSecurityInfoLoaded = true;
  1099   nsCOMPtr<nsISerializable> serializable =
  1100     do_QueryInterface(aSecurityInfo);
  1101   if (aSecurityInfo && !serializable)
  1102     return NS_ERROR_UNEXPECTED;
  1104   nsCString info;
  1105   if (serializable) {
  1106     rv = NS_SerializeToString(serializable, info);
  1107     NS_ENSURE_SUCCESS(rv, rv);
  1110   rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
  1111   NS_ENSURE_SUCCESS(rv, rv);
  1113   return NS_OK;
  1116 NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
  1118   NS_ENSURE_ARG(aStorageDataSize);
  1120   int64_t dataSize;
  1121   nsresult rv = GetDataSize(&dataSize);
  1122   if (NS_FAILED(rv))
  1123     return rv;
  1125   *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
  1127   return NS_OK;
  1130 NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
  1132   LOG(("CacheEntry::AsyncDoom [this=%p]", this));
  1135     mozilla::MutexAutoLock lock(mLock);
  1137     if (mIsDoomed || mDoomCallback)
  1138       return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
  1140     mIsDoomed = true;
  1141     mDoomCallback = aCallback;
  1144   // This immediately removes the entry from the master hashtable and also
  1145   // immediately dooms the file.  This way we make sure that any consumer
  1146   // after this point asking for the same entry won't get
  1147   //   a) this entry
  1148   //   b) a new entry with the same file
  1149   PurgeAndDoom();
  1151   return NS_OK;
  1154 NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
  1156   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
  1158   return mFile->GetElement(aKey, aRetval);
  1161 NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
  1163   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
  1165   return mFile->SetElement(aKey, aValue);
  1168 NS_IMETHODIMP CacheEntry::MetaDataReady()
  1170   mozilla::MutexAutoLock lock(mLock);
  1172   LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
  1174   MOZ_ASSERT(mState > EMPTY);
  1176   if (mState == WRITING)
  1177     mState = READY;
  1179   InvokeCallbacks();
  1181   return NS_OK;
  1184 NS_IMETHODIMP CacheEntry::SetValid()
  1186   LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
  1188   nsCOMPtr<nsIOutputStream> outputStream;
  1191     mozilla::MutexAutoLock lock(mLock);
  1193     MOZ_ASSERT(mState > EMPTY);
  1195     mState = READY;
  1196     mHasData = true;
  1198     InvokeCallbacks();
  1200     outputStream.swap(mOutputStream);
  1203   if (outputStream) {
  1204     LOG(("  abandoning phantom output stream"));
  1205     outputStream->Close();
  1208   return NS_OK;
  1211 NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly,
  1212                                    nsICacheEntry **_retval)
  1214   LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
  1216   mozilla::MutexAutoLock lock(mLock);
  1218   nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
  1219   if (handle) {
  1220     handle.forget(_retval);
  1221     return NS_OK;
  1224   BackgroundOp(Ops::CALLBACKS, true);
  1225   return NS_OK;
  1228 NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize)
  1230   LOG(("CacheEntry::GetDataSize [this=%p]", this));
  1231   *aDataSize = 0;
  1234     mozilla::MutexAutoLock lock(mLock);
  1236     if (!mHasData) {
  1237       LOG(("  write in progress (no data)"));
  1238       return NS_ERROR_IN_PROGRESS;
  1242   NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
  1244   // mayhemer: TODO Problem with compression?
  1245   if (!mFile->DataSize(aDataSize)) {
  1246     LOG(("  write in progress (stream active)"));
  1247     return NS_ERROR_IN_PROGRESS;
  1250   LOG(("  size=%lld", *aDataSize));
  1251   return NS_OK;
  1254 NS_IMETHODIMP CacheEntry::MarkValid()
  1256   // NOT IMPLEMENTED ACTUALLY
  1257   return NS_OK;
  1260 NS_IMETHODIMP CacheEntry::MaybeMarkValid()
  1262   // NOT IMPLEMENTED ACTUALLY
  1263   return NS_OK;
  1266 NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
  1268   *aWriteAccess = aWriteAllowed;
  1269   return NS_OK;
  1272 NS_IMETHODIMP CacheEntry::Close()
  1274   // NOT IMPLEMENTED ACTUALLY
  1275   return NS_OK;
  1278 // nsIRunnable
  1280 NS_IMETHODIMP CacheEntry::Run()
  1282   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1284   mozilla::MutexAutoLock lock(mLock);
  1286   BackgroundOp(mBackgroundOperations.Grab());
  1287   return NS_OK;
  1290 // Management methods
  1292 double CacheEntry::GetFrecency() const
  1294   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1295   return mFrecency;
  1298 uint32_t CacheEntry::GetExpirationTime() const
  1300   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1301   return mSortingExpirationTime;
  1304 bool CacheEntry::IsRegistered() const
  1306   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1307   return mRegistration == REGISTERED;
  1310 bool CacheEntry::CanRegister() const
  1312   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1313   return mRegistration == NEVERREGISTERED;
  1316 void CacheEntry::SetRegistered(bool aRegistered)
  1318   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1320   if (aRegistered) {
  1321     MOZ_ASSERT(mRegistration == NEVERREGISTERED);
  1322     mRegistration = REGISTERED;
  1324   else {
  1325     MOZ_ASSERT(mRegistration == REGISTERED);
  1326     mRegistration = DEREGISTERED;
  1330 bool CacheEntry::Purge(uint32_t aWhat)
  1332   LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
  1334   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1336   switch (aWhat) {
  1337   case PURGE_DATA_ONLY_DISK_BACKED:
  1338   case PURGE_WHOLE_ONLY_DISK_BACKED:
  1339     // This is an in-memory only entry, don't purge it
  1340     if (!mUseDisk) {
  1341       LOG(("  not using disk"));
  1342       return false;
  1346   if (mState == WRITING || mState == LOADING || mFrecency == 0) {
  1347     // In-progress (write or load) entries should (at least for consistency and from
  1348     // the logical point of view) stay in memory.
  1349     // Zero-frecency entries are those which have never been given to any consumer, those
  1350     // are actually very fresh and should not go just because frecency had not been set
  1351     // so far.
  1352     LOG(("  state=%s, frecency=%1.10f", StateString(mState), mFrecency));
  1353     return false;
  1356   if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
  1357     // The file is used when there are open streams or chunks/metadata still waiting for
  1358     // write.  In this case, this entry cannot be purged, otherwise reopenned entry
  1359     // would may not even find the data on disk - CacheFile is not shared and cannot be
  1360     // left orphan when its job is not done, hence keep the whole entry.
  1361     LOG(("  file still under use"));
  1362     return false;
  1365   switch (aWhat) {
  1366   case PURGE_WHOLE_ONLY_DISK_BACKED:
  1367   case PURGE_WHOLE:
  1369       if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
  1370         LOG(("  not purging, still referenced"));
  1371         return false;
  1374       CacheStorageService::Self()->UnregisterEntry(this);
  1376       // Entry removed it self from control arrays, return true
  1377       return true;
  1380   case PURGE_DATA_ONLY_DISK_BACKED:
  1382       NS_ENSURE_SUCCESS(mFileStatus, false);
  1384       mFile->ThrowMemoryCachedData();
  1386       // Entry has been left in control arrays, return false (not purged)
  1387       return false;
  1391   LOG(("  ?"));
  1392   return false;
  1395 void CacheEntry::PurgeAndDoom()
  1397   LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
  1399   CacheStorageService::Self()->RemoveEntry(this);
  1400   DoomAlreadyRemoved();
  1403 void CacheEntry::DoomAlreadyRemoved()
  1405   LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
  1407   mozilla::MutexAutoLock lock(mLock);
  1409   mIsDoomed = true;
  1411   // This schedules dooming of the file, dooming is ensured to happen
  1412   // sooner than demand to open the same file made after this point
  1413   // so that we don't get this file for any newer opened entry(s).
  1414   DoomFile();
  1416   // Must force post here since may be indirectly called from
  1417   // InvokeCallbacks of this entry and we don't want reentrancy here.
  1418   BackgroundOp(Ops::CALLBACKS, true);
  1419   // Process immediately when on the management thread.
  1420   BackgroundOp(Ops::UNREGISTER);
  1423 void CacheEntry::DoomFile()
  1425   nsresult rv = NS_ERROR_NOT_AVAILABLE;
  1427   if (NS_SUCCEEDED(mFileStatus)) {
  1428     // Always calls the callback asynchronously.
  1429     rv = mFile->Doom(mDoomCallback ? this : nullptr);
  1430     if (NS_SUCCEEDED(rv)) {
  1431       LOG(("  file doomed"));
  1432       return;
  1435     if (NS_ERROR_FILE_NOT_FOUND == rv) {
  1436       // File is set to be just memory-only, notify the callbacks
  1437       // and pretend dooming has succeeded.  From point of view of
  1438       // the entry it actually did - the data is gone and cannot be
  1439       // reused.
  1440       rv = NS_OK;
  1444   // Always posts to the main thread.
  1445   OnFileDoomed(rv);
  1448 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
  1450   mLock.AssertCurrentThreadOwns();
  1452   if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
  1453     if (mBackgroundOperations.Set(aOperations))
  1454       CacheStorageService::Self()->Dispatch(this);
  1456     LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
  1457     return;
  1460   mozilla::MutexAutoUnlock unlock(mLock);
  1462   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
  1464   if (aOperations & Ops::FRECENCYUPDATE) {
  1465     #ifndef M_LN2
  1466     #define M_LN2 0.69314718055994530942
  1467     #endif
  1469     // Half-life is dynamic, in seconds.
  1470      static double half_life = CacheObserver::HalfLifeSeconds();
  1471     // Must convert from seconds to milliseconds since PR_Now() gives usecs.
  1472     static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
  1474     double now_decay = static_cast<double>(PR_Now()) * decay;
  1476     if (mFrecency == 0) {
  1477       mFrecency = now_decay;
  1479     else {
  1480       // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
  1481       // more precise.
  1482       mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
  1484     LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
  1486     // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
  1487     // is not thread-safe) we must post to the main thread...
  1488     nsRefPtr<nsRunnableMethod<CacheEntry> > event =
  1489       NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency);
  1490     NS_DispatchToMainThread(event);
  1493   if (aOperations & Ops::REGISTER) {
  1494     LOG(("CacheEntry REGISTER [this=%p]", this));
  1496     CacheStorageService::Self()->RegisterEntry(this);
  1499   if (aOperations & Ops::UNREGISTER) {
  1500     LOG(("CacheEntry UNREGISTER [this=%p]", this));
  1502     CacheStorageService::Self()->UnregisterEntry(this);
  1505   if (aOperations & Ops::CALLBACKS) {
  1506     LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
  1508     mozilla::MutexAutoLock lock(mLock);
  1509     InvokeCallbacks();
  1513 void CacheEntry::StoreFrecency()
  1515   // No need for thread safety over mFrecency, it will be rewriten
  1516   // correctly on following invocation if broken by concurrency.
  1517   MOZ_ASSERT(NS_IsMainThread());
  1518   mFile->SetFrecency(FRECENCY2INT(mFrecency));
  1521 // CacheOutputCloseListener
  1523 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
  1524 : mEntry(aEntry)
  1526   MOZ_COUNT_CTOR(CacheOutputCloseListener);
  1529 CacheOutputCloseListener::~CacheOutputCloseListener()
  1531   MOZ_COUNT_DTOR(CacheOutputCloseListener);
  1534 void CacheOutputCloseListener::OnOutputClosed()
  1536   // We need this class and to redispatch since this callback is invoked
  1537   // under the file's lock and to do the job we need to enter the entry's
  1538   // lock too.  That would lead to potential deadlocks.
  1539   NS_DispatchToCurrentThread(this);
  1542 NS_IMETHODIMP CacheOutputCloseListener::Run()
  1544   mEntry->OnOutputClosed();
  1545   return NS_OK;
  1548 // Memory reporting
  1550 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
  1552   size_t n = 0;
  1553   nsCOMPtr<nsISizeOf> sizeOf;
  1555   n += mCallbacks.SizeOfExcludingThis(mallocSizeOf);
  1556   if (mFile) {
  1557     n += mFile->SizeOfIncludingThis(mallocSizeOf);
  1560   sizeOf = do_QueryInterface(mURI);
  1561   if (sizeOf) {
  1562     n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
  1565   n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
  1566   n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
  1568   // mDoomCallback is an arbitrary class that is probably reported elsewhere.
  1569   // mOutputStream is reported in mFile.
  1570   // mWriter is one of many handles we create, but (intentionally) not keep
  1571   // any reference to, so those unfortunatelly cannot be reported.  Handles are
  1572   // small, though.
  1573   // mSecurityInfo doesn't impl nsISizeOf.
  1575   return n;
  1578 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
  1580   return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
  1583 } // net
  1584 } // mozilla

mercurial