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 125178 1c74122a6fcfeb556a1cebe979b74b9f85dddef6
parent 125177 3173175efff1a391e065f81ed4de36f896bca592
child 125179 d8be8785e3a7da63748b0e5103026aa618e5aabd
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs808997
milestone20.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 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",