Bug 808997 - Explicitly close all output and input streams of all active cache entries during shutdown, r=hurley
authorMichal Novotny <michal.novotny@gmail.com>
Fri, 14 Dec 2012 18:03:01 +0100
changeset 116076 1c74122a6fcfeb556a1cebe979b74b9f85dddef6
parent 116075 3173175efff1a391e065f81ed4de36f896bca592
child 116077 d8be8785e3a7da63748b0e5103026aa618e5aabd
push idunknown
push userunknown
push dateunknown
reviewershurley
bugs808997
milestone20.0a1
Bug 808997 - Explicitly close all output and input streams of all active cache entries during shutdown, r=hurley
netwerk/base/src/nsStreamListenerTee.cpp
netwerk/cache/Makefile.in
netwerk/cache/nsCacheEntry.cpp
netwerk/cache/nsCacheEntry.h
netwerk/cache/nsCacheEntryDescriptor.cpp
netwerk/cache/nsCacheEntryDescriptor.h
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsDiskCacheStreams.cpp
netwerk/cache/nsDiskCacheStreams.h
netwerk/cache/nsIDiskCacheStreamInternal.idl
netwerk/test/unit/test_doomentry.js
toolkit/components/telemetry/Histograms.json
--- a/netwerk/base/src/nsStreamListenerTee.cpp
+++ b/netwerk/base/src/nsStreamListenerTee.cpp
@@ -35,17 +35,20 @@ nsStreamListenerTee::OnStopRequest(nsIRe
         mInputTee->SetSink(nullptr);
         mInputTee = 0;
     }
 
     // release sink on the same thread where the data was written (bug 716293)
     if (mEventTarget) {
         nsIOutputStream *sink = nullptr;
         mSink.swap(sink);
-        NS_ProxyRelease(mEventTarget, sink);
+        if (NS_FAILED(NS_ProxyRelease(mEventTarget, sink))) {
+            NS_WARNING("Releasing sink on the current thread!");
+            NS_RELEASE(sink);
+        }
     }
     else {
         mSink = 0;
     }
 
     nsresult rv = mListener->OnStopRequest(request, context, status);
     if (mObserver)
         mObserver->OnStopRequest(request, context, status);
--- a/netwerk/cache/Makefile.in
+++ b/netwerk/cache/Makefile.in
@@ -21,17 +21,16 @@ FORCE_STATIC_LIB = 1
 
 XPIDLSRCS = \
   nsICache.idl \
   nsICacheEntryDescriptor.idl \
   nsICacheListener.idl \
   nsICacheService.idl \
   nsICacheSession.idl \
   nsICacheVisitor.idl \
-  nsIDiskCacheStreamInternal.idl \
   $(NULL)
 
 EXPORTS = \
   nsCacheService.h \
   nsApplicationCacheService.h \
   $(NULL)
 
 CPPSRCS = \
--- a/netwerk/cache/nsCacheEntry.cpp
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -206,65 +206,69 @@ nsCacheEntry::RemoveRequest(nsCacheReque
 
     // return true if this entry should stay active
     return !((PR_CLIST_IS_EMPTY(&mRequestQ)) &&
              (PR_CLIST_IS_EMPTY(&mDescriptorQ)));
 }
 
 
 bool
-nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor * descriptor)
+nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor * descriptor,
+                               bool                   * doomEntry)
 {
     NS_ASSERTION(descriptor->CacheEntry() == this, "### Wrong cache entry!!");
-    nsresult rv = descriptor->CloseOutput();
-    if (rv == NS_BASE_STREAM_WOULD_BLOCK)
-        return true;
 
-    descriptor->ClearCacheEntry();
-    PR_REMOVE_AND_INIT_LINK(descriptor);
+    *doomEntry = descriptor->ClearCacheEntry();
 
-    // Doom entry if something bad happens while closing. See bug #673543
-    if (NS_FAILED(rv))
-        nsCacheService::DoomEntry(this);
+    PR_REMOVE_AND_INIT_LINK(descriptor);
 
     if (!PR_CLIST_IS_EMPTY(&mDescriptorQ))
         return true;  // stay active if we still have open descriptors
 
     if (PR_CLIST_IS_EMPTY(&mRequestQ))
         return false; // no descriptors or requests, we can deactivate
 
     return true;     // find next best request to give a descriptor to
 }
 
 
 void
-nsCacheEntry::DetachDescriptors(void)
+nsCacheEntry::DetachDescriptors()
 {
     nsCacheEntryDescriptor * descriptor =
         (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ);
 
     while (descriptor != &mDescriptorQ) {
         nsCacheEntryDescriptor * nextDescriptor =
             (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor);
 
-        // Doom entry if something bad happens while closing. See bug #673543
-        // Errors are handled different from RemoveDescriptor because this
-        // method is only called from ClearDoomList (in which case the entry is
-        // doomed anyway) and ClearActiveEntries (in which case we are shutting
-        // down and really want to get rid of the entry immediately)
-        if (NS_FAILED(descriptor->CloseOutput()))
-            nsCacheService::DoomEntry(this);
-
         descriptor->ClearCacheEntry();
         PR_REMOVE_AND_INIT_LINK(descriptor);
         descriptor = nextDescriptor;
     }
 }
 
 
+void
+nsCacheEntry::GetDescriptors(
+    nsTArray<nsRefPtr<nsCacheEntryDescriptor> > &outDescriptors)
+{
+    nsCacheEntryDescriptor * descriptor =
+        (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ);
+
+    while (descriptor != &mDescriptorQ) {
+        nsCacheEntryDescriptor * nextDescriptor =
+            (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor);
+
+        outDescriptors.AppendElement(descriptor);
+        descriptor = nextDescriptor;
+    }
+}
+
+
 /******************************************************************************
  * nsCacheEntryInfo - for implementing about:cache
  *****************************************************************************/
 
 NS_IMPL_ISUPPORTS1(nsCacheEntryInfo, nsICacheEntryInfo)
 
 
 NS_IMETHODIMP
--- a/netwerk/cache/nsCacheEntry.h
+++ b/netwerk/cache/nsCacheEntry.h
@@ -186,26 +186,27 @@ public:
 
 
     // methods for nsCacheService
     nsresult RequestAccess( nsCacheRequest * request, nsCacheAccessMode *accessGranted);
     nsresult CreateDescriptor( nsCacheRequest *           request,
                                nsCacheAccessMode          accessGranted,
                                nsICacheEntryDescriptor ** result);
 
-    //    nsresult Open(nsCacheRequest *request, nsICacheEntryDescriptor ** result);
-    //    nsresult AsyncOpen(nsCacheRequest *request);
     bool     RemoveRequest( nsCacheRequest * request);
-    bool     RemoveDescriptor( nsCacheEntryDescriptor * descriptor);
-    
+    bool     RemoveDescriptor( nsCacheEntryDescriptor * descriptor,
+                               bool                   * doomEntry);
+
+    void     GetDescriptors(nsTArray<nsRefPtr<nsCacheEntryDescriptor> > &outDescriptors);
+
 private:
     friend class nsCacheEntryHashTable;
     friend class nsCacheService;
 
