Bug 1247644 - Don't do any I/O on doomed and unused HTTP cache entries, r=michal
authorHonza Bambas <honzab.moz@firemni.cz>
Fri, 12 Feb 2016 14:45:00 +0100
changeset 321728 74c396a8252c15c22b71d6e9f12614c8e44f49a3
parent 321727 f72ada9941bbfe936e1d4704e28b950200884352
child 321729 4ca40a403e9d239733d4ac0890bb475760337732
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs1247644
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1247644 - Don't do any I/O on doomed and unused HTTP cache entries, r=michal
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileChunk.cpp
netwerk/cache2/CacheFileChunk.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileInputStream.cpp
netwerk/cache2/CacheFileInputStream.h
netwerk/cache2/CacheFileMetadata.h
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -923,16 +923,23 @@ CacheEntryHandle* CacheEntry::NewWriteHa
 }
 
 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
 {
   LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
 
   mozilla::MutexAutoLock lock(mLock);
 
+  if (IsDoomed() && mHandlesCount == 0 && NS_SUCCEEDED(mFileStatus)) {
+    // This entry is no longer referenced from outside and is doomed.
+    // Tell the file to kill the handle, i.e. bypass any I/O operations
+    // on it except removing the file.
+    mFile->Kill();
+  }
+
   if (mWriter != aHandle) {
     LOG(("  not the writer"));
     return;
   }
 
   if (mOutputStream) {
     LOG(("  abandoning phantom output stream"));
     // No one took our internal output stream, so there are no data
@@ -1112,18 +1119,20 @@ NS_IMETHODIMP CacheEntry::SetExpirationT
 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
 {
   LOG(("CacheEntry::OpenInputStream [this=%p]", this));
 
   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
 
   nsresult rv;
 
+  RefPtr<CacheEntryHandle> selfHandle = NewHandle();
+
   nsCOMPtr<nsIInputStream> stream;
-  rv = mFile->OpenInputStream(getter_AddRefs(stream));
+  rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsISeekableStream> seekable =
     do_QueryInterface(stream, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -189,27 +189,28 @@ CacheFile::CacheFile()
   , mPriority(false)
   , mDataAccessed(false)
   , mDataIsDirty(false)
   , mWritingMetadata(false)
   , mPreloadWithoutInputStreams(true)
   , mPreloadChunkCount(0)
   , mStatus(NS_OK)
   , mDataSize(-1)
+  , mKill(false)
   , mOutput(nullptr)
 {
   LOG(("CacheFile::CacheFile() [this=%p]", this));
 }
 
 CacheFile::~CacheFile()
 {
   LOG(("CacheFile::~CacheFile() [this=%p]", this));
 
   MutexAutoLock lock(mLock);
-  if (!mMemoryOnly && mReady) {
+  if (!mMemoryOnly && mReady && !mKill) {
     // mReady flag indicates we have metadata plus in a valid state.
     WriteMetadataIfNeededLocked(true);
   }
 }
 
 nsresult
 CacheFile::Init(const nsACString &aKey,
                 bool aCreateNew,
@@ -698,18 +699,28 @@ CacheFile::OnEOFSet(CacheFileHandle *aHa
 
 nsresult
 CacheFile::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
 {
   MOZ_CRASH("CacheFile::OnFileRenamed should not be called!");
   return NS_ERROR_UNEXPECTED;
 }
 
+bool CacheFile::IsKilled()
+{
+  bool killed = mKill;
+  if (killed) {
+    LOG(("CacheFile is killed, this=%p", this));
+  }
+
+  return killed;
+}
+
 nsresult
-CacheFile::OpenInputStream(nsIInputStream **_retval)
+CacheFile::OpenInputStream(nsICacheEntry *aEntryHandle, nsIInputStream **_retval)
 {
   CacheFileAutoLock lock(this);
 
   MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
 
   if (!mReady) {
     LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
          this));
@@ -730,17 +741,17 @@ CacheFile::OpenInputStream(nsIInputStrea
     return mStatus;
   }
 
   // Once we open input stream we no longer allow preloading of chunks without
   // input stream, i.e. we will no longer keep first few chunks preloaded when
   // the last input stream is closed.
   mPreloadWithoutInputStreams = false;
 
-  CacheFileInputStream *input = new CacheFileInputStream(this);
+  CacheFileInputStream *input = new CacheFileInputStream(this, aEntryHandle);
 
   LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
        input, this));
 
   mInputs.AppendElement(input);
   NS_ADDREF(input);
 
   mDataAccessed = true;
@@ -902,16 +913,19 @@ CacheFile::GetElement(const char *aKey, 
   *_retval = NS_strdup(value);
   return NS_OK;
 }
 
 nsresult
 CacheFile::SetElement(const char *aKey, const char *aValue)
 {
   CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::SetElement() this=%p", this));
+
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
   PostWriteTimer();
   return mMetadata->SetElement(aKey, aValue);
 }
 
 nsresult
@@ -936,16 +950,20 @@ CacheFile::ElementsSize(uint32_t *_retva
   *_retval = mMetadata->ElementsSize();
   return NS_OK;
 }
 
 nsresult
 CacheFile::SetExpirationTime(uint32_t aExpirationTime)
 {
   CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::SetExpirationTime() this=%p, expiration=%u",
+       this, aExpirationTime));
+
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
   PostWriteTimer();
 
   if (mHandle && !mHandle->IsDoomed())
     CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &aExpirationTime);
 
@@ -961,16 +979,20 @@ CacheFile::GetExpirationTime(uint32_t *_
 
   return mMetadata->GetExpirationTime(_retval);
 }
 
 nsresult
 CacheFile::SetFrecency(uint32_t aFrecency)
 {
   CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::SetFrecency() this=%p, frecency=%u",
+       this, aFrecency));
+
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
   PostWriteTimer();
 
   if (mHandle && !mHandle->IsDoomed())
     CacheFileIOManager::UpdateIndexEntry(mHandle, &aFrecency, nullptr);
 
@@ -1016,16 +1038,19 @@ CacheFile::GetFetchCount(uint32_t *_retv
 
   return mMetadata->GetFetchCount(_retval);
 }
 
 nsresult
 CacheFile::OnFetched()
 {
   CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::OnFetched() this=%p", this));
+
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
   PostWriteTimer();
 
   return mMetadata->OnFetched();
 }
 
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -69,25 +69,27 @@ public:
 
   NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
                            nsresult aResult) override;
   NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) override;
   NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) override;
