Bug 913812 - HTTP cache v2: properly propagate errors through the cache file code, r=honzab
authorMichal Novotny <michal.novotny@gmail.com>
Tue, 22 Apr 2014 13:23:17 +0200
changeset 179511 65219cf254c72af512d1fe447f38944ef40a83b8
parent 179510 622f28ca04b8e6adc6b466b65b87bba596d38e9c
child 179512 62a26bb25da897723e3b5db3be80f0b525d01988
push id26629
push userryanvm@gmail.com
push dateTue, 22 Apr 2014 19:42:36 +0000
treeherdermozilla-central@9f20e885fa3a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs913812
milestone31.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 913812 - HTTP cache v2: properly propagate errors through the cache file code, r=honzab
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileChunk.cpp
netwerk/cache2/CacheFileChunk.h
netwerk/cache2/CacheFileInputStream.cpp
netwerk/cache2/CacheFileOutputStream.cpp
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -314,17 +314,20 @@ CacheFile::OnChunkRead(nsresult aResult,
 
   nsresult rv;
 
   uint32_t index = aChunk->Index();
 
   LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
        this, aResult, aChunk, index));
 
-  // TODO handle ERROR state
+  if (NS_FAILED(aResult)) {
+    SetError(aResult);
+    CacheFileIOManager::DoomFile(mHandle, nullptr);
+  }
 
   if (HaveChunkListeners(index)) {
     rv = NotifyChunkListeners(index, aResult, aChunk);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
@@ -336,65 +339,65 @@ CacheFile::OnChunkWritten(nsresult aResu
 
   nsresult rv;
 
   LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
        this, aResult, aChunk, aChunk->Index()));
 
   MOZ_ASSERT(!mMemoryOnly);
   MOZ_ASSERT(!mOpeningFile);
-
-  // TODO handle ERROR state
+  MOZ_ASSERT(mHandle);
 
   if (NS_FAILED(aResult)) {
-    // TODO ??? doom entry
-    // TODO mark this chunk as memory only, since it wasn't written to disk and
-    // therefore cannot be released from memory
-    // LOG
+    SetError(aResult);
+    CacheFileIOManager::DoomFile(mHandle, nullptr);
   }
 
   if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) {
     // update hash value in metadata
     mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
   }
 
   // notify listeners if there is any
   if (HaveChunkListeners(aChunk->Index())) {
     // don't release the chunk since there are some listeners queued
-    rv = NotifyChunkListeners(aChunk->Index(), NS_OK, aChunk);
+    rv = NotifyChunkListeners(aChunk->Index(), aResult, aChunk);
     if (NS_SUCCEEDED(rv)) {
       MOZ_ASSERT(aChunk->mRefCnt != 2);
       return NS_OK;
     }
   }
 
   if (aChunk->mRefCnt != 2) {
     LOG(("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
          " refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
 
     return NS_OK;
   }
 
 #ifdef CACHE_CHUNKS
-  LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]",
-       this, aChunk));
+  if (NS_SUCCEEDED(aResult)) {
+    LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, "
+         "chunk=%p]", this, aChunk));
+  } else {
+    LOG(("CacheFile::OnChunkWritten() - Removing failed chunk [this=%p, "
+         "chunk=%p]", this, aChunk));
+  }
 #else
-  LOG(("CacheFile::OnChunkWritten() - Releasing unused chunk [this=%p, "
-       "chunk=%p]", this, aChunk));
+  LOG(("CacheFile::OnChunkWritten() - Releasing %s chunk [this=%p, chunk=%p]",
+       NS_SUCCEEDED(aResult) ? "unused" : "failed", this, aChunk));
 #endif
 
-  aChunk->mRemovingChunk = true;
-  ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
-                       aChunk->mFile.forget().take()));
-
+  RemoveChunkInternal(aChunk,
 #ifdef CACHE_CHUNKS
-  mCachedChunks.Put(aChunk->Index(), aChunk);
+                      NS_SUCCEEDED(aResult));
+#else
+                      false);
 #endif
 
-  mChunks.Remove(aChunk->Index());
   WriteMetadataIfNeededLocked();
 
   return NS_OK;
 }
 
 nsresult
 CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
                             CacheFileChunk *aChunk)
@@ -995,16 +998,26 @@ CacheFile::GetChunkLocked(uint32_t aInde
 
   nsresult rv;
 
   nsRefPtr<CacheFileChunk> chunk;
   if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
     LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
          chunk.get(), this));
 