-    void     DetachDescriptors(void);
+    void     DetachDescriptors();
 
     // internal methods
     void MarkDoomed()          { mFlags |=  eDoomedMask; }
     void MarkStreamBased()     { mFlags |=  eStreamDataMask; }
     void MarkInitialized()     { mFlags |=  eInitializedMask; }
     void MarkActive()          { mFlags |=  eActiveMask; }
     void MarkInactive()        { mFlags &= ~eActiveMask; }
 
--- a/netwerk/cache/nsCacheEntryDescriptor.cpp
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -72,36 +72,41 @@ private:
 NS_IMPL_THREADSAFE_ISUPPORTS2(nsCacheEntryDescriptor,
                               nsICacheEntryDescriptor,
                               nsICacheEntryInfo)
 
 nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry * entry,
                                                nsCacheAccessMode accessGranted)
     : mCacheEntry(entry),
       mAccessGranted(accessGranted),
-      mOutput(nullptr),
+      mOutputWrapper(nullptr),
       mLock("nsCacheEntryDescriptor.mLock"),
       mAsyncDoomPending(false),
-      mDoomedOnClose(false)
+      mDoomedOnClose(false),
+      mClosingDescriptor(false)
 {
     PR_INIT_CLIST(this);
     NS_ADDREF(nsCacheService::GlobalInstance());  // ensure it lives for the lifetime of the descriptor
 }
 
 
 nsCacheEntryDescriptor::~nsCacheEntryDescriptor()
 {
     // No need to close if the cache entry has already been severed.  This
     // helps avoid a shutdown assertion (bug 285519) that is caused when
     // consumers end up holding onto these objects past xpcom-shutdown.  It's
     // okay for them to do that because the cache service calls our Close
     // method during xpcom-shutdown, so we don't need to complain about it.
     if (mCacheEntry)
         Close();
 
+    NS_ASSERTION(mInputWrappers.Count() == 0,
+                 "We have still some input wrapper!");
+    NS_ASSERTION(!mOutputWrapper, "We have still an output wrapper!");
+
     nsCacheService * service = nsCacheService::GlobalInstance();
     NS_RELEASE(service);
 }
 
 
 NS_IMETHODIMP
 nsCacheEntryDescriptor::GetClientID(char ** result)
 {
@@ -304,69 +309,81 @@ nsCacheEntryDescriptor::SetDataSize(uint
 }
 
 
 NS_IMETHODIMP
 nsCacheEntryDescriptor::OpenInputStream(uint32_t offset, nsIInputStream ** result)
 {
     NS_ENSURE_ARG_POINTER(result);
 
+    nsInputStreamWrapper* cacheInput = nullptr;
     {
         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENINPUTSTREAM));
         if (!mCacheEntry)                  return NS_ERROR_NOT_AVAILABLE;
         if (!mCacheEntry->IsStreamData())  return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
 
+        // Don't open any new stream when closing descriptor or clearing entries
+        if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+            return NS_ERROR_NOT_AVAILABLE;
+
         // ensure valid permissions
         if (!(mAccessGranted & nsICache::ACCESS_READ))
             return NS_ERROR_CACHE_READ_ACCESS_DENIED;
-    }
 
-    nsInputStreamWrapper* cacheInput = nullptr;
-    const char *val;
-    val = mCacheEntry->GetMetaDataElement("uncompressed-len");
-    if (val) {
-        cacheInput = new nsDecompressInputStreamWrapper(this, offset);
-    } else {
-        cacheInput = new nsInputStreamWrapper(this, offset);
+        const char *val;
+        val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+        if (val) {
+            cacheInput = new nsDecompressInputStreamWrapper(this, offset);
+        } else {
+            cacheInput = new nsInputStreamWrapper(this, offset);
+        }
+        if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY;
+
+        mInputWrappers.AppendElement(cacheInput);
     }
-    if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY;
 
     NS_ADDREF(*result = cacheInput);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset, nsIOutputStream ** result)
 {
     NS_ENSURE_ARG_POINTER(result);
 
+    nsOutputStreamWrapper* cacheOutput = nullptr;
     {
         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENOUTPUTSTREAM));
         if (!mCacheEntry)                  return NS_ERROR_NOT_AVAILABLE;
         if (!mCacheEntry->IsStreamData())  return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
 
+        // Don't open any new stream when closing descriptor or clearing entries
+        if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+            return NS_ERROR_NOT_AVAILABLE;
+
         // ensure valid permissions
         if (!(mAccessGranted & nsICache::ACCESS_WRITE))
             return NS_ERROR_CACHE_WRITE_ACCESS_DENIED;
-    }
 
-    nsOutputStreamWrapper* cacheOutput = nullptr;
-    int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
-    const char *val;
-    val = mCacheEntry->GetMetaDataElement("uncompressed-len");
-    if ((compressionLevel > 0) && val) {
-        cacheOutput = new nsCompressOutputStreamWrapper(this, offset);
-    } else {
-        // clear compression flag when compression disabled - see bug #715198
-        if (val) {
-            mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr);
+        int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+        const char *val;
+        val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+        if ((compressionLevel > 0) && val) {
+            cacheOutput = new nsCompressOutputStreamWrapper(this, offset);
+        } else {
+            // clear compression flag when compression disabled - see bug 715198
+            if (val) {
+                mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr);
+            }
+            cacheOutput = new nsOutputStreamWrapper(this, offset);
         }
-        cacheOutput = new nsOutputStreamWrapper(this, offset);
+        if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY;
+
+        mOutputWrapper = cacheOutput;
     }
-    if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY;
 
     NS_ADDREF(*result = cacheOutput);
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsCacheEntryDescriptor::GetCacheElement(nsISupports ** result)
@@ -532,18 +549,48 @@ nsCacheEntryDescriptor::MarkValid()
     nsresult  rv = nsCacheService::ValidateEntry(mCacheEntry);
     return rv;
 }
 
 
 NS_IMETHODIMP
 nsCacheEntryDescriptor::Close()
 {
+    nsRefPtr<nsOutputStreamWrapper> outputWrapper;
+    nsTArray<nsRefPtr<nsInputStreamWrapper> > inputWrappers;
+
+    {
+        nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+        if (!mCacheEntry)  return NS_ERROR_NOT_AVAILABLE;
+
+        // Make sure no other stream can be opened
+        mClosingDescriptor = true;
+        outputWrapper = mOutputWrapper;
+        for (int32_t i = 0 ; i < mInputWrappers.Count() ; i++)
+            inputWrappers.AppendElement(static_cast<nsInputStreamWrapper *>(
+                        mInputWrappers[i]));
+    }
+
+    // Call Close() on the streams outside the lock since it might need to call
+    // methods that grab the cache service lock, e.g. compressed output stream
+    // when it finalizes the entry
+    if (outputWrapper) {
+        if (NS_FAILED(outputWrapper->Close())) {
+            NS_WARNING("Dooming entry because Close() failed!!!");
+            Doom();
+        }
+        outputWrapper = nullptr;
+    }
+
+    for (uint32_t i = 0 ; i < inputWrappers.Length() ; i++)
+        inputWrappers[i]->Close();
+
+    inputWrappers.Clear();
+
     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
-    if (!mCacheEntry)  return NS_ERROR_NOT_AVAILABLE;
 
     // XXX perhaps closing descriptors should clear/sever transports
 
     // tell nsCacheService we're going away
     nsCacheService::CloseDescriptor(this);
     NS_ASSERTION(mCacheEntry == nullptr, "mCacheEntry not null");
 
     return NS_OK;
@@ -608,16 +655,19 @@ nsCacheEntryDescriptor::VisitMetaData(ns
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsInputStreamWrapper,
                               nsIInputStream)
 
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::LazyInit()
 {
     nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_LAZYINIT));
 