+  virtual bool IsKilled() override;
 
   NS_IMETHOD OnMetadataRead(nsresult aResult) override;
   NS_IMETHOD OnMetadataWritten(nsresult aResult) override;
 
-  NS_IMETHOD OpenInputStream(nsIInputStream **_retval);
+  NS_IMETHOD OpenInputStream(nsICacheEntry *aCacheEntryHandle, nsIInputStream **_retval);
   NS_IMETHOD OpenOutputStream(CacheOutputCloseListener *aCloseListener, nsIOutputStream **_retval);
   NS_IMETHOD SetMemoryOnly();
   NS_IMETHOD Doom(CacheFileListener *aCallback);
 
+  void Kill() { mKill = true; }
   nsresult   ThrowMemoryCachedData();
 
   // metadata forwarders
   nsresult GetElement(const char *aKey, char **_retval);
   nsresult SetElement(const char *aKey, const char *aValue);
   nsresult VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor);
   nsresult ElementsSize(uint32_t *_retval);
   nsresult SetExpirationTime(uint32_t aExpirationTime);
@@ -193,16 +195,17 @@ private:
   nsresult       mStatus;
   int64_t        mDataSize;
   nsCString      mKey;
 
   RefPtr<CacheFileHandle>      mHandle;
   RefPtr<CacheFileMetadata>    mMetadata;
   nsCOMPtr<CacheFileListener>  mListener;
   nsCOMPtr<CacheFileIOListener>   mDoomAfterOpenListener;