+    // We might get failed chunk between releasing the lock in
+    // CacheFileChunk::OnDataWritten/Read and CacheFile::OnChunkWritten/Read
+    rv = chunk->GetStatus();
+    if (NS_FAILED(rv)) {
+      SetError(rv);
+      LOG(("CacheFile::GetChunkLocked() - Found failed chunk in mChunks "
+           "[this=%p]", this));
+      return rv;
+    }
+
     if (chunk->IsReady() || aWriter) {
       chunk.swap(*_retval);
     }
     else {
       rv = QueueChunkListener(aIndex, aCallback);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
@@ -1051,22 +1064,19 @@ CacheFile::GetChunkLocked(uint32_t aInde
 
     LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
          "the disk [this=%p]", chunk.get(), this));
 
     // Read the chunk from the disk
     rv = chunk->Read(mHandle, std::min(static_cast<uint32_t>(mDataSize - off),
                      static_cast<uint32_t>(kChunkSize)),
                      mMetadata->GetHash(aIndex), this);
-    if (NS_FAILED(rv)) {
-      chunk->mRemovingChunk = true;
-      ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
-                           chunk->mFile.forget().take()));
-      mChunks.Remove(aIndex);
-      NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      RemoveChunkInternal(chunk, false);
+      return rv;
     }
 
     if (aWriter) {
       chunk.swap(*_retval);
     }
     else {
       rv = QueueChunkListener(aIndex, aCallback);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -1199,28 +1209,42 @@ CacheFile::RemoveChunk(CacheFileChunk *a
 
       // We also shouldn't have any queued listener for this chunk
       ChunkListeners *listeners;
       mChunkListeners.Get(chunk->Index(), &listeners);
       MOZ_ASSERT(!listeners);
     }
 #endif
 
+    if (NS_FAILED(mStatus)) {
+      // Don't write any chunk to disk since this entry will be doomed
+      LOG(("CacheFile::RemoveChunk() - Removing chunk because of status "
+           "[this=%p, chunk=%p, mStatus=0x%08x]", this, chunk.get(), mStatus));
+
+      RemoveChunkInternal(chunk, false);
+      return mStatus;
+    }
+
     if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
       LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk "
            "[this=%p]", this));
 
       mDataIsDirty = true;
 
       rv = chunk->Write(mHandle, this);
       if (NS_FAILED(rv)) {
-        // TODO ??? doom entry
-        // TODO mark this chunk as memory only, since it wasn't written to disk
-        // and therefore cannot be released from memory
-        // LOG
+        LOG(("CacheFile::RemoveChunk() - CacheFileChunk::Write() failed "
+             "synchronously. Removing it. [this=%p, chunk=%p, rv=0x%08x]",
+             this, chunk.get(), rv));
+
+        RemoveChunkInternal(chunk, false);
+
+        SetError(rv);
+        CacheFileIOManager::DoomFile(mHandle, nullptr);
+        return rv;
       }
       else {
         // Chunk will be removed in OnChunkWritten if it is still unused
 
         // chunk needs to be released under the lock to be able to rely on
         // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
         chunk = nullptr;
         return NS_OK;
@@ -1236,35 +1260,45 @@ CacheFile::RemoveChunk(CacheFileChunk *a
            " reason=%s]", this, chunk.get(),
            mMemoryOnly ? "memory-only" : "opening-file"));
     } else {
       LOG(("CacheFile::RemoveChunk() - Releasing unused chunk [this=%p, "
            "chunk=%p]", this, chunk.get()));
     }
 #endif
 
-    chunk->mRemovingChunk = true;
-    ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
-                         chunk->mFile.forget().take()));
-#ifndef CACHE_CHUNKS
-    // Cache the chunk only when we have a reason to do so
-    if (mMemoryOnly || mOpeningFile)
+    RemoveChunkInternal(chunk,
+#ifdef CACHE_CHUNKS
+                        true);
+#else
+                        // Cache the chunk only when we have a reason to do so
+                        mMemoryOnly || mOpeningFile);
 #endif
-    {
-      mCachedChunks.Put(chunk->Index(), chunk);
-    }
 
-    mChunks.Remove(chunk->Index());
     if (!mMemoryOnly)
       WriteMetadataIfNeededLocked();
   }
 
   return NS_OK;
 }
 