+    if (!mDescriptor)
+        return NS_ERROR_NOT_AVAILABLE;
+
     nsCacheAccessMode mode;
     nsresult rv = mDescriptor->GetAccessGranted(&mode);
     if (NS_FAILED(rv)) return rv;
 
     NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED);
 
     nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
     if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
@@ -631,37 +681,80 @@ nsInputStreamWrapper::LazyInit()
                       mDescriptor, this, mInput.get(), int(rv)));
 
     if (NS_FAILED(rv)) return rv;
 
     mInitialized = true;
     return NS_OK;
 }
 
+void nsCacheEntryDescriptor::
+nsInputStreamWrapper::CloseInternal()
+{
+    mLock.AssertCurrentThreadOwns();
+    nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+    if (mDescriptor) {
+        NS_ASSERTION(mDescriptor->mInputWrappers.IndexOf(this) != -1,
+                     "Wrapper not found in array!");
+        mDescriptor->mInputWrappers.RemoveElement(this);
+        nsCacheService::ReleaseObject_Locked(mDescriptor);
+        mDescriptor = nullptr;
+    }
+    mInitialized = false;
+    mInput = nullptr;
+}
+
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::Close()
 {
+    mozilla::MutexAutoLock lock(mLock);
+
+    return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Close_Locked()
+{
     nsresult rv = EnsureInit();
-    if (NS_FAILED(rv)) return rv;
+    if (NS_SUCCEEDED(rv)) {
+        rv = mInput->Close();
+    } else {
+        NS_ASSERTION(!mInput,
+                     "Shouldn't have mInput when EnsureInit() failed");
+    }
 
-    return mInput->Close();
+    // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+    // closing streams with nsCacheService::CloseAllStream()
+    CloseInternal();
+    return rv;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::Available(uint64_t *avail)
 {
+    mozilla::MutexAutoLock lock(mLock);
+
     nsresult rv = EnsureInit();
     if (NS_FAILED(rv)) return rv;
 
     return mInput->Available(avail);
 }
 
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::Read(char *buf, uint32_t count, uint32_t *countRead)
 {
+    mozilla::MutexAutoLock lock(mLock);
+
+    return Read_Locked(buf, count, countRead);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Read_Locked(char *buf, uint32_t count, uint32_t *countRead)
+{
     nsresult rv = EnsureInit();
     if (NS_SUCCEEDED(rv))
         rv = mInput->Read(buf, count, countRead);
 
     CACHE_LOG_DEBUG(("nsInputStreamWrapper::Read "
                       "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
                       mDescriptor, this, mInput.get(), rv));
 
@@ -692,16 +785,18 @@ nsInputStreamWrapper::IsNonBlocking(bool
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper,
                               nsIInputStream)
 
 NS_IMETHODIMP nsCacheEntryDescriptor::
 nsDecompressInputStreamWrapper::Read(char *    buf, 
                                      uint32_t  count, 
                                      uint32_t *countRead)
 {
+    mozilla::MutexAutoLock lock(mLock);
+
     int zerr = Z_OK;
     nsresult rv = NS_OK;
 
     if (!mStreamInitialized) {
         rv = InitZstream();
         if (NS_FAILED(rv)) {
             return rv;
         }
@@ -732,19 +827,19 @@ nsDecompressInputStreamWrapper::Read(cha
 
     // read and inflate data until the output buffer is full, or
     // there is no more data to read
     while (NS_SUCCEEDED(rv) &&
            zerr == Z_OK && 
            mZstream.avail_out > 0 &&
            count > 0) {
         if (mZstream.avail_in == 0) {
-            rv = nsInputStreamWrapper::Read((char*)mReadBuffer, 
-                                            mReadBufferLen, 
-                                            &mZstream.avail_in);
+            rv = nsInputStreamWrapper::Read_Locked((char*)mReadBuffer,
+                                                   mReadBufferLen,
+                                                   &mZstream.avail_in);
             if (NS_FAILED(rv) || !mZstream.avail_in) {
                 break;
             }
             mZstream.next_in = mReadBuffer;
         }
         zerr = inflate(&mZstream, Z_NO_FLUSH);
         if (zerr == Z_STREAM_END) {
             // The compressed data may have been stored in multiple
@@ -769,28 +864,39 @@ nsDecompressInputStreamWrapper::Read(cha
         *countRead = count - mZstream.avail_out;
     }
     return rv;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsDecompressInputStreamWrapper::Close()
 {
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (!mDescriptor)
+        return NS_ERROR_NOT_AVAILABLE;
+
     EndZstream();
     if (mReadBuffer) {
         nsMemory::Free(mReadBuffer);
         mReadBuffer = 0;
         mReadBufferLen = 0;
     }
-    return nsInputStreamWrapper::Close();
+    return nsInputStreamWrapper::Close_Locked();
 }
 
 nsresult nsCacheEntryDescriptor::
 nsDecompressInputStreamWrapper::InitZstream()
 {
+    if (!mDescriptor)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    if (mStreamEnded)
+        return NS_ERROR_FAILURE;
+
     // Initialize zlib inflate stream
     mZstream.zalloc = Z_NULL;
     mZstream.zfree = Z_NULL;
     mZstream.opaque = Z_NULL;
     mZstream.next_out = Z_NULL;
     mZstream.avail_out = 0;
     mZstream.next_in = Z_NULL;
     mZstream.avail_in = 0;
@@ -801,16 +907,17 @@ nsDecompressInputStreamWrapper::InitZstr
     return NS_OK;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsDecompressInputStreamWrapper::EndZstream()
 {
     if (mStreamInitialized && !mStreamEnded) {
         inflateEnd(&mZstream);
+        mStreamInitialized = false;
         mStreamEnded = true;
     }
     return NS_OK;
 }
 
 
 /******************************************************************************
  * nsOutputStreamWrapper - a wrapper for nsIOutputstream to track the amount of
@@ -821,16 +928,19 @@ nsDecompressInputStreamWrapper::EndZstre
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsOutputStreamWrapper,
                               nsIOutputStream)
 
 nsresult nsCacheEntryDescriptor::
 nsOutputStreamWrapper::LazyInit()
 {
     nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_LAZYINIT));
 
+    if (!mDescriptor)
+        return NS_ERROR_NOT_AVAILABLE;
+
     nsCacheAccessMode mode;
     nsresult rv = mDescriptor->GetAccessGranted(&mode);
     if (NS_FAILED(rv)) return rv;
 
     NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED);
 
     nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
     if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
@@ -852,56 +962,102 @@ nsOutputStreamWrapper::LazyInit()
             cacheEntry->SetDataSize(mStartOffset);
     } else {
         rv = NS_ERROR_NOT_AVAILABLE;
     }
 
     // If anything above failed, clean up internal state and get out of here
     // (see bug #654926)...
     if (NS_FAILED(rv)) {
-        mDescriptor->InternalCleanup(stream);
+        nsCacheService::ReleaseObject_Locked(stream.forget().get());
+        mDescriptor->mOutputWrapper = nullptr;
+        nsCacheService::ReleaseObject_Locked(mDescriptor);
+        mDescriptor = nullptr;
+        mInitialized = false;
         return rv;
     }
 
-    // ... otherwise, set members and mark initialized
-    mDescriptor->mOutput = mOutput = stream;
+    mOutput = stream;
     mInitialized = true;
     return NS_OK;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsOutputStreamWrapper::OnWrite(uint32_t count)
 {
     if (count > INT32_MAX)  return NS_ERROR_UNEXPECTED;
     return mDescriptor->RequestDataSizeChange((int32_t)count);
 }
 
+void nsCacheEntryDescriptor::
+nsOutputStreamWrapper::CloseInternal()
+{
+    mLock.AssertCurrentThreadOwns();
+    nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+    if (mDescriptor) {
+        mDescriptor->mOutputWrapper = nullptr;
+        nsCacheService::ReleaseObject_Locked(mDescriptor);
+        mDescriptor = nullptr;
+    }
+    mInitialized = false;
+    mOutput = nullptr;
+}
+
+
 NS_IMETHODIMP nsCacheEntryDescriptor::
 nsOutputStreamWrapper::Close()
 {
+    mozilla::MutexAutoLock lock(mLock);
+
+    return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Close_Locked()
+{
     nsresult rv = EnsureInit();
-    if (NS_FAILED(rv)) return rv;
+    if (NS_SUCCEEDED(rv)) {
+        rv = mOutput->Close();
+    } else {
+        NS_ASSERTION(!mOutput,
+                     "Shouldn't have mOutput when EnsureInit() failed");
+    }
 
-    return mOutput->Close();
+    // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+    // closing streams with nsCacheService::CloseAllStream()
+    CloseInternal();
+    return rv;
 }
 
 NS_IMETHODIMP nsCacheEntryDescriptor::
 nsOutputStreamWrapper::Flush()
 {
+    mozilla::MutexAutoLock lock(mLock);
+
     nsresult rv = EnsureInit();
     if (NS_FAILED(rv)) return rv;
 
     return mOutput->Flush();
 }
 
 NS_IMETHODIMP nsCacheEntryDescriptor::
 nsOutputStreamWrapper::Write(const char * buf,
                              uint32_t     count,
                              uint32_t *   result)
 {
+    mozilla::MutexAutoLock lock(mLock);
+    return Write_Locked(buf, count, result);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Write_Locked(const char * buf,
+                                    uint32_t count,
+                                    uint32_t * result)
+{
     nsresult rv = EnsureInit();
     if (NS_FAILED(rv)) return rv;
 
     rv = OnWrite(count);
     if (NS_FAILED(rv)) return rv;
 
     return mOutput->Write(buf, count, result);
 }
@@ -940,16 +1096,18 @@ nsOutputStreamWrapper::IsNonBlocking(boo
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper,
                               nsIOutputStream)
 
 NS_IMETHODIMP nsCacheEntryDescriptor::
 nsCompressOutputStreamWrapper::Write(const char * buf,
                                      uint32_t     count,
                                      uint32_t *   result)
 {
+    mozilla::MutexAutoLock lock(mLock);
+
     int zerr = Z_OK;
     nsresult rv = NS_OK;
 
     if (!mStreamInitialized) {
         rv = InitZstream();
         if (NS_FAILED(rv)) {
             return rv;
         }
@@ -972,80 +1130,105 @@ nsCompressOutputStreamWrapper::Write(con
     // Compress (deflate) the requested buffer. Keep going
     // until the entire buffer has been deflated.
     mZstream.avail_in = count;
     mZstream.next_in = (Bytef*)buf;
     while (mZstream.avail_in > 0) {
         zerr = deflate(&mZstream, Z_NO_FLUSH);
         if (zerr == Z_STREAM_ERROR) {
             deflateEnd(&mZstream);
+            mStreamEnded = true;
             mStreamInitialized = false;
             return NS_ERROR_FAILURE;
         }
         // Note: Z_BUF_ERROR is non-fatal and sometimes expected here.
 
         // If the compression stream output buffer is filled, write
         // it out to the underlying stream wrapper.
         if (mZstream.avail_out == 0) {
             rv = WriteBuffer();
             if (NS_FAILED(rv)) {
                 deflateEnd(&mZstream);
+                mStreamEnded = true;
                 mStreamInitialized = false;
                 return rv;
             }
         }
     }
     *result = count;
     mUncompressedCount += *result;
     return NS_OK;
 }
 
 NS_IMETHODIMP nsCacheEntryDescriptor::
 nsCompressOutputStreamWrapper::Close()
 {
-    nsresult rv = NS_OK;
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (!mDescriptor)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    nsresult retval = NS_OK;
+    nsresult rv;
     int zerr = 0;
 
     if (mStreamInitialized) {
         // complete compression of any data remaining in the zlib stream
         do {
             zerr = deflate(&mZstream, Z_FINISH);
             rv = WriteBuffer();
+            if (NS_FAILED(rv))
+                retval = rv;
         } while (zerr == Z_OK && rv == NS_OK);
         deflateEnd(&mZstream);
+        mStreamInitialized = false;
     }
+    // Do not allow to initialize stream after calling Close().
+    mStreamEnded = true;
 
     if (mDescriptor->CacheEntry()) {
         nsAutoCString uncompressedLenStr;
         rv = mDescriptor->GetMetaDataElement("uncompressed-len",
                                              getter_Copies(uncompressedLenStr));
         if (NS_SUCCEEDED(rv)) {
             int32_t oldCount = uncompressedLenStr.ToInteger(&rv);
             if (NS_SUCCEEDED(rv)) {
                 mUncompressedCount += oldCount;
             }
         }
         uncompressedLenStr.Adopt(0);
         uncompressedLenStr.AppendInt(mUncompressedCount);
         rv = mDescriptor->SetMetaDataElement("uncompressed-len",
             uncompressedLenStr.get());
+        if (NS_FAILED(rv))
+            retval = rv;
     }
 
     if (mWriteBuffer) {
         nsMemory::Free(mWriteBuffer);
         mWriteBuffer = 0;
         mWriteBufferLen = 0;
     }
 
-    return nsOutputStreamWrapper::Close();
+    rv = nsOutputStreamWrapper::Close_Locked();
+    if (NS_FAILED(rv))
+        retval = rv;
+
+    return retval;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsCompressOutputStreamWrapper::InitZstream()
 {
+    if (!mDescriptor)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    if (mStreamEnded)
+        return NS_ERROR_FAILURE;
+
     // Determine compression level: Aggressive compression
     // may impact performance on mobile devices, while a
     // lower compression level still provides substantial
     // space savings for many text streams.
     int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
 
     // Initialize zlib deflate stream
     mZstream.zalloc = Z_NULL;
@@ -1063,15 +1246,15 @@ nsCompressOutputStreamWrapper::InitZstre
     return NS_OK;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsCompressOutputStreamWrapper::WriteBuffer()
 {
     uint32_t bytesToWrite = mWriteBufferLen - mZstream.avail_out;
     uint32_t result = 0;
-    nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write(
+    nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
         (const char *)mWriteBuffer, bytesToWrite, &result);
     mZstream.next_out = mWriteBuffer;
     mZstream.avail_out = mWriteBufferLen;
     return rv;
 }
 
--- a/netwerk/cache/nsCacheEntryDescriptor.h
+++ b/netwerk/cache/nsCacheEntryDescriptor.h
@@ -8,115 +8,119 @@
 #ifndef _nsCacheEntryDescriptor_h_
 #define _nsCacheEntryDescriptor_h_
 
 #include "nsICacheEntryDescriptor.h"
 #include "nsCacheEntry.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsCacheService.h"
-#include "nsIDiskCacheStreamInternal.h"
 #include "zlib.h"
 #include "mozilla/Mutex.h"
+#include "nsVoidArray.h"
 
 /******************************************************************************
 * nsCacheEntryDescriptor
 *******************************************************************************/
 class nsCacheEntryDescriptor :
     public PRCList,
     public nsICacheEntryDescriptor
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICACHEENTRYDESCRIPTOR
     NS_DECL_NSICACHEENTRYINFO
 
     friend class nsAsyncDoomEvent;
+    friend class nsCacheService;
 
     nsCacheEntryDescriptor(nsCacheEntry * entry, nsCacheAccessMode  mode);
     virtual ~nsCacheEntryDescriptor();
     
     /**
      * utility method to attempt changing data size of associated entry
      */
     nsresult  RequestDataSizeChange(int32_t deltaSize);
     
     /**
      * methods callbacks for nsCacheService
      */
     nsCacheEntry * CacheEntry(void)      { return mCacheEntry; }
-    void           ClearCacheEntry(void)
+    bool           ClearCacheEntry(void)
     {
+      NS_ASSERTION(mInputWrappers.Count() == 0, "Bad state");
+      NS_ASSERTION(!mOutputWrapper, "Bad state");
+
+      bool doomEntry = false;
       bool asyncDoomPending;
       {
         mozilla::MutexAutoLock lock(mLock);
         asyncDoomPending = mAsyncDoomPending;
       }
 
       if (asyncDoomPending && mCacheEntry) {
-        nsCacheService::gService->DoomEntry_Internal(mCacheEntry, true);
+        doomEntry = true;
         mDoomedOnClose = true;
       }
       mCacheEntry = nullptr;
-    }
 
-    nsresult       CloseOutput(void)
-    {
-      nsresult rv = InternalCleanup(mOutput);
-      mOutput = nullptr;
-      return rv;
+      return doomEntry;
     }
 
 private:
-    nsresult       InternalCleanup(nsIOutputStream *stream)
-    {
-      if (stream) {
-        nsCOMPtr<nsIDiskCacheStreamInternal> tmp (do_QueryInterface(stream));
-        if (tmp)
-          return tmp->CloseInternal();
-        else
-          return stream->Close();
-      }
-      return NS_OK;
-    }
-
-
      /*************************************************************************
       * input stream wrapper class -
       *
       * The input stream wrapper references the descriptor, but the descriptor
       * doesn't need any references to the stream wrapper.
       *************************************************************************/
      class nsInputStreamWrapper : public nsIInputStream {
+         friend class nsCacheEntryDescriptor;
+
      private:
          nsCacheEntryDescriptor    * mDescriptor;
          nsCOMPtr<nsIInputStream>    mInput;
          uint32_t                    mStartOffset;
          bool                        mInitialized;
+         mozilla::Mutex              mLock;
      public:
          NS_DECL_ISUPPORTS
          NS_DECL_NSIINPUTSTREAM
 
          nsInputStreamWrapper(nsCacheEntryDescriptor * desc, uint32_t off)
              : mDescriptor(desc)
              , mStartOffset(off)
              , mInitialized(false)
+             , mLock("nsInputStreamWrapper.mLock")
          {
              NS_ADDREF(mDescriptor);
          }
          virtual ~nsInputStreamWrapper()
          {
-             NS_RELEASE(mDescriptor);
+             nsCOMPtr<nsCacheEntryDescriptor> desc;
+             {
+                 nsCacheServiceAutoLock lock(LOCK_TELEM(
+                                             NSINPUTSTREAMWRAPPER_DESTRUCTOR));
+                 desc.swap(mDescriptor);
+                 if (desc) {
+                     NS_ASSERTION(desc->mInputWrappers.IndexOf(this) != -1,
+                                  "Wrapper not found in array!");
+                     desc->mInputWrappers.RemoveElement(this);
+                 }
+             }
+
          }
 
      private:
          nsresult LazyInit();
          nsresult EnsureInit() { return mInitialized ? NS_OK : LazyInit(); }
+         nsresult Read_Locked(char *buf, uint32_t count, uint32_t *countRead);
+         nsresult Close_Locked();
+         void CloseInternal();
      };
-     friend class nsInputStreamWrapper;
 
 
      class nsDecompressInputStreamWrapper : public nsInputStreamWrapper {
      private:
          unsigned char* mReadBuffer;
          uint32_t mReadBufferLen;
          z_stream mZstream;
          bool mStreamInitialized;
@@ -147,66 +151,82 @@ private:
 
      /*************************************************************************
       * output stream wrapper class -
       *
       * The output stream wrapper references the descriptor, but the descriptor
       * doesn't need any references to the stream wrapper.
       *************************************************************************/
      class nsOutputStreamWrapper : public nsIOutputStream {
+         friend class nsCacheEntryDescriptor;
+
      protected:
          nsCacheEntryDescriptor *    mDescriptor;
          nsCOMPtr<nsIOutputStream>   mOutput;
          uint32_t                    mStartOffset;
          bool                        mInitialized;
+         mozilla::Mutex              mLock;
      public:
          NS_DECL_ISUPPORTS
          NS_DECL_NSIOUTPUTSTREAM
 
          nsOutputStreamWrapper(nsCacheEntryDescriptor * desc, uint32_t off)
              : mDescriptor(desc)
              , mStartOffset(off)
              , mInitialized(false)
+             , mLock("nsOutputStreamWrapper.mLock")
          {
              NS_ADDREF(mDescriptor); // owning ref
          }
          virtual ~nsOutputStreamWrapper()
          { 
              // XXX _HACK_ the storage stream needs this!
              Close();
+             nsCOMPtr<nsCacheEntryDescriptor> desc;
              {
-             nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSE));
-             mDescriptor->mOutput = nullptr;
+                 nsCacheServiceAutoLock lock(LOCK_TELEM(
+                                             NSOUTPUTSTREAMWRAPPER_DESTRUCTOR));
+                 desc.swap(mDescriptor);
+                 if (desc) {
+                     desc->mOutputWrapper = nullptr;
+                 }
+                 mOutput = nullptr;
              }
-             NS_RELEASE(mDescriptor);
          }
 
      private:
          nsresult LazyInit();
          nsresult EnsureInit() { return mInitialized ? NS_OK : LazyInit(); }
          nsresult OnWrite(uint32_t count);
+         nsresult Write_Locked(const char * buf,
+                               uint32_t count,
+                               uint32_t * result);
+         nsresult Close_Locked();
+         void CloseInternal();
      };
-     friend class nsOutputStreamWrapper;
+
 
      class nsCompressOutputStreamWrapper : public nsOutputStreamWrapper {
      private:
          unsigned char* mWriteBuffer;
          uint32_t mWriteBufferLen;
          z_stream mZstream;
          bool mStreamInitialized;
+         bool mStreamEnded;
          uint32_t mUncompressedCount;
      public:
          NS_DECL_ISUPPORTS
 
          nsCompressOutputStreamWrapper(nsCacheEntryDescriptor * desc, 
                                        uint32_t off)
           : nsOutputStreamWrapper(desc, off)
           , mWriteBuffer(0)
           , mWriteBufferLen(0)
           , mStreamInitialized(false)
+          , mStreamEnded(false)
           , mUncompressedCount(0)
          {
          }
          virtual ~nsCompressOutputStreamWrapper()
          { 
              Close();
          }
          NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t * result);
@@ -217,16 +237,18 @@ private:
      };
 
  private:
      /**
       * nsCacheEntryDescriptor data members
       */
      nsCacheEntry          * mCacheEntry; // we are a child of the entry
      nsCacheAccessMode       mAccessGranted;
-     nsIOutputStream       * mOutput;
+     nsVoidArray             mInputWrappers;
+     nsOutputStreamWrapper * mOutputWrapper;
      mozilla::Mutex          mLock;
      bool                    mAsyncDoomPending;
      bool                    mDoomedOnClose;
+     bool                    mClosingDescriptor;
 };
 
 
 #endif // _nsCacheEntryDescriptor_h_
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -1192,40 +1192,49 @@ nsCacheService::ShutdownCustomCacheDevic
 {
     aDevice->Shutdown();
     return PL_DHASH_REMOVE;
 }
 
 void
 nsCacheService::Shutdown()
 {
-    // Thie method must be called on the main thread because mCacheIOThread must
+    // This method must be called on the main thread because mCacheIOThread must
     // only be modified on the main thread.
     if (!NS_IsMainThread()) {
         NS_RUNTIMEABORT("nsCacheService::Shutdown called off the main thread");
     }
 
     nsCOMPtr<nsIThread> cacheIOThread;
     Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
 
     bool shouldSanitize = false;
     nsCOMPtr<nsIFile> parentDir;
 
     {
-    nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
-    NS_ASSERTION(mInitialized, 
-                 "can't shutdown nsCacheService unless it has been initialized.");
-
-    if (mInitialized) {
+        nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+        NS_ASSERTION(mInitialized,
+            "can't shutdown nsCacheService unless it has been initialized.");
+        if (!mInitialized)
+            return;
+
+        mClearingEntries = true;
+        DoomActiveEntries(nullptr);
+    }
+
+    CloseAllStreams();
+
+    {
+        nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+        NS_ASSERTION(mInitialized, "Bad state");
 
         mInitialized = false;
 
         // Clear entries
         ClearDoomList();
-        ClearActiveEntries();
 
         if (mSmartSizeTimer) {
             mSmartSizeTimer->Cancel();
             mSmartSizeTimer = nullptr;
         }
 
         // Make sure to wait for any pending cache-operations before
         // proceeding with destructive actions (bug #620660)
@@ -1256,19 +1265,19 @@ nsCacheService::Shutdown()
         NS_IF_RELEASE(mOfflineDevice);
 
         mCustomOfflineDevices.Enumerate(&nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
 
 #ifdef PR_LOGGING
         LogCacheStatistics();
 #endif
 
+        mClearingEntries = false;
         mCacheIOThread.swap(cacheIOThread);
     }
-    } // lock
 
     if (cacheIOThread)
         cacheIOThread->Shutdown();
 
     if (shouldSanitize) {
         nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
         if (NS_SUCCEEDED(rv)) {
             bool exists;
@@ -2331,20 +2340,25 @@ void
 nsCacheService::OnProfileShutdown(bool cleanse)
 {
     if (!gService)  return;
     if (!gService->mInitialized) {
         // The cache service has been shut down, but someone is still holding
         // a reference to it. Ignore this call.
         return;
     }
+    {
+        nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+        gService->mClearingEntries = true;
+        gService->DoomActiveEntries(nullptr);
+    }
+
+    gService->CloseAllStreams();
+
     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
-    gService->mClearingEntries = true;
-
-    gService->DoomActiveEntries(nullptr);
     gService->ClearDoomList();
 
     // Make sure to wait for any pending cache-operations before
     // proceeding with destructive actions (bug #620660)
     (void) SyncWithCacheIOThread();
 
     if (gService->mDiskDevice && gService->mEnableDiskDevice) {
         if (cleanse)
@@ -2529,23 +2543,29 @@ nsCacheService::SetMemoryCache()
 
 /******************************************************************************
  * static methods for nsCacheEntryDescriptor
  *****************************************************************************/
 void
 nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
 {
     // ask entry to remove descriptor
-    nsCacheEntry * entry       = descriptor->CacheEntry();
-    bool           stillActive = entry->RemoveDescriptor(descriptor);
+    nsCacheEntry * entry = descriptor->CacheEntry();
+    bool doomEntry;
+    bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
 
     if (!entry->IsValid()) {
         gService->ProcessPendingRequests(entry);
     }
 
+    if (doomEntry) {
+        gService->DoomEntry_Internal(entry, true);
+        return;
+    }
+
     if (!stillActive) {
         gService->DeactivateEntry(entry);
     }
 }
 
 
 nsresult        
 nsCacheService::GetFileForEntry(nsCacheEntry *         entry,
@@ -2839,77 +2859,37 @@ nsCacheService::ProcessPendingRequests(n
         }
         if (newWriter)  break;  // process remaining requests after validation
         request = nextRequest;
     }
 
     return NS_OK;
 }
 
-
-void
-nsCacheService::ClearPendingRequests(nsCacheEntry * entry)
-{
-    nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
-    
-    while (request != &entry->mRequestQ) {
-        nsCacheRequest * next = (nsCacheRequest *)PR_NEXT_LINK(request);
-
-        // XXX we're just dropping these on the floor for now...definitely wrong.
-        PR_REMOVE_AND_INIT_LINK(request);
-        delete request;
-        request = next;
-    }
-}
-
 bool
 nsCacheService::IsDoomListEmpty()
 {
     nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
     return &mDoomedEntries == entry;
 }
 
 void
 nsCacheService::ClearDoomList()
 {
     nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
 
     while (entry != &mDoomedEntries) {
         nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
-        
-         entry->DetachDescriptors();
-         DeactivateEntry(entry);
-         entry = next;
-    }        
+
+        entry->DetachDescriptors();
+        DeactivateEntry(entry);
+        entry = next;
+    }
 }
 
-
-void
-nsCacheService::ClearActiveEntries()
-{
-    nsVoidArray entries;
-
-    // We can't detach descriptors while enumerating hash table since calling
-    // entry->DetachDescriptors() could involve dooming the entry which tries
-    // to remove the entry from the hash table.
-    mActiveEntries.VisitEntries(GetActiveEntries, &entries);
-
-    for (int32_t i = 0 ; i < entries.Count() ; i++) {
-        nsCacheEntry * entry = static_cast<nsCacheEntry *>(entries.ElementAt(i));
-        NS_ASSERTION(entry, "### active entry = nullptr!");
-        // only called from Shutdown() so we don't worry about pending requests
-        gService->ClearPendingRequests(entry);
-        entry->DetachDescriptors();
-        gService->DeactivateEntry(entry);
-    }
-
-    mActiveEntries.Shutdown();
-}
-
-
 PLDHashOperator
 nsCacheService::GetActiveEntries(PLDHashTable *    table,
                                  PLDHashEntryHdr * hdr,
                                  uint32_t          number,
                                  void *            arg)
 {
     static_cast<nsVoidArray *>(arg)->AppendElement(
         ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry);
@@ -2952,16 +2932,77 @@ nsCacheService::RemoveActiveEntry(PLDHas
     args->mActiveArray->AppendElement(entry);
 
     // entry is being removed from the active entry list
     entry->MarkInactive();
     return PL_DHASH_REMOVE; // and continue enumerating
 }
 
 
+void
+nsCacheService::CloseAllStreams()
+{
+    nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
+    nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
+
+    {
+        nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
+
+        nsVoidArray entries;
+
+#if DEBUG
+        // make sure there is no active entry
+        mActiveEntries.VisitEntries(GetActiveEntries, &entries);
+        NS_ASSERTION(entries.Count() == 0, "Bad state");
+#endif
+
+        // Get doomed entries
+        nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+        while (entry != &mDoomedEntries) {
+            nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+            entries.AppendElement(entry);
+            entry = next;
+        }
+
+        // Iterate through all entries and collect input and output streams
+        for (int32_t i = 0 ; i < entries.Count() ; i++) {
+            entry = static_cast<nsCacheEntry *>(entries.ElementAt(i));
+
+            nsTArray<nsRefPtr<nsCacheEntryDescriptor> > descs;
+            entry->GetDescriptors(descs);
+
+            for (uint32_t j = 0 ; j < descs.Length() ; j++) {
+                if (descs[j]->mOutputWrapper)
+                    outputs.AppendElement(descs[j]->mOutputWrapper);
+
+                for (int32_t k = 0 ; k < descs[j]->mInputWrappers.Count() ; k++)
+                    inputs.AppendElement(static_cast<
+                        nsCacheEntryDescriptor::nsInputStreamWrapper *>(
+                        descs[j]->mInputWrappers[k]));
+            }
+        }
+    }
+
+    uint32_t i;
+    for (i = 0 ; i < inputs.Length() ; i++)
+        inputs[i]->Close();
+
+    for (i = 0 ; i < outputs.Length() ; i++)
+        outputs[i]->Close();
+}
+
+
+bool
+nsCacheService::GetClearingEntries()
+{
+    AssertOwnsLock();
+    return gService->mClearingEntries;
+}
+
+
 #if defined(PR_LOGGING)
 void
 nsCacheService::LogCacheStatistics()
 {
     uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
         ((double)(mCacheHits + mCacheMisses))) * 100);
     CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
     CACHE_LOG_ALWAYS(("    TotalEntries   = %d\n", mTotalEntries));
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -116,16 +116,18 @@ public:
     static nsresult  OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize);
 
     static nsresult  SetCacheElement(nsCacheEntry * entry, nsISupports * element);
 
     static nsresult  ValidateEntry(nsCacheEntry * entry);
 
     static int32_t   CacheCompressionLevel();
 
+    static bool      GetClearingEntries();
+
     /**
      * Methods called by any cache classes
      */
 
     static
     nsCacheService * GlobalInstance()   { return gService; }
 
     static int64_t   MemoryDeviceSize();
@@ -279,20 +281,19 @@ private:
     void             DeactivateEntry(nsCacheEntry * entry);
 
     nsresult         ProcessRequest(nsCacheRequest *           request,
                                     bool                       calledFromOpenCacheEntry,
                                     nsICacheEntryDescriptor ** result);
 
     nsresult         ProcessPendingRequests(nsCacheEntry * entry);
 
-    void             ClearPendingRequests(nsCacheEntry * entry);
     void             ClearDoomList(void);
-    void             ClearActiveEntries(void);
     void             DoomActiveEntries(DoomCheckFn check);
+    void             CloseAllStreams();
 
     static
     PLDHashOperator  GetActiveEntries(PLDHashTable *    table,
                                       PLDHashEntryHdr * hdr,
                                       uint32_t          number,
                                       void *            arg);
     static
     PLDHashOperator  RemoveActiveEntry(PLDHashTable *    table,
--- a/netwerk/cache/nsDiskCacheStreams.cpp
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -6,17 +6,16 @@
 
 
 #include "nsCache.h"
 #include "nsDiskCache.h"
 #include "nsDiskCacheDevice.h"
 #include "nsDiskCacheStreams.h"
 #include "nsCacheService.h"
 #include "mozilla/FileUtils.h"
-#include "nsIDiskCacheStreamInternal.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 
 
 
 // Assumptions:
 //      - cache descriptors live for life of streams
@@ -179,37 +178,34 @@ nsDiskCacheInputStream::IsNonBlocking(bo
     return NS_OK;
 }
 
 
 /******************************************************************************
  *  nsDiskCacheOutputStream
  *****************************************************************************/
 class nsDiskCacheOutputStream : public nsIOutputStream
-                              , public nsIDiskCacheStreamInternal
 {
 public:
     nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
     virtual ~nsDiskCacheOutputStream();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOUTPUTSTREAM
-    NS_DECL_NSIDISKCACHESTREAMINTERNAL
 
     void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
 
 private:
     nsDiskCacheStreamIO *           mStreamIO;  // backpointer to parent
     bool                            mClosed;
 };
 
 
-NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheOutputStream,
-                              nsIOutputStream,
-                              nsIDiskCacheStreamInternal)
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheOutputStream,
+                              nsIOutputStream)
 
 nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
     : mStreamIO(parent)
     , mClosed(false)
 {
     NS_ADDREF(mStreamIO);
 }
 
@@ -240,39 +236,16 @@ nsDiskCacheOutputStream::Close()
         id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
 
     mozilla::Telemetry::AccumulateTimeDelta(id, start);
 
     return rv;
 }
 
 NS_IMETHODIMP
-nsDiskCacheOutputStream::CloseInternal()
-{
-    nsresult rv = NS_OK;
-    mozilla::TimeStamp start = mozilla::TimeStamp::Now();
-
-    if (!mClosed) {
-        mClosed = true;
-        // tell parent streamIO we are closing
-        rv = mStreamIO->CloseOutputStreamInternal(this);
-    }
-
-    mozilla::Telemetry::ID id;
-    if (NS_IsMainThread())
-        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
-    else
-        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
-
-    mozilla::Telemetry::AccumulateTimeDelta(id, start);
-
-    return rv;
-}
-
-NS_IMETHODIMP
 nsDiskCacheOutputStream::Flush()
 {
     if (mClosed)  return NS_BASE_STREAM_CLOSED;
     // yeah, yeah, well get to it...eventually...
     return NS_OK;
 }
 
 
@@ -460,30 +433,24 @@ nsDiskCacheStreamIO::ClearBinding()
     mBinding = nullptr;
     return rv;
 }
 
 nsresult
 nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream *  outputStream)
 {
     nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); // grab service lock
-    return CloseOutputStreamInternal(outputStream);
-}
 
-nsresult
-nsDiskCacheStreamIO::CloseOutputStreamInternal(
-    nsDiskCacheOutputStream * outputStream)
-{
     nsresult   rv;
 
     if (outputStream != mOutStream) {
         NS_WARNING("mismatched output streams");
         return NS_ERROR_UNEXPECTED;
     }
-    
+
     // output stream is closing
     if (!mBinding) {    // if we're severed, just clear member variables
         NS_ASSERTION(!mBufDirty, "oops");
         mOutStream = nullptr;
         outputStream->ReleaseStreamIO();
         return NS_ERROR_NOT_AVAILABLE;
     }
 
--- a/netwerk/cache/nsDiskCacheStreams.h
+++ b/netwerk/cache/nsDiskCacheStreams.h
@@ -27,18 +27,17 @@ public:
     virtual ~nsDiskCacheStreamIO();
     
     NS_DECL_ISUPPORTS
 
     nsresult    GetInputStream(uint32_t offset, nsIInputStream ** inputStream);
     nsresult    GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream);
 
     nsresult    CloseOutputStream(nsDiskCacheOutputStream * outputStream);
-    nsresult    CloseOutputStreamInternal(nsDiskCacheOutputStream * outputStream);
-        
+
     nsresult    Write( const char * buffer,
                        uint32_t     count,
                        uint32_t *   bytesWritten);
 
     nsresult    Seek(int32_t whence, int32_t offset);
     nsresult    Tell(uint32_t * position);    
     nsresult    SetEOF();
 
deleted file mode 100644
--- a/netwerk/cache/nsIDiskCacheStreamInternal.idl
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-[scriptable, uuid(61ff88f7-516e-4924-93af-42e7c412d18b)]
-interface nsIDiskCacheStreamInternal : nsISupports
-{
-    /**
-     * We use this method internally to close nsDiskCacheOutputStream under
-     * the cache service lock.
-     */
-    void closeInternal();
-};
--- a/netwerk/test/unit/test_doomentry.js
+++ b/netwerk/test/unit/test_doomentry.js
@@ -133,18 +133,18 @@ function write_entry2(entry, ostream)
 }
 
 function check_doom3(status)
 {
   do_check_eq(status, Cr.NS_OK);
   // entry was doomed but writing should still succeed
   var data = "testdata";
   write_and_check(gOstream, data, data.length);
+  gOstream.close();
   gEntry.close();
-  gOstream.close();
   // dooming the same entry again should fail
   new DoomEntry("testentry", check_doom4);
 }
 
 function check_doom4(status)
 {
   do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE);
   do_test_finished();
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -995,28 +995,46 @@
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSPROCESSREQUESTEVENT_RUN"
   },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSOUTPUTSTREAMWRAPPER_LAZYINIT": {
     "kind": "exponential",
     "high": "10 * 1000",
     "n_buckets": 50,
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSOUTPUTSTREAMWRAPPER_LAZYINIT"
   },
-  "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSOUTPUTSTREAMWRAPPER_CLOSE": {
+  "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL": {
     "kind": "exponential",
     "high": "10 * 1000",
     "n_buckets": 50,
-    "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSOUTPUTSTREAMWRAPPER_CLOSE"
+    "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL"
+  },
+  "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSOUTPUTSTREAMWRAPPER_DESTRUCTOR": {
+    "kind": "exponential",
+    "high": "10 * 1000",
+    "n_buckets": 50,
+    "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSOUTPUTSTREAMWRAPPER_DESTRUCTOR"
   },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSINPUTSTREAMWRAPPER_LAZYINIT": {
     "kind": "exponential",
     "high": "10 * 1000",
     "n_buckets": 50,
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSINPUTSTREAMWRAPPER_LAZYINIT"
   },
+  "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSINPUTSTREAMWRAPPER_CLOSEINTERNAL": {
+    "kind": "exponential",
+    "high": "10 * 1000",
+    "n_buckets": 50,
+    "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSINPUTSTREAMWRAPPER_CLOSEINTERNAL"
+  },
+  "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSINPUTSTREAMWRAPPER_DESTRUCTOR": {
+    "kind": "exponential",
+    "high": "10 * 1000",
+    "n_buckets": 50,
+    "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSINPUTSTREAMWRAPPER_DESTRUCTOR"
+  },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSEVICTDISKCACHEENTRIESEVENT_RUN": {
     "kind": "exponential",
     "high": "10 * 1000",
     "n_buckets": 50,
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSEVICTDISKCACHEENTRIESEVENT_RUN"
   },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSDOOMEVENT_RUN": {
     "kind": "exponential",
@@ -1145,16 +1163,22 @@
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSCACHESERVICE_EVICTENTRIESFORCLIENT"
   },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSCACHESERVICE_DISKDEVICEHEAPSIZE": {
     "kind": "exponential",
     "high": "10 * 1000",
     "n_buckets": 50,
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSCACHESERVICE_DISKDEVICEHEAPSIZE"
   },