+  Atomic<bool, Relaxed>        mKill;
 
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
   nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
 
   nsTArray<CacheFileInputStream*> mInputs;
   CacheFileOutputStream          *mOutput;
 
--- a/netwerk/cache2/CacheFileChunk.cpp
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -590,16 +590,22 @@ CacheFileChunk::OnEOFSet(CacheFileHandle
 nsresult
 CacheFileChunk::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
 {
   MOZ_CRASH("CacheFileChunk::OnFileRenamed should not be called!");
   return NS_ERROR_UNEXPECTED;
 }
 
 bool
+CacheFileChunk::IsKilled()
+{
+  return mFile->IsKilled();
+}
+
+bool
 CacheFileChunk::IsReady() const
 {
   mFile->AssertOwnsLock();
 
   return (NS_SUCCEEDED(mStatus) && (mState == READY || mState == WRITING));
 }
 
 bool
--- a/netwerk/cache2/CacheFileChunk.h
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -89,16 +89,17 @@ public:
 
   NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
                            nsresult aResult) override;
   NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) override;
   NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) override;
+  virtual bool IsKilled() override;
 
   bool   IsReady() const;
   bool   IsDirty() const;
 
   nsresult GetStatus();
   void     SetError(nsresult aStatus);
 
   char *       BufForWriting() const;
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -658,17 +658,17 @@ protected:
     MOZ_COUNT_DTOR(ReadEvent);
   }
 
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
-    if (mHandle->IsClosed()) {
+    if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
       rv = CacheFileIOManager::gInstance->ReadInternal(
         mHandle, mOffset, mBuf, mCount);
     }
 
     mCallback->OnDataRead(mHandle, mBuf, rv);
     return NS_OK;
@@ -708,17 +708,17 @@ protected:
     }
   }
 
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
-    if (mHandle->IsClosed()) {
+    if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
       // We usually get here only after the internal shutdown
       // (i.e. mShuttingDown == true).  Pretend write has succeeded
       // to avoid any past-shutdown file dooming.
       rv = (CacheFileIOManager::gInstance->IsPastShutdownIOLag() ||
             CacheFileIOManager::gInstance->mShuttingDown)
         ? NS_OK
         : NS_ERROR_NOT_INITIALIZED;
     } else {
@@ -881,17 +881,17 @@ protected:
     MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent);
   }
 
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
-    if (mHandle->IsClosed()) {
+    if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
       rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
         mHandle, mTruncatePos, mEOFPos);
     }
 
     if (mCallback) {
       mCallback->OnEOFSet(mHandle, rv);
@@ -1914,17 +1914,17 @@ CacheFileIOManager::Write(CacheFileHandl
 {
   LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, "
        "validate=%d, truncate=%d, listener=%p]", aHandle, aOffset, aCount,
        aValidate, aTruncate, aCallback));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
-  if (aHandle->IsClosed() || !ioMan) {
+  if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
     if (!aCallback) {
       // When no callback is provided, CacheFileIOManager is responsible for
       // releasing the buffer. We must release it even in case of failure.
       free(const_cast<char *>(aBuf));
     }
     return NS_ERROR_NOT_INITIALIZED;
   }
 
@@ -2324,17 +2324,17 @@ CacheFileIOManager::TruncateSeekSetEOF(C
                                        CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, "
        "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
-  if (aHandle->IsClosed() || !ioMan) {
+  if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   RefPtr<TruncateSeekSetEOFEvent> ev = new TruncateSeekSetEOFEvent(
                                            aHandle, aTruncatePos, aEOFPos,
                                            aCallback);
   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -3786,16 +3786,18 @@ CacheFileIOManager::CreateCacheTree()
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
 {
+  LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p", aHandle));
+
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(!aHandle->mFD);
   MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
   MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
   MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
              (!aCreate && aHandle->mFileExists));
 
   nsresult rv;
@@ -3855,16 +3857,19 @@ CacheFileIOManager::OpenNSPRHandle(Cache
       LOG(("  file doesn't exists"));
       aHandle->mFileExists = false;
       return DoomFileInternal(aHandle);
     }
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mHandlesByLastUsed.AppendElement(aHandle);
+
+  LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p", aHandle));
+
   return NS_OK;
 }
 
 void
 CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(aHandle->mFD);
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -229,16 +229,18 @@ public:
   NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) = 0;
   NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
                            nsresult aResult) = 0;
   NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf,
                         nsresult aResult) = 0;
   NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) = 0;
   NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) = 0;
   NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) = 0;