+void
+CacheFile::RemoveChunkInternal(CacheFileChunk *aChunk, bool aCacheChunk)
+{
+  aChunk->mRemovingChunk = true;
+  ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                       aChunk->mFile.forget().take()));
+
+  if (aCacheChunk) {
+    mCachedChunks.Put(aChunk->Index(), aChunk);
+  }
+
+  mChunks.Remove(aChunk->Index());
+}
+
 nsresult
 CacheFile::RemoveInput(CacheFileInputStream *aInput)
 {
   CacheFileAutoLock lock(this);
 
   LOG(("CacheFile::RemoveInput() [this=%p, input=%p]", this, aInput));
 
   DebugOnly<bool> found;
@@ -1496,21 +1530,20 @@ CacheFile::WriteMetadataIfNeededLocked(b
   LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]",
        this));
 
   rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this);
   if (NS_SUCCEEDED(rv)) {
     mWritingMetadata = true;
     mDataIsDirty = false;
   } else {
-    LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously failed "
-         "[this=%p]", this));
+    LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously "
+         "failed [this=%p]", this));
     // TODO: close streams with error
-    if (NS_SUCCEEDED(mStatus))
-      mStatus = rv;
+    SetError(rv);
   }
 }
 
 void
 CacheFile::PostWriteTimer()
 {
   LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
 
@@ -1610,16 +1643,24 @@ CacheFile::PadChunkWithZeroes(uint32_t a
   chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(),
                         false);
 
   ReleaseOutsideLock(chunk.forget().take());
 
   return NS_OK;
 }
 
+void
+CacheFile::SetError(nsresult aStatus)
+{
+  if (NS_SUCCEEDED(mStatus)) {
+    mStatus = aStatus;
+  }
+}
+
 nsresult
 CacheFile::InitIndexEntry()
 {
   MOZ_ASSERT(mHandle);
 
   if (mHandle->IsDoomed())
     return NS_OK;
 
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -121,16 +121,17 @@ private:
 
   nsresult GetChunk(uint32_t aIndex, bool aWriter,
                     CacheFileChunkListener *aCallback,
                     CacheFileChunk **_retval);
   nsresult GetChunkLocked(uint32_t aIndex, bool aWriter,
                           CacheFileChunkListener *aCallback,
                           CacheFileChunk **_retval);
   nsresult RemoveChunk(CacheFileChunk *aChunk);
+  void     RemoveChunkInternal(CacheFileChunk *aChunk, bool aCacheChunk);
 
   nsresult RemoveInput(CacheFileInputStream *aInput);
   nsresult RemoveOutput(CacheFileOutputStream *aOutput);
   nsresult NotifyChunkListener(CacheFileChunkListener *aCallback,
                                nsIEventTarget *aTarget,
                                nsresult aResult,
                                uint32_t aChunkIdx,
                                CacheFileChunk *aChunk);
@@ -156,16 +157,18 @@ private:
                            void* aClosure);
 
   static PLDHashOperator FailUpdateListeners(const uint32_t& aIdx,
                                              nsRefPtr<CacheFileChunk>& aChunk,
                                              void* aClosure);
 
   nsresult PadChunkWithZeroes(uint32_t aChunkIdx);
 
+  void SetError(nsresult aStatus);
+
   nsresult InitIndexEntry();
 
   mozilla::Mutex mLock;
   bool           mOpeningFile;
   bool           mReady;
   bool           mMemoryOnly;
   bool           mOpenAsMemoryOnly;
   bool           mDataAccessed;
--- a/netwerk/cache2/CacheFileChunk.cpp
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -122,16 +122,17 @@ NS_INTERFACE_MAP_BEGIN(CacheFileChunk)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END_THREADSAFE
 
 CacheFileChunk::CacheFileChunk(CacheFile *aFile, uint32_t aIndex)
   : CacheMemoryConsumer(aFile->mOpenAsMemoryOnly ? MEMORY_ONLY : DONT_REPORT)
   , mIndex(aIndex)
   , mState(INITIAL)
+  , mStatus(NS_OK)
   , mIsDirty(false)
   , mRemovingChunk(false)
   , mDataSize(0)
   , mBuf(nullptr)
   , mBufSize(0)
   , mRWBuf(nullptr)
   , mRWBufSize(0)
   , mReadHash(0)