+  "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSCACHESERVICE_CLOSEALLSTREAMS": {
+    "kind": "exponential",
+    "high": "10 * 1000",
+    "n_buckets": 50,
+    "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSCACHESERVICE_CLOSEALLSTREAMS"
+  },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSCACHEENTRYDESCRIPTOR_DOOM": {
     "kind": "exponential",
     "high": "10 * 1000",
     "n_buckets": 50,
     "description": "Time spent waiting on the cache service lock (ms) on the main thread in NSCACHEENTRYDESCRIPTOR_DOOM"
   },
   "CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_NSCACHEENTRYDESCRIPTOR_SETPREDICTEDDATASIZE": {
     "kind": "exponential",
@@ -1693,28 +1717,16 @@
     "description": "Time spent in nsDiskCacheOutputStream::Close() on non-main thread (ms)"
   },
   "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 10,
     "description": "Time spent in nsDiskCacheOutputStream::Close() on the main thread (ms)"
   },
-  "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL": {
-    "kind": "exponential",
-    "high": "10000",
-    "n_buckets": 10,
-    "description": "Time spent in nsDiskCacheOutputStream::CloseInternal() on non-main thread (ms)"
-  },
-  "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD": {
-    "kind": "exponential",
-    "high": "10000",
-    "n_buckets": 10,
-    "description": "Time spent in nsDiskCacheOutputStream::CloseInternal on the main thread (ms)"
-  },
   "IDLE_NOTIFY_BACK_MS": {
     "kind": "exponential",
     "high": "5000",
     "n_buckets": 10,
     "description": "Time spent checking for and notifying listeners that the user is back (ms)"
   },
   "IDLE_NOTIFY_BACK_LISTENERS": {
     "kind": "linear",