+
+  virtual bool IsKilled() { return false; }
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID)
 
 
 class CacheFileIOManager : public nsITimerCallback
 {
 public:
--- a/netwerk/cache2/CacheFileInputStream.cpp
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -37,24 +37,25 @@ CacheFileInputStream::Release()
 NS_INTERFACE_MAP_BEGIN(CacheFileInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
   NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END_THREADSAFE
 
-CacheFileInputStream::CacheFileInputStream(CacheFile *aFile)
+CacheFileInputStream::CacheFileInputStream(CacheFile *aFile, nsISupports *aEntry)
   : mFile(aFile)
   , mPos(0)
   , mClosed(false)
   , mStatus(NS_OK)
   , mWaitingForUpdate(false)
   , mListeningForChunk(-1)
   , mCallbackFlags(0)
+  , mCacheEntryHandle(aEntry)
 {
   LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
   MOZ_COUNT_CTOR(CacheFileInputStream);
 }
 
 CacheFileInputStream::~CacheFileInputStream()
 {
   LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
@@ -235,16 +236,18 @@ CacheFileInputStream::CloseWithStatusLoc
   if (mChunk) {
     ReleaseChunk();
   }
 
   // TODO propagate error from input stream to other streams ???
 
   MaybeNotifyListener();
 
+  mFile->ReleaseOutsideLock(mCacheEntryHandle.forget());
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CacheFileInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
                                 uint32_t aFlags,
                                 uint32_t aRequestedCount,
                                 nsIEventTarget *aEventTarget)
--- a/netwerk/cache2/CacheFileInputStream.h
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -6,33 +6,32 @@
 #define CacheFileInputStream__h__
 
 #include "nsIAsyncInputStream.h"
 #include "nsISeekableStream.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "CacheFileChunk.h"
 
-
 namespace mozilla {
 namespace net {
 
 class CacheFile;
 
 class CacheFileInputStream : public nsIAsyncInputStream
                            , public nsISeekableStream
                            , public CacheFileChunkListener
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
 
 public:
-  explicit CacheFileInputStream(CacheFile *aFile);
+  explicit CacheFileInputStream(CacheFile *aFile, nsISupports *aEntry);
 
   NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
                               CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) override;
 
   // Memory reporting
@@ -59,15 +58,17 @@ private:
   bool                     mClosed;
   nsresult                 mStatus;
   bool                     mWaitingForUpdate;
   int64_t                  mListeningForChunk;
 
   nsCOMPtr<nsIInputStreamCallback> mCallback;
   uint32_t                         mCallbackFlags;
   nsCOMPtr<nsIEventTarget>         mCallbackTarget;
+  // Held purely for referencing purposes
+  RefPtr<nsISupports>              mCacheEntryHandle;
 };
 
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheFileMetadata.h
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -111,16 +111,17 @@ public:
 
 class CacheFileMetadataListener : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEMETADATALISTENER_IID)
 
   NS_IMETHOD OnMetadataRead(nsresult aResult) = 0;
   NS_IMETHOD OnMetadataWritten(nsresult aResult) = 0;
+  virtual bool IsKilled() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileMetadataListener,
                               CACHEFILEMETADATALISTENER_IID)
 
 
 class CacheFileMetadata : public CacheFileIOListener
                         , public CacheMemoryConsumer
@@ -178,16 +179,17 @@ public:
 
   NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
                            nsresult aResult) override;
   NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) override;
   NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) override;
   NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) override;
+  virtual bool IsKilled() override { return mListener && mListener->IsKilled(); }
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   virtual ~CacheFileMetadata();