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 179917 65219cf254c72af512d1fe447f38944ef40a83b8
parent 179916 622f28ca04b8e6adc6b466b65b87bba596d38e9c
child 179918 62a26bb25da897723e3b5db3be80f0b525d01988
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewershonzab
bugs913812
milestone31.0a1
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);