@@ -198,27 +199,27 @@ CacheFileChunk::Read(CacheFileHandle *aH
 
   mRWBuf = static_cast<char *>(moz_xmalloc(aLen));
   mRWBufSize = aLen;
 
   DoMemoryReport(MemorySize());
 
   rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize, mRWBuf, aLen,
                                 this);
-  if (NS_FAILED(rv)) {
-    mState = READING;   // TODO: properly handle error states
-//    mState = ERROR;
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    rv = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND;
+    SetError(rv);
+  } else {
+    mState = READING;
+    mListener = aCallback;
+    mDataSize = aLen;
+    mReadHash = aHash;
   }
 
-  mState = READING;
-  mListener = aCallback;
-  mDataSize = aLen;
-  mReadHash = aHash;
-  return NS_OK;
+  return rv;
 }
 
 nsresult
 CacheFileChunk::Write(CacheFileHandle *aHandle,
                       CacheFileChunkListener *aCallback)
 {
   mFile->AssertOwnsLock();
 
@@ -234,26 +235,25 @@ CacheFileChunk::Write(CacheFileHandle *a
 
   mRWBuf = mBuf;
   mRWBufSize = mBufSize;
   mBuf = nullptr;
   mBufSize = 0;
 
   rv = CacheFileIOManager::Write(aHandle, mIndex * kChunkSize, mRWBuf,
                                  mDataSize, false, this);
-  if (NS_FAILED(rv)) {
-    mState = WRITING;   // TODO: properly handle error states
-//    mState = ERROR;
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    SetError(rv);
+  } else {
+    mState = WRITING;
+    mListener = aCallback;
+    mIsDirty = false;
   }
 
-  mState = WRITING;
-  mListener = aCallback;
-  mIsDirty = false;
-  return NS_OK;
+  return rv;
 }
 
 void
 CacheFileChunk::WaitForUpdate(CacheFileChunkListener *aCallback)
 {
   mFile->AssertOwnsLock();
 
   LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]",
@@ -363,16 +363,20 @@ CacheFileChunk::DataSize()
 void
 CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen, bool aEOF)
 {
   mFile->AssertOwnsLock();
 
   MOZ_ASSERT(!aEOF, "Implement me! What to do with opened streams?");
   MOZ_ASSERT(aOffset <= mDataSize);
 
+  // UpdateDataSize() is called only when we've written some data to the chunk
+  // and we never write data anymore once some error occurs.
+  MOZ_ASSERT(mState != ERROR);
+
   LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d, EOF=%d]",
        this, aOffset, aLen, aEOF));
 
   mIsDirty = true;
 
   int64_t fileSize = kChunkSize * mIndex + aOffset + aLen;
   bool notify = false;
 
@@ -463,39 +467,33 @@ CacheFileChunk::OnDataWritten(CacheFileH
   nsCOMPtr<CacheFileChunkListener> listener;
 
   {
     CacheFileAutoLock lock(mFile);
 
     MOZ_ASSERT(mState == WRITING);
     MOZ_ASSERT(mListener);
 
-#if 0
-    // TODO: properly handle error states
-    if (NS_FAILED(aResult)) {
-      mState = ERROR;
-    }
-    else {
-#endif
+    if (NS_WARN_IF(NS_FAILED(aResult))) {
+      SetError(aResult);
+    } else {
       mState = READY;
-      if (!mBuf) {
-        mBuf = mRWBuf;
-        mBufSize = mRWBufSize;
-      }
-      else {
-        free(mRWBuf);
-      }
+    }
 
-      mRWBuf = nullptr;
-      mRWBufSize = 0;
+    if (!mBuf) {
+      mBuf = mRWBuf;
+      mBufSize = mRWBufSize;
+    } else {
+      free(mRWBuf);
+    }
 
-      DoMemoryReport(MemorySize());
-#if 0
-    }
-#endif
+    mRWBuf = nullptr;
+    mRWBufSize = 0;
+
+    DoMemoryReport(MemorySize());
 
     mListener.swap(listener);
   }
 
   listener->OnChunkWritten(aResult, this);
 
   return NS_OK;
 }
@@ -550,24 +548,20 @@ CacheFileChunk::OnDataRead(CacheFileHand
           mRWBufSize = 0;
 
           DoMemoryReport(MemorySize());
         }
       }
     }
 
     if (NS_FAILED(aResult)) {
-#if 0
-      // TODO: properly handle error states
-      mState = ERROR;
-#endif
-      mState = READY;
+      aResult = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND;
+      SetError(aResult);
       mDataSize = 0;
-    }
-    else {
+    } else {
       mState = READY;
     }
 
     mListener.swap(listener);
   }
 
   listener->OnChunkRead(aResult, this);
 
@@ -595,27 +589,47 @@ CacheFileChunk::OnFileRenamed(CacheFileH
   return NS_ERROR_UNEXPECTED;
 }
 
 bool
 CacheFileChunk::IsReady() const
 {
   mFile->AssertOwnsLock();
 
-  return (mState == READY || mState == WRITING);
+  return (NS_SUCCEEDED(mStatus) && (mState == READY || mState == WRITING));
 }
 
 bool
 CacheFileChunk::IsDirty() const
 {
   mFile->AssertOwnsLock();
 
   return mIsDirty;
 }
 
+nsresult
+CacheFileChunk::GetStatus()
+{
+  mFile->AssertOwnsLock();
+
+  return mStatus;
+}
+
+void
+CacheFileChunk::SetError(nsresult aStatus)
+{
+  if (NS_SUCCEEDED(mStatus)) {
+    MOZ_ASSERT(mState != ERROR);
+    mStatus = aStatus;
+    mState = ERROR;
+  } else {
+    MOZ_ASSERT(mState == ERROR);
+  }
+}
+
 char *
 CacheFileChunk::BufForWriting() const
 {
   mFile->AssertOwnsLock();
 
   MOZ_ASSERT(mBuf); // Writer should always first call EnsureBufSize()
 
   MOZ_ASSERT((mState == READY && !mRWBuf) ||
@@ -636,16 +650,20 @@ CacheFileChunk::BufForReading() const
   return mBuf ? mBuf : mRWBuf;
 }
 
 void
 CacheFileChunk::EnsureBufSize(uint32_t aBufSize)
 {
   mFile->AssertOwnsLock();
 
+  // EnsureBufSize() is called only when we want to write some data to the chunk
+  // and we never write data anymore once some error occurs.
+  MOZ_ASSERT(mState != ERROR);
+
   if (mBufSize >= aBufSize)
     return;
 
   bool copy = false;
   if (!mBuf && mState == WRITING) {
     // We need to duplicate the data that is being written on the background
     // thread, so make sure that all the data fits into the new buffer.
     copy = true;
--- a/netwerk/cache2/CacheFileChunk.h
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -92,16 +92,19 @@ public:
   NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
   NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
   NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
   NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult);
 
   bool   IsReady() const;
   bool   IsDirty() const;
 
+  nsresult GetStatus();
+  void     SetError(nsresult aStatus);
+
   char *       BufForWriting() const;
   const char * BufForReading() const;
   void         EnsureBufSize(uint32_t aBufSize);
   uint32_t     MemorySize() const { return sizeof(CacheFileChunk) + mRWBufSize + mBufSize; }
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
@@ -118,16 +121,17 @@ private:
     READING = 1,
     WRITING = 2,
     READY   = 3,
     ERROR   = 4
   };
 
   uint32_t mIndex;
   EState   mState;
+  nsresult mStatus;
   bool     mIsDirty;
   bool     mRemovingChunk;
   uint32_t mDataSize;
 
   char    *mBuf;
   uint32_t mBufSize;
 
   char               *mRWBuf;
--- a/netwerk/cache2/CacheFileInputStream.cpp
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -78,16 +78,19 @@ CacheFileInputStream::Available(uint64_t
 
   if (mClosed) {
     LOG(("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
          "status=0x%08x]", this, mStatus));
     return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
   }
 
   EnsureCorrectChunk(false);
+  if (NS_FAILED(mStatus))
+    return mStatus;
+
   *_retval = 0;
 
   if (mChunk) {
     int64_t canRead;
     const char *buf;
     CanRead(&canRead, &buf);
 
     if (canRead > 0)
@@ -119,16 +122,19 @@ CacheFileInputStream::Read(char *aBuf, u
     if NS_FAILED(mStatus)
       return mStatus;
 
     *_retval = 0;
     return NS_OK;
   }
 
   EnsureCorrectChunk(false);
+  if (NS_FAILED(mStatus))
+    return mStatus;
+
   if (!mChunk) {
     if (mListeningForChunk == -1) {
       LOG(("  no chunk, returning 0 read and NS_OK"));
       *_retval = 0;
       return NS_OK;
     }
     else {
       LOG(("  waiting for chuck, returning WOULD_BLOCK"));
@@ -189,16 +195,19 @@ CacheFileInputStream::ReadSegments(nsWri
     if NS_FAILED(mStatus)
       return mStatus;
 
     *_retval = 0;
     return NS_OK;
   }
 
   EnsureCorrectChunk(false);
+  if (NS_FAILED(mStatus))
+    return mStatus;
+
   if (!mChunk) {
     if (mListeningForChunk == -1) {
       *_retval = 0;
       return NS_OK;
     }
     else {
       return NS_BASE_STREAM_WOULD_BLOCK;
     }
@@ -430,17 +439,27 @@ CacheFileInputStream::OnChunkAvailable(n
     MOZ_ASSERT(!mCallback);
 
     LOG(("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
          "ignoring notification. [this=%p]", this));
 
     return NS_OK;
   }
 
-  mChunk = aChunk;
+  if (NS_SUCCEEDED(aResult)) {
+    mChunk = aChunk;
+  } else if (aResult != NS_ERROR_NOT_AVAILABLE) {
+    // We store the error in mStatus, so we can propagate it later to consumer
+    // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
+    // differently since it is returned when the requested chunk is not
+    // available and there is no writer that could create it, i.e. it means that
+    // we've reached the end of the file.
+    mStatus = aResult;
+  }
+
   MaybeNotifyListener();
 
   return NS_OK;
 }
 
 nsresult
 CacheFileInputStream::OnChunkUpdated(CacheFileChunk *aChunk)
 {
@@ -523,17 +542,24 @@ CacheFileInputStream::EnsureCorrectChunk
 
     return;
   }
 
   rv = mFile->GetChunkLocked(chunkIdx, false, this, getter_AddRefs(mChunk));
   if (NS_FAILED(rv)) {
     LOG(("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
          "[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv));
-
+    if (rv != NS_ERROR_NOT_AVAILABLE) {
+      // We store the error in mStatus, so we can propagate it later to consumer
+      // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
+      // differently since it is returned when the requested chunk is not
+      // available and there is no writer that could create it, i.e. it means
+      // that we've reached the end of the file.
+      mStatus = rv;
+    }
   }
   else if (!mChunk) {
     mListeningForChunk = static_cast<int64_t>(chunkIdx);
   }
 
   MaybeNotifyListener();
 }
 
@@ -582,17 +608,17 @@ CacheFileInputStream::MaybeNotifyListene
   LOG(("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
        "mClosed=%d, mStatus=0x%08x, mChunk=%p, mListeningForChunk=%lld, "
        "mWaitingForUpdate=%d]", this, mCallback.get(), mClosed, mStatus,
        mChunk.get(), mListeningForChunk, mWaitingForUpdate));
 
   if (!mCallback)
     return;
 
-  if (mClosed) {
+  if (mClosed || NS_FAILED(mStatus)) {
     NotifyListener();
     return;
   }
 
   if (!mChunk) {
     if (mListeningForChunk == -1) {
       // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
       NotifyListener();
--- a/netwerk/cache2/CacheFileOutputStream.cpp
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -93,16 +93,18 @@ CacheFileOutputStream::Write(const char 
 
     return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
   }
 
   *_retval = aCount;
 
   while (aCount) {
     EnsureCorrectChunk(false);
+    if (NS_FAILED(mStatus))
+      return mStatus;
 
     FillHole();
 
     uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
     uint32_t canWrite = kChunkSize - chunkOffset;
     uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
     mChunk->EnsureBufSize(chunkOffset + thisWrite);
     memcpy(mChunk->BufForWriting() + chunkOffset, aBuf, thisWrite);
@@ -337,20 +339,23 @@ CacheFileOutputStream::EnsureCorrectChun
     else {
       ReleaseChunk();
     }
   }
 
   if (aReleaseOnly)
     return;
 
-  DebugOnly<nsresult> rv;
+  nsresult rv;
   rv = mFile->GetChunkLocked(chunkIdx, true, nullptr, getter_AddRefs(mChunk));
-  MOZ_ASSERT(NS_SUCCEEDED(rv),
-             "CacheFile::GetChunkLocked() should always succeed for writer");
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
+         "[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv));
+    mStatus = rv;
+  }
 }
 
 void
 CacheFileOutputStream::FillHole()
 {
   mFile->AssertOwnsLock();
 
   MOZ_ASSERT(mChunk);