Bug 1143223 - Teach Cache ReadStream not to AddRef() itself in its destructor. r=ehsan
authorBen Kelly <ben@wanderview.com>
Fri, 20 Mar 2015 11:01:57 -0700
changeset 263664 edbc24a15142cf92563c69c0b01f6c2d7672be8f
parent 263663 2fd02f191756702740331b7ca94cece8e1c976c5
child 263665 2f2c17a6a4fa200014f303deee04137a66adc3ed
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1143223
milestone39.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 1143223 - Teach Cache ReadStream not to AddRef() itself in its destructor. r=ehsan
dom/cache/CacheStreamControlChild.cpp
dom/cache/CacheStreamControlChild.h
dom/cache/CacheStreamControlParent.cpp
dom/cache/CacheStreamControlParent.h
dom/cache/ReadStream.cpp
dom/cache/ReadStream.h
dom/cache/StreamControl.cpp
dom/cache/StreamControl.h
dom/cache/moz.build
--- a/dom/cache/CacheStreamControlChild.cpp
+++ b/dom/cache/CacheStreamControlChild.cpp
@@ -4,23 +4,32 @@
  * 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 "mozilla/dom/cache/CacheStreamControlChild.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
 #include "mozilla/dom/cache/ReadStream.h"
+#include "mozilla/ipc/FileDescriptorSetChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PFileDescriptorSetChild.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
+using mozilla::ipc::FileDescriptor;
+using mozilla::ipc::FileDescriptorSetChild;
+using mozilla::ipc::OptionalFileDescriptorSet;
+using mozilla::ipc::PFileDescriptorSetChild;
+
 // declared in ActorUtils.h
 PCacheStreamControlChild*
 AllocPCacheStreamControlChild()
 {
   return new CacheStreamControlChild();
 }
 
 // declared in ActorUtils.h
@@ -38,40 +47,16 @@ CacheStreamControlChild::CacheStreamCont
 
 CacheStreamControlChild::~CacheStreamControlChild()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
   MOZ_COUNT_DTOR(cache::CacheStreamControlChild);
 }
 
 void
-CacheStreamControlChild::AddListener(ReadStream* aListener)
-{
-  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
-  MOZ_ASSERT(aListener);
-  MOZ_ASSERT(!mListeners.Contains(aListener));
-  mListeners.AppendElement(aListener);
-}
-
-void
-CacheStreamControlChild::RemoveListener(ReadStream* aListener)
-{
-  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
-  MOZ_ASSERT(aListener);
-  MOZ_ALWAYS_TRUE(mListeners.RemoveElement(aListener));
-}
-
-void
-CacheStreamControlChild::NoteClosed(const nsID& aId)
-{
-  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
-  unused << SendNoteClosed(aId);
-}
-
-void
 CacheStreamControlChild::StartDestroy()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
   // This can get called twice under some circumstances.  For example, if the
   // actor is added to a Feature that has already been notified and the Cache
   // actor has no mListener.
   if (mDestroyStarted) {
     return;
@@ -79,59 +64,96 @@ CacheStreamControlChild::StartDestroy()
   mDestroyStarted = true;
 
   // Begin shutting down all streams.  This is the same as if the parent had
   // asked us to shutdown.  So simulate the CloseAll IPC message.
   RecvCloseAll();
 }
 
 void
+CacheStreamControlChild::SerializeControl(PCacheReadStream* aReadStreamOut)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  aReadStreamOut->controlParent() = nullptr;
+  aReadStreamOut->controlChild() = this;
+}
+
+void
+CacheStreamControlChild::SerializeFds(PCacheReadStream* aReadStreamOut,
+                                      const nsTArray<FileDescriptor>& aFds)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  PFileDescriptorSetChild* fdSet = nullptr;
+  if (!aFds.IsEmpty()) {
+    fdSet = Manager()->SendPFileDescriptorSetConstructor(aFds[0]);
+    for (uint32_t i = 1; i < aFds.Length(); ++i) {
+      unused << fdSet->SendAddFileDescriptor(aFds[i]);
+    }
+  }
+
+  if (fdSet) {
+    aReadStreamOut->fds() = fdSet;
+  } else {
+    aReadStreamOut->fds() = void_t();
+  }
+}
+
+void
+CacheStreamControlChild::DeserializeFds(const PCacheReadStream& aReadStream,
+                                        nsTArray<FileDescriptor>& aFdsOut)
+{
+  if (aReadStream.fds().type() !=
+      OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
+    return;
+  }
+
+  auto fdSetActor = static_cast<FileDescriptorSetChild*>(
+    aReadStream.fds().get_PFileDescriptorSetChild());
+  MOZ_ASSERT(fdSetActor);
+
+  fdSetActor->ForgetFileDescriptors(aFdsOut);
+  MOZ_ASSERT(!aFdsOut.IsEmpty());
+
+  unused << fdSetActor->Send__delete__(fdSetActor);
+}
+
+void
+CacheStreamControlChild::NoteClosedAfterForget(const nsID& aId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  unused << SendNoteClosed(aId);
+}
+
+#ifdef DEBUG
+void
+CacheStreamControlChild::AssertOwningThread()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+}
+#endif
+
+void
 CacheStreamControlChild::ActorDestroy(ActorDestroyReason aReason)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
-  // Note, we cannot trigger IPC traffic here.  So use
-  // CloseStreamWithoutReporting().
-  ReadStreamList::ForwardIterator iter(mListeners);
-  while (iter.HasMore()) {
-    nsRefPtr<ReadStream> stream = iter.GetNext();
-    stream->CloseStreamWithoutReporting();
-  }
-  mListeners.Clear();
-
+  CloseAllReadStreamsWithoutReporting();
   RemoveFeature();
 }
 
 bool
 CacheStreamControlChild::RecvClose(const nsID& aId)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
-  DebugOnly<uint32_t> closedCount = 0;
-
-  ReadStreamList::ForwardIterator iter(mListeners);
-  while (iter.HasMore()) {
-    nsRefPtr<ReadStream> stream = iter.GetNext();
-    // note, multiple streams may exist for same ID
-    if (stream->MatchId(aId)) {
-      stream->CloseStream();
-      closedCount += 1;
-    }
-  }
-
-  MOZ_ASSERT(closedCount > 0);
-
+  CloseReadStreams(aId);
   return true;
 }
 
 bool
 CacheStreamControlChild::RecvCloseAll()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
-  ReadStreamList::ForwardIterator iter(mListeners);
-  while (iter.HasMore()) {
-    nsRefPtr<ReadStream> stream = iter.GetNext();
-    stream->CloseStream();
-  }
+  CloseAllReadStreams();
   return true;
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheStreamControlChild.h
+++ b/dom/cache/CacheStreamControlChild.h
@@ -4,48 +4,62 @@
  * 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/. */
 
 #ifndef mozilla_dom_cache_CacheStreamControlChild_h
 #define mozilla_dom_cache_CacheStreamControlChild_h
 
 #include "mozilla/dom/cache/ActorChild.h"
 #include "mozilla/dom/cache/PCacheStreamControlChild.h"
+#include "mozilla/dom/cache/StreamControl.h"
 #include "nsTObserverArray.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class ReadStream;
 
 class CacheStreamControlChild MOZ_FINAL : public PCacheStreamControlChild
+                                        , public StreamControl
                                         , public ActorChild
 {
 public:
   CacheStreamControlChild();
   ~CacheStreamControlChild();
 
-  void AddListener(ReadStream* aListener);
-  void RemoveListener(ReadStream* aListener);
-
-  void NoteClosed(const nsID& aId);
-
   // ActorChild methods
   virtual void StartDestroy() MOZ_OVERRIDE;
 
+  // StreamControl methods
+  virtual void
+  SerializeControl(PCacheReadStream* aReadStreamOut) MOZ_OVERRIDE;
+
+  virtual void
+  SerializeFds(PCacheReadStream* aReadStreamOut,
+               const nsTArray<mozilla::ipc::FileDescriptor>& aFds) MOZ_OVERRIDE;
+
+  virtual void
+  DeserializeFds(const PCacheReadStream& aReadStream,
+                 nsTArray<mozilla::ipc::FileDescriptor>& aFdsOut) MOZ_OVERRIDE;
+
 private:
+  virtual void
+  NoteClosedAfterForget(const nsID& aId) MOZ_OVERRIDE;
+
+#ifdef DEBUG
+  virtual void
+  AssertOwningThread() MOZ_OVERRIDE;
+#endif
+
   // PCacheStreamControlChild methods
   virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
   virtual bool RecvClose(const nsID& aId) MOZ_OVERRIDE;
   virtual bool RecvCloseAll() MOZ_OVERRIDE;
 
-  typedef nsTObserverArray<ReadStream*> ReadStreamList;
-  ReadStreamList mListeners;
-
   bool mDestroyStarted;
 
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheStreamControlParent.cpp
+++ b/dom/cache/CacheStreamControlParent.cpp
@@ -3,24 +3,33 @@
 /* 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 "mozilla/dom/cache/CacheStreamControlParent.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/unused.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
 #include "mozilla/dom/cache/ReadStream.h"
 #include "mozilla/dom/cache/StreamList.h"
+#include "mozilla/ipc/FileDescriptorSetParent.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/ipc/PFileDescriptorSetParent.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
+using mozilla::ipc::FileDescriptor;
+using mozilla::ipc::FileDescriptorSetParent;
+using mozilla::ipc::OptionalFileDescriptorSet;
+using mozilla::ipc::PFileDescriptorSetParent;
+
 // declared in ActorUtils.h
 void
 DeallocPCacheStreamControlParent(PCacheStreamControlParent* aActor)
 {
   delete aActor;
 }
 
 CacheStreamControlParent::CacheStreamControlParent()
@@ -31,43 +40,85 @@ CacheStreamControlParent::CacheStreamCon
 CacheStreamControlParent::~CacheStreamControlParent()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
   MOZ_ASSERT(!mStreamList);
   MOZ_COUNT_DTOR(cache::CacheStreamControlParent);
 }
 
 void
-CacheStreamControlParent::AddListener(ReadStream* aListener)
+CacheStreamControlParent::SerializeControl(PCacheReadStream* aReadStreamOut)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  aReadStreamOut->controlChild() = nullptr;
+  aReadStreamOut->controlParent() = this;
+}
+
+void
+CacheStreamControlParent::SerializeFds(PCacheReadStream* aReadStreamOut,
+                                       const nsTArray<FileDescriptor>& aFds)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
-  MOZ_ASSERT(aListener);
-  MOZ_ASSERT(!mListeners.Contains(aListener));
-  mListeners.AppendElement(aListener);
+  PFileDescriptorSetParent* fdSet = nullptr;
+  if (!aFds.IsEmpty()) {
+    fdSet = Manager()->SendPFileDescriptorSetConstructor(aFds[0]);
+    for (uint32_t i = 1; i < aFds.Length(); ++i) {
+      unused << fdSet->SendAddFileDescriptor(aFds[i]);
+    }
+  }
+
+  if (fdSet) {
+    aReadStreamOut->fds() = fdSet;
+  } else {
+    aReadStreamOut->fds() = void_t();
+  }
 }
 
 void
-CacheStreamControlParent::RemoveListener(ReadStream* aListener)
+CacheStreamControlParent::DeserializeFds(const PCacheReadStream& aReadStream,
+                                         nsTArray<FileDescriptor>& aFdsOut)
+{
+  if (aReadStream.fds().type() !=
+      OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
+    return;
+  }
+
+  FileDescriptorSetParent* fdSetActor =
+    static_cast<FileDescriptorSetParent*>(aReadStream.fds().get_PFileDescriptorSetParent());
+  MOZ_ASSERT(fdSetActor);
+
+  fdSetActor->ForgetFileDescriptors(aFdsOut);
+  MOZ_ASSERT(!aFdsOut.IsEmpty());
+
+  if (!fdSetActor->Send__delete__(fdSetActor)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to delete fd set actor.");
+  }
+}
+
+void
+CacheStreamControlParent::NoteClosedAfterForget(const nsID& aId)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
-  MOZ_ASSERT(aListener);
-  DebugOnly<bool> removed = mListeners.RemoveElement(aListener);
-  MOZ_ASSERT(removed);
+  RecvNoteClosed(aId);
 }
 
+#ifdef DEBUG
+void
+CacheStreamControlParent::AssertOwningThread()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+}
+#endif
+
 void
 CacheStreamControlParent::ActorDestroy(ActorDestroyReason aReason)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
-  MOZ_ASSERT(mStreamList);
-  ReadStreamList::ForwardIterator iter(mListeners);
-  while (iter.HasMore()) {
-    nsRefPtr<ReadStream> stream = iter.GetNext();
-    stream->CloseStreamWithoutReporting();
-  }
+  CloseAllReadStreamsWithoutReporting();
   mStreamList->RemoveStreamControl(this);
   mStreamList->NoteClosedAll();
   mStreamList = nullptr;
 }
 
 bool
 CacheStreamControlParent::RecvNoteClosed(const nsID& aId)
 {
@@ -111,37 +162,21 @@ CacheStreamControlParent::Shutdown()
     return;
   }
 }
 
 void
 CacheStreamControlParent::NotifyClose(const nsID& aId)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
-  DebugOnly<uint32_t> closedCount = 0;
-
-  ReadStreamList::ForwardIterator iter(mListeners);
-  while (iter.HasMore()) {
-    nsRefPtr<ReadStream> stream = iter.GetNext();
-    // note, multiple streams may exist for same ID
-    if (stream->MatchId(aId)) {
-      stream->CloseStream();
-      closedCount += 1;
-    }
-  }
-
-  MOZ_ASSERT(closedCount > 0);
+  CloseReadStreams(aId);
 }
 
 void
 CacheStreamControlParent::NotifyCloseAll()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
-  ReadStreamList::ForwardIterator iter(mListeners);
-  while (iter.HasMore()) {
-    nsRefPtr<ReadStream> stream = iter.GetNext();
-    stream->CloseStream();
-  }
+  CloseAllReadStreams();
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheStreamControlParent.h
+++ b/dom/cache/CacheStreamControlParent.h
@@ -3,55 +3,71 @@
 /* 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/. */
 
 #ifndef mozilla_dom_cache_CacheStreamControlParent_h
 #define mozilla_dom_cache_CacheStreamControlParent_h
 
 #include "mozilla/dom/cache/PCacheStreamControlParent.h"
+#include "mozilla/dom/cache/StreamControl.h"
 #include "nsTObserverArray.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class ReadStream;
 class StreamList;
 
 class CacheStreamControlParent : public PCacheStreamControlParent
+                               , public StreamControl
 {
 public:
   CacheStreamControlParent();
   ~CacheStreamControlParent();
 
-  void AddListener(ReadStream* aListener);
-  void RemoveListener(ReadStream* aListener);
-
   void SetStreamList(StreamList* aStreamList);
   void Close(const nsID& aId);
   void CloseAll();
   void Shutdown();
 
+  // StreamControl methods
+  virtual void
+  SerializeControl(PCacheReadStream* aReadStreamOut) MOZ_OVERRIDE;
+
+  virtual void
+  SerializeFds(PCacheReadStream* aReadStreamOut,
+               const nsTArray<mozilla::ipc::FileDescriptor>& aFds) MOZ_OVERRIDE;
+
+  virtual void
+  DeserializeFds(const PCacheReadStream& aReadStream,
+                 nsTArray<mozilla::ipc::FileDescriptor>& aFdsOut) MOZ_OVERRIDE;
+
+private:
+  virtual void
+  NoteClosedAfterForget(const nsID& aId) MOZ_OVERRIDE;
+
+#ifdef DEBUG
+  virtual void
+  AssertOwningThread() MOZ_OVERRIDE;
+#endif
+
   // PCacheStreamControlParent methods
   virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
   virtual bool RecvNoteClosed(const nsID& aId) MOZ_OVERRIDE;
 
-private:
   void NotifyClose(const nsID& aId);
   void NotifyCloseAll();
 
   // Cycle with StreamList via a weak-ref to us.  Cleanup occurs when the actor
   // is deleted by the PBackground manager.  ActorDestroy() then calls
   // StreamList::RemoveStreamControl() to clear the weak ref.
   nsRefPtr<StreamList> mStreamList;
 
-  typedef nsTObserverArray<ReadStream*> ReadStreamList;
-  ReadStreamList mListeners;
-
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_CacheStreamControlParent_h
--- a/dom/cache/ReadStream.cpp
+++ b/dom/cache/ReadStream.cpp
@@ -4,295 +4,390 @@
  * 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 "mozilla/dom/cache/ReadStream.h"
 
 #include "mozilla/unused.h"
 #include "mozilla/dom/cache/CacheStreamControlChild.h"
 #include "mozilla/dom/cache/CacheStreamControlParent.h"
-#include "mozilla/dom/cache/PCacheStreamControlChild.h"
-#include "mozilla/dom/cache/PCacheStreamControlParent.h"
 #include "mozilla/dom/cache/PCacheTypes.h"
 #include "mozilla/ipc/FileDescriptor.h"
-#include "mozilla/ipc/FileDescriptorSetChild.h"
-#include "mozilla/ipc/FileDescriptorSetParent.h"
-#include "mozilla/ipc/InputStreamParams.h"
 #include "mozilla/ipc/InputStreamUtils.h"
-#include "mozilla/ipc/PBackgroundChild.h"
-#include "mozilla/ipc/PBackgroundParent.h"
-#include "mozilla/ipc/PFileDescriptorSetChild.h"
-#include "mozilla/ipc/PFileDescriptorSetParent.h"
 #include "mozilla/SnappyUncompressInputStream.h"
 #include "nsIAsyncInputStream.h"
 #include "nsTArray.h"
 
-namespace {
-
-using mozilla::unused;
-using mozilla::void_t;
-using mozilla::dom::cache::CacheStreamControlChild;
-using mozilla::dom::cache::CacheStreamControlParent;
-using mozilla::dom::cache::PCacheReadStream;
-using mozilla::dom::cache::PCacheStreamControlChild;
-using mozilla::dom::cache::PCacheStreamControlParent;
-using mozilla::dom::cache::ReadStream;
-using mozilla::ipc::FileDescriptor;
-using mozilla::ipc::PFileDescriptorSetChild;
-using mozilla::ipc::PFileDescriptorSetParent;
-
-// There are separate concrete implementations of ReadStream for the child
-// and parent processes.  This is unfortunately necessary because the
-// actor types are distinct for these two cases.  Also, the interface for
-// reporting the close event differs slightly for the child and parent
-// StreamControl actors.
-
-// ----------------------------------------------------------------------------
-
-class ReadStreamChild MOZ_FINAL : public ReadStream
-{
-public:
-  ReadStreamChild(PCacheStreamControlChild* aControl, const nsID& aId,
-                  nsIInputStream* aStream)
-    : ReadStream(aId, aStream)
-    , mControl(static_cast<CacheStreamControlChild*>(aControl))
-  {
-    MOZ_ASSERT(mControl);
-    mControl->AddListener(this);
-  }
-
-  virtual ~ReadStreamChild()
-  {
-    NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-    NoteClosed();
-  }
-
-  virtual void NoteClosedOnOwningThread() MOZ_OVERRIDE
-  {
-    NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-    if (mClosed) {
-      return;
-    }
-
-    mClosed = true;
-    mControl->RemoveListener(this);
-    mControl->NoteClosed(mId);
-  }
-
-  virtual void ForgetOnOwningThread() MOZ_OVERRIDE
-  {
-    NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-    if (mClosed) {
-      return;
-    }
-
-    mClosed = true;
-    mControl->RemoveListener(this);
-  }
-
-  virtual void SerializeControl(PCacheReadStream* aReadStreamOut) MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(aReadStreamOut);
-    MOZ_ASSERT(!mClosed);
-    aReadStreamOut->controlParent() = nullptr;
-    aReadStreamOut->controlChild() = mControl;
-  }
-
-  virtual void
-  SerializeFds(PCacheReadStream* aReadStreamOut,
-               const nsTArray<FileDescriptor>& fds) MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(!mClosed);
-    PFileDescriptorSetChild* fdSet = nullptr;
-    if (!fds.IsEmpty()) {
-      fdSet = mControl->Manager()->SendPFileDescriptorSetConstructor(fds[0]);
-      for (uint32_t i = 1; i < fds.Length(); ++i) {
-        unused << fdSet->SendAddFileDescriptor(fds[i]);
-      }
-    }
-
-    if (fdSet) {
-      aReadStreamOut->fds() = fdSet;
-    } else {
-      aReadStreamOut->fds() = void_t();
-    }
-  }
-
-private:
-  CacheStreamControlChild* mControl;
-};
-
-// ----------------------------------------------------------------------------
-
-class ReadStreamParent MOZ_FINAL : public ReadStream
-{
-public:
-  ReadStreamParent(PCacheStreamControlParent* aControl, const nsID& aId,
-                  nsIInputStream* aStream)
-    : ReadStream(aId, aStream)
-    , mControl(static_cast<CacheStreamControlParent*>(aControl))
-  {
-    MOZ_ASSERT(mControl);
-    mControl->AddListener(this);
-  }
-
-  virtual ~ReadStreamParent()
-  {
-    NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-    NoteClosed();
-  }
-
-  virtual void NoteClosedOnOwningThread() MOZ_OVERRIDE
-  {
-    NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-    if (mClosed) {
-      return;
-    }
-
-    mClosed = true;
-    mControl->RemoveListener(this);
-    // This can cause mControl to be destructed
-    mControl->RecvNoteClosed(mId);
-    mControl = nullptr;
-  }
-
-  virtual void ForgetOnOwningThread() MOZ_OVERRIDE
-  {
-    NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-    if (mClosed) {
-      return;
-    }
-
-    mClosed = true;
-    // This can cause mControl to be destroyed
-    mControl->RemoveListener(this);
-    mControl = nullptr;
-  }
-
-  virtual void SerializeControl(PCacheReadStream* aReadStreamOut) MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(aReadStreamOut);
-    MOZ_ASSERT(!mClosed);
-    MOZ_ASSERT(mControl);
-    aReadStreamOut->controlChild() = nullptr;
-    aReadStreamOut->controlParent() = mControl;
-  }
-
-  virtual void
-  SerializeFds(PCacheReadStream* aReadStreamOut,
-               const nsTArray<FileDescriptor>& fds) MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(!mClosed);
-    MOZ_ASSERT(mControl);
-    PFileDescriptorSetParent* fdSet = nullptr;
-    if (!fds.IsEmpty()) {
-      fdSet = mControl->Manager()->SendPFileDescriptorSetConstructor(fds[0]);
-      for (uint32_t i = 1; i < fds.Length(); ++i) {
-        unused << fdSet->SendAddFileDescriptor(fds[i]);
-      }
-    }
-
-    if (fdSet) {
-      aReadStreamOut->fds() = fdSet;
-    } else {
-      aReadStreamOut->fds() = void_t();
-    }
-  }
-
-private:
-  CacheStreamControlParent* mControl;
-};
-
-// ----------------------------------------------------------------------------
-
-} // anonymous namespace
-
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::unused;
 using mozilla::ipc::FileDescriptor;
-using mozilla::ipc::FileDescriptorSetChild;
-using mozilla::ipc::FileDescriptorSetParent;
-using mozilla::ipc::InputStreamParams;
-using mozilla::ipc::OptionalFileDescriptorSet;
-using mozilla::ipc::PFileDescriptorSetChild;
+
+// ----------------------------------------------------------------------------
+
+// The inner stream class.  This is where all of the real work is done.  As
+// an invariant Inner::Close() must be called before ~Inner().  This is
+// guaranteed by our outer ReadStream class.
+class ReadStream::Inner MOZ_FINAL : public ReadStream::Controllable
+{
+public:
+  Inner(StreamControl* aControl, const nsID& aId,
+        nsIInputStream* aStream);
+
+  void
+  Serialize(PCacheReadStreamOrVoid* aReadStreamOut);
+
+  void
+  Serialize(PCacheReadStream* aReadStreamOut);
+
+  // ReadStream::Controllable methods
+  virtual void
+  CloseStream() MOZ_OVERRIDE;
+
+  virtual void
+  CloseStreamWithoutReporting() MOZ_OVERRIDE;
+
+  virtual bool
+  MatchId(const nsID& aId) const MOZ_OVERRIDE;
+
+  // Simulate nsIInputStream methods, but we don't actually inherit from it
+  NS_METHOD
+  Close();
+
+  NS_METHOD
+  Available(uint64_t *aNumAvailableOut);
+
+  NS_METHOD
+  Read(char *aBuf, uint32_t aCount, uint32_t *aNumReadOut);
+
+  NS_METHOD
+  ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount,
+               uint32_t *aNumReadOut);
+
+  NS_METHOD
+  IsNonBlocking(bool *aNonBlockingOut);
+
+private:
+  class NoteClosedRunnable;
+  class ForgetRunnable;
+
+  ~Inner();
+
+  void
+  NoteClosed();
+
+  void
+  Forget();
+
+  void
+  NoteClosedOnOwningThread();
+
+  void
+  ForgetOnOwningThread();
+
+  // Weak ref to the stream control actor.  The actor will always call either
+  // CloseStream() or CloseStreamWithoutReporting() before it's destroyed.  The
+  // weak ref is cleared in the resulting NoteClosedOnOwningThread() or
+  // ForgetOnOwningThread() method call.
+  StreamControl* mControl;
+
+  const nsID mId;
+  nsCOMPtr<nsIInputStream> mStream;
+  nsCOMPtr<nsIInputStream> mSnappyStream;
+  nsCOMPtr<nsIThread> mOwningThread;
+
+  enum State
+  {
+    Open,
+    Closed,
+    NumStates
+  };
+  Atomic<State> mState;
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::ReadStream::Inner)
+};
+
+// ----------------------------------------------------------------------------
 
 // Runnable to notify actors that the ReadStream has closed.  This must
 // be done on the thread associated with the PBackground actor.  Must be
 // cancelable to execute on Worker threads (which can occur when the
 // ReadStream is constructed on a child process Worker thread).
-class ReadStream::NoteClosedRunnable MOZ_FINAL : public nsCancelableRunnable
+class ReadStream::Inner::NoteClosedRunnable MOZ_FINAL : public nsCancelableRunnable
 {
 public:
-  explicit NoteClosedRunnable(ReadStream* aStream)
+  explicit NoteClosedRunnable(ReadStream::Inner* aStream)
     : mStream(aStream)
   { }
 
   NS_IMETHOD Run()
   {
     mStream->NoteClosedOnOwningThread();
+    mStream = nullptr;
     return NS_OK;
   }
 
   // Note, we must proceed with the Run() method since our actor will not
   // clean itself up until we note that the stream is closed.
   NS_IMETHOD Cancel()
   {
     Run();
     return NS_OK;
   }
 
 private:
   ~NoteClosedRunnable() { }
 
-  nsRefPtr<ReadStream> mStream;
+  nsRefPtr<ReadStream::Inner> mStream;
 };
 
+// ----------------------------------------------------------------------------
+
 // Runnable to clear actors without reporting that the ReadStream has
 // closed.  Since this can trigger actor destruction, we need to do
 // it on the thread associated with the PBackground actor.  Must be
 // cancelable to execute on Worker threads (which can occur when the
 // ReadStream is constructed on a child process Worker thread).
-class ReadStream::ForgetRunnable MOZ_FINAL : public nsCancelableRunnable
+class ReadStream::Inner::ForgetRunnable MOZ_FINAL : public nsCancelableRunnable
 {
 public:
-  explicit ForgetRunnable(ReadStream* aStream)
+  explicit ForgetRunnable(ReadStream::Inner* aStream)
     : mStream(aStream)
   { }
 
   NS_IMETHOD Run()
   {
     mStream->ForgetOnOwningThread();
+    mStream = nullptr;
     return NS_OK;
   }
 
   // Note, we must proceed with the Run() method so that we properly
   // call RemoveListener on the actor.
   NS_IMETHOD Cancel()
   {
     Run();
     return NS_OK;
   }
 
 private:
   ~ForgetRunnable() { }
 
-  nsRefPtr<ReadStream> mStream;
+  nsRefPtr<ReadStream::Inner> mStream;
 };
 
-NS_IMPL_ISUPPORTS(mozilla::dom::cache::ReadStream, nsIInputStream,
-                                                   ReadStream);
+// ----------------------------------------------------------------------------
+
+ReadStream::Inner::Inner(StreamControl* aControl, const nsID& aId,
+                         nsIInputStream* aStream)
+  : mControl(aControl)
+  , mId(aId)
+  , mStream(aStream)
+  , mSnappyStream(new SnappyUncompressInputStream(aStream))
+  , mOwningThread(NS_GetCurrentThread())
+  , mState(Open)
+{
+  MOZ_ASSERT(mStream);
+  MOZ_ASSERT(mControl);
+  mControl->AddReadStream(this);
+}
+
+void
+ReadStream::Inner::Serialize(PCacheReadStreamOrVoid* aReadStreamOut)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+  MOZ_ASSERT(aReadStreamOut);
+  PCacheReadStream stream;
+  Serialize(&stream);
+  *aReadStreamOut = stream;
+}
+
+void
+ReadStream::Inner::Serialize(PCacheReadStream* aReadStreamOut)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+  MOZ_ASSERT(aReadStreamOut);
+  MOZ_ASSERT(mState == Open);
+  MOZ_ASSERT(mControl);
+
+  aReadStreamOut->id() = mId;
+  mControl->SerializeControl(aReadStreamOut);
+
+  nsAutoTArray<FileDescriptor, 4> fds;
+  SerializeInputStream(mStream, aReadStreamOut->params(), fds);
+
+  mControl->SerializeFds(aReadStreamOut, fds);
+
+  // We're passing ownership across the IPC barrier with the control, so
+  // do not signal that the stream is closed here.
+  Forget();
+}
+
+void
+ReadStream::Inner::CloseStream()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+  Close();
+}
+
+void
+ReadStream::Inner::CloseStreamWithoutReporting()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+  Forget();
+}
+
+bool
+ReadStream::Inner::MatchId(const nsID& aId) const
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+  return mId.Equals(aId);
+}
+
+NS_IMETHODIMP
+ReadStream::Inner::Close()
+{
+  // stream ops can happen on any thread
+  nsresult rv = mStream->Close();
+  NoteClosed();
+  return rv;
+}
+
+NS_IMETHODIMP
+ReadStream::Inner::Available(uint64_t* aNumAvailableOut)
+{
+  // stream ops can happen on any thread
+  nsresult rv = mSnappyStream->Available(aNumAvailableOut);
+
+  if (NS_FAILED(rv)) {
+    Close();
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+ReadStream::Inner::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut)
+{
+  // stream ops can happen on any thread
+  MOZ_ASSERT(aNumReadOut);
+
+  nsresult rv = mSnappyStream->Read(aBuf, aCount, aNumReadOut);
+
+  if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) ||
+      *aNumReadOut == 0) {
+    Close();
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+                                uint32_t aCount, uint32_t* aNumReadOut)
+{
+  // stream ops can happen on any thread
+  MOZ_ASSERT(aNumReadOut);
+
+  nsresult rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount,
+                                            aNumReadOut);
+
+  if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK &&
+                        rv != NS_ERROR_NOT_IMPLEMENTED) || *aNumReadOut == 0) {
+    Close();
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+ReadStream::Inner::IsNonBlocking(bool* aNonBlockingOut)
+{
+  // stream ops can happen on any thread
+  return mSnappyStream->IsNonBlocking(aNonBlockingOut);
+}
+
+ReadStream::Inner::~Inner()
+{
+  // Any thread
+  MOZ_ASSERT(mState == Closed);
+  MOZ_ASSERT(!mControl);
+}
+
+void
+ReadStream::Inner::NoteClosed()
+{
+  // Any thread
+  if (mState == Closed) {
+    return;
+  }
+
+  if (NS_GetCurrentThread() == mOwningThread) {
+    NoteClosedOnOwningThread();
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> runnable = new NoteClosedRunnable(this);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
+}
+
+void
+ReadStream::Inner::Forget()
+{
+  // Any thread
+  if (mState == Closed) {
+    return;
+  }
+
+  if (NS_GetCurrentThread() == mOwningThread) {
+    ForgetOnOwningThread();
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> runnable = new ForgetRunnable(this);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
+}
+
+void
+ReadStream::Inner::NoteClosedOnOwningThread()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+
+  // Mark closed and do nothing if we were already closed
+  if (!mState.compareExchange(Open, Closed)) {
+    return;
+  }
+
+  MOZ_ASSERT(mControl);
+  mControl->NoteClosed(this, mId);
+  mControl = nullptr;
+}
+
+void
+ReadStream::Inner::ForgetOnOwningThread()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
+
+  // Mark closed and do nothing if we were already closed
+  if (!mState.compareExchange(Open, Closed)) {
+    return;
+  }
+
+  MOZ_ASSERT(mControl);
+  mControl->ForgetReadStream(this);
+  mControl = nullptr;
+}
+
+// ----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(cache::ReadStream, nsIInputStream, ReadStream);
 
 // static
 already_AddRefed<ReadStream>
 ReadStream::Create(const PCacheReadStreamOrVoid& aReadStreamOrVoid)
 {
   if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) {
     return nullptr;
   }
@@ -306,233 +401,109 @@ ReadStream::Create(const PCacheReadStrea
 {
   // The parameter may or may not be for a Cache created stream.  The way we
   // tell is by looking at the stream control actor.  If the actor exists,
   // then we know the Cache created it.
   if (!aReadStream.controlChild() && !aReadStream.controlParent()) {
     return nullptr;
   }
 
-  nsAutoTArray<FileDescriptor, 4> fds;
-  if (aReadStream.fds().type() ==
-      OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
-
-    FileDescriptorSetChild* fdSetActor =
-      static_cast<FileDescriptorSetChild*>(aReadStream.fds().get_PFileDescriptorSetChild());
-    MOZ_ASSERT(fdSetActor);
-
-    fdSetActor->ForgetFileDescriptors(fds);
-    MOZ_ASSERT(!fds.IsEmpty());
+  // Control is guaranteed to survive this method as ActorDestroy() cannot
+  // run on this thread until we complete.
+  StreamControl* control;
+  if (aReadStream.controlChild()) {
+    auto actor = static_cast<CacheStreamControlChild*>(aReadStream.controlChild());
+    control = actor;
+  } else {
+    auto actor = static_cast<CacheStreamControlParent*>(aReadStream.controlParent());
+    control = actor;
+  }
+  MOZ_ASSERT(control);
 
-    unused << fdSetActor->Send__delete__(fdSetActor);
-  } else if (aReadStream.fds().type() ==
-      OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
-
-    FileDescriptorSetParent* fdSetActor =
-      static_cast<FileDescriptorSetParent*>(aReadStream.fds().get_PFileDescriptorSetParent());
-    MOZ_ASSERT(fdSetActor);
-
-    fdSetActor->ForgetFileDescriptors(fds);
-    MOZ_ASSERT(!fds.IsEmpty());
-
-    if (!fdSetActor->Send__delete__(fdSetActor)) {
-      // child process is gone, warn and allow actor to clean up normally
-      NS_WARNING("Cache failed to delete fd set actor.");
-    }
-  }
+  nsAutoTArray<FileDescriptor, 4> fds;
+  control->DeserializeFds(aReadStream, fds);
 
   nsCOMPtr<nsIInputStream> stream =
     DeserializeInputStream(aReadStream.params(), fds);
   MOZ_ASSERT(stream);
 
   // Currently we expect all cache read streams to be blocking file streams.
 #ifdef DEBUG
   nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(stream);
   MOZ_ASSERT(!asyncStream);
 #endif
 
-  nsRefPtr<ReadStream> ref;
-
-  if (aReadStream.controlChild()) {
-    ref = new ReadStreamChild(aReadStream.controlChild(), aReadStream.id(),
-                              stream);
-  } else {
-    ref = new ReadStreamParent(aReadStream.controlParent(), aReadStream.id(),
-                               stream);
-  }
-
+  nsRefPtr<Inner> inner = new Inner(control, aReadStream.id(), stream);
+  nsRefPtr<ReadStream> ref = new ReadStream(inner);
   return ref.forget();
 }
 
 // static
 already_AddRefed<ReadStream>
 ReadStream::Create(PCacheStreamControlParent* aControl, const nsID& aId,
                    nsIInputStream* aStream)
 {
-  nsRefPtr<ReadStream> ref = new ReadStreamParent(aControl, aId, aStream);
+  MOZ_ASSERT(aControl);
+  auto actor = static_cast<CacheStreamControlParent*>(aControl);
+  nsRefPtr<Inner> inner = new Inner(actor, aId, aStream);
+  nsRefPtr<ReadStream> ref = new ReadStream(inner);
   return ref.forget();
 }
 
 void
 ReadStream::Serialize(PCacheReadStreamOrVoid* aReadStreamOut)
 {
-  MOZ_ASSERT(aReadStreamOut);
-  PCacheReadStream stream;
-  Serialize(&stream);
-  *aReadStreamOut = stream;
+  mInner->Serialize(aReadStreamOut);
 }
 
 void
 ReadStream::Serialize(PCacheReadStream* aReadStreamOut)
 {
-  MOZ_ASSERT(aReadStreamOut);
-  MOZ_ASSERT(!mClosed);
-
-  aReadStreamOut->id() = mId;
-  SerializeControl(aReadStreamOut);
-
-  nsAutoTArray<FileDescriptor, 4> fds;
-  SerializeInputStream(mStream, aReadStreamOut->params(), fds);
-
-  SerializeFds(aReadStreamOut, fds);
-
-  // We're passing ownership across the IPC barrier with the control, so
-  // do not signal that the stream is closed here.
-  Forget();
+  mInner->Serialize(aReadStreamOut);
 }
 
-void
-ReadStream::CloseStream()
-{
-  Close();
-}
-
-void
-ReadStream::CloseStreamWithoutReporting()
+ReadStream::ReadStream(ReadStream::Inner* aInner)
+  : mInner(aInner)
 {
-  Forget();
-}
-
-bool
-ReadStream::MatchId(const nsID& aId) const
-{
-  return mId.Equals(aId);
-}
-
-ReadStream::ReadStream(const nsID& aId, nsIInputStream* aStream)
-  : mId(aId)
-  , mStream(aStream)
-  , mSnappyStream(new SnappyUncompressInputStream(aStream))
-  , mOwningThread(NS_GetCurrentThread())
-  , mClosed(false)
-{
-  MOZ_ASSERT(mStream);
+  MOZ_ASSERT(mInner);
 }
 
 ReadStream::~ReadStream()
 {
-  NS_ASSERT_OWNINGTHREAD(ReadStream);
-
-  // We cannot directly call NoteClosed() here.  The concrete subclasses
-  // destructors must do this because it takes code paths through virtual
-  // methods.  We don't want to execute these while partially destroyed.
-  MOZ_ASSERT(mClosed);
-}
-
-void
-ReadStream::NoteClosed()
-{
-  if (mClosed) {
-    return;
-  }
-
-  if (NS_GetCurrentThread() == mOwningThread) {
-    NoteClosedOnOwningThread();
-    return;
-  }
-
-  nsCOMPtr<nsIRunnable> runnable = new NoteClosedRunnable(this);
-  nsresult rv = mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch Cache ReadStream NoteClosed() runnable.");
-  }
-}
-
-void
-ReadStream::Forget()
-{
-  if (mClosed) {
-    return;
-  }
-
-  if (NS_GetCurrentThread() == mOwningThread) {
-    ForgetOnOwningThread();
-    return;
-  }
-
-  nsCOMPtr<nsIRunnable> runnable = new ForgetRunnable(this);
-  nsresult rv = mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch Cache ReadStream Forget() runnable.");
-  }
+  // Explicitly close the inner stream so that it does not have to
+  // deal with implicitly closing at destruction time.
+  mInner->Close();
 }
 
 NS_IMETHODIMP
 ReadStream::Close()
 {
-  nsresult rv = mStream->Close();
-  NoteClosed();
-  return rv;
+  return mInner->Close();
 }
 
 NS_IMETHODIMP
 ReadStream::Available(uint64_t* aNumAvailableOut)
 {
-  nsresult rv = mSnappyStream->Available(aNumAvailableOut);
-
-  if (NS_FAILED(rv)) {
-    Close();
-  }
-
-  return rv;
+  return mInner->Available(aNumAvailableOut);
 }
 
 NS_IMETHODIMP
 ReadStream::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut)
 {
-  MOZ_ASSERT(aNumReadOut);
-
-  nsresult rv = mSnappyStream->Read(aBuf, aCount, aNumReadOut);
-
-  if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) ||
-      *aNumReadOut == 0) {
-    Close();
-  }
-
-  return rv;
+  return mInner->Read(aBuf, aCount, aNumReadOut);
 }
 
 NS_IMETHODIMP
 ReadStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
                          uint32_t aCount, uint32_t* aNumReadOut)
 {
-  MOZ_ASSERT(aNumReadOut);
-
-  nsresult rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount,
-                                            aNumReadOut);
-
-  if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK &&
-                        rv != NS_ERROR_NOT_IMPLEMENTED) || *aNumReadOut == 0) {
-    Close();
-  }
-
-  return rv;
+  return mInner->ReadSegments(aWriter, aClosure, aCount, aNumReadOut);
 }
 
 NS_IMETHODIMP
 ReadStream::IsNonBlocking(bool* aNonBlockingOut)
 {
-  return mSnappyStream->IsNonBlocking(aNonBlockingOut);
+  return mInner->IsNonBlocking(aNonBlockingOut);
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/ReadStream.h
+++ b/dom/cache/ReadStream.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_cache_ReadStream_h
 #define mozilla_dom_cache_ReadStream_h
 
 #include "mozilla/ipc/FileDescriptor.h"
 #include "nsCOMPtr.h"
 #include "nsID.h"
 #include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
+#include "nsRefPtr.h"
 #include "nsTArrayForwardDeclare.h"
 
 class nsIThread;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
@@ -24,76 +25,84 @@ class PCacheReadStream;
 class PCacheReadStreamOrVoid;
 class PCacheStreamControlParent;
 
 // IID for the dom::cache::ReadStream interface
 #define NS_DOM_CACHE_READSTREAM_IID \
 {0x8e5da7c9, 0x0940, 0x4f1d, \
   {0x97, 0x25, 0x5c, 0x59, 0x38, 0xdd, 0xb9, 0x9f}}
 
+
 // Custom stream class for Request and Response bodies being read from
 // a Cache.  The main purpose of this class is to report back to the
 // Cache's Manager when the stream is closed.  This allows the Cache to
 // accurately determine when the underlying body file can be deleted,
 // etc.
 //
 // The ReadStream class also provides us with a convenient QI'able
 // interface that we can use to pass additional meta-data with the
 // stream channel.  For example, Cache.put() can detect that the content
 // script is passing a Cache-originated-stream back into the Cache
 // again.  This enables certain optimizations.
-class ReadStream : public nsIInputStream
+class ReadStream MOZ_FINAL : public nsIInputStream
 {
 public:
+  // Interface that lets the StreamControl classes interact with
+  // our private inner stream.
+  class Controllable
+  {
+  public:
+    // Closes the stream, notifies the stream control, and then forgets
+    // the stream control.
+    virtual void
+    CloseStream() = 0;
+
+    // Closes the stream and then forgets the stream control.  Does not
+    // notify.
+    virtual void
+    CloseStreamWithoutReporting() = 0;
+
+    virtual bool
+    MatchId(const nsID& aId) const = 0;
+
+    NS_IMETHOD_(MozExternalRefCountType)
+    AddRef(void) = 0;
+
+    NS_IMETHOD_(MozExternalRefCountType)
+    Release(void) = 0;
+  };
+
   static already_AddRefed<ReadStream>
   Create(const PCacheReadStreamOrVoid& aReadStreamOrVoid);
 
   static already_AddRefed<ReadStream>
   Create(const PCacheReadStream& aReadStream);
 
   static already_AddRefed<ReadStream>
   Create(PCacheStreamControlParent* aControl, const nsID& aId,
          nsIInputStream* aStream);
 
   void Serialize(PCacheReadStreamOrVoid* aReadStreamOut);
   void Serialize(PCacheReadStream* aReadStreamOut);
 
-  // methods called from the child and parent CacheStreamControl actors
-  void CloseStream();
-  void CloseStreamWithoutReporting();
-  bool MatchId(const nsID& aId) const;
+private:
+  class Inner;
 
-protected:
-  class NoteClosedRunnable;
-  class ForgetRunnable;
-
-  ReadStream(const nsID& aId, nsIInputStream* aStream);
-  virtual ~ReadStream();
+  explicit ReadStream(Inner* aInner);
+  ~ReadStream();
 
-  void NoteClosed();
-  void Forget();
-
-  virtual void NoteClosedOnOwningThread() = 0;
-  virtual void ForgetOnOwningThread() = 0;
-  virtual void SerializeControl(PCacheReadStream* aReadStreamOut) = 0;
-
-  virtual void
-  SerializeFds(PCacheReadStream* aReadStreamOut,
-               const nsTArray<mozilla::ipc::FileDescriptor>& fds) = 0;
-
-  const nsID mId;
-  nsCOMPtr<nsIInputStream> mStream;
-  nsCOMPtr<nsIInputStream> mSnappyStream;
-  nsCOMPtr<nsIThread> mOwningThread;
-  bool mClosed;
+  // Hold a strong ref to an inner class that actually implements the
+  // majority of the stream logic.  Before releasing this ref the outer
+  // ReadStream guarantees it will call Close() on the inner stream.
+  // This is essential for the inner stream to avoid dealing with the
+  // implicit close that can happen when a stream is destroyed.
+  nsRefPtr<Inner> mInner;
 
 public:
-
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_CACHE_READSTREAM_IID);
-
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(ReadStream, NS_DOM_CACHE_READSTREAM_IID);
 
 } // namespace cache
 } // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/cache/StreamControl.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/cache/StreamControl.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+void
+StreamControl::AddReadStream(ReadStream::Controllable* aReadStream)
+{
+  AssertOwningThread();
+  MOZ_ASSERT(aReadStream);
+  MOZ_ASSERT(!mReadStreamList.Contains(aReadStream));
+  mReadStreamList.AppendElement(aReadStream);
+}
+
+void
+StreamControl::ForgetReadStream(ReadStream::Controllable* aReadStream)
+{
+  AssertOwningThread();
+  MOZ_ALWAYS_TRUE(mReadStreamList.RemoveElement(aReadStream));
+}
+
+void
+StreamControl::NoteClosed(ReadStream::Controllable* aReadStream,
+                          const nsID& aId)
+{
+  AssertOwningThread();
+  ForgetReadStream(aReadStream);
+  NoteClosedAfterForget(aId);
+}
+
+StreamControl::~StreamControl()
+{
+  // owning thread only, but can't call virtual AssertOwningThread in destructor
+  MOZ_ASSERT(mReadStreamList.IsEmpty());
+}
+
+void
+StreamControl::CloseReadStreams(const nsID& aId)
+{
+  AssertOwningThread();
+  DebugOnly<uint32_t> closedCount = 0;
+
+  ReadStreamList::ForwardIterator iter(mReadStreamList);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream::Controllable> stream = iter.GetNext();
+    if (stream->MatchId(aId)) {
+      stream->CloseStream();
+      closedCount += 1;
+    }
+  }
+
+  MOZ_ASSERT(closedCount > 0);
+}
+
+void
+StreamControl::CloseAllReadStreams()
+{
+  AssertOwningThread();
+
+  ReadStreamList::ForwardIterator iter(mReadStreamList);
+  while (iter.HasMore()) {
+    iter.GetNext()->CloseStream();
+  }
+}
+
+void
+StreamControl::CloseAllReadStreamsWithoutReporting()
+{
+  AssertOwningThread();
+
+  ReadStreamList::ForwardIterator iter(mReadStreamList);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream::Controllable> stream = iter.GetNext();
+    // Note, we cannot trigger IPC traffic here.  So use
+    // CloseStreamWithoutReporting().
+    stream->CloseStreamWithoutReporting();
+  }
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/StreamControl.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_cache_StreamControl_h
+#define mozilla_dom_cache_StreamControl_h
+
+#include "mozilla/dom/cache/ReadStream.h"
+#include "nsRefPtr.h"
+#include "nsTObserverArray.h"
+
+struct nsID;
+
+namespace mozilla {
+namespace ipc {
+ class FileDescriptor;
+}
+namespace dom {
+namespace cache {
+
+class PCacheReadStream;
+
+// Abstract class to help implement the stream control Child and Parent actors.
+// This provides an interface to partly help with serialization of IPC types,
+// but also an implementation for tracking ReadStream objects.
+class StreamControl
+{
+public:
+  // abstract interface that must be implemented by child class
+  virtual void
+  SerializeControl(PCacheReadStream* aReadStreamOut) = 0;
+
+  virtual void
+  SerializeFds(PCacheReadStream* aReadStreamOut,
+               const nsTArray<mozilla::ipc::FileDescriptor>& aFds) = 0;
+
+  virtual void
+  DeserializeFds(const PCacheReadStream& aReadStream,
+                 nsTArray<mozilla::ipc::FileDescriptor>& aFdsOut) = 0;
+
+  // inherited implementation of the ReadStream::Controllable list
+
+  // Begin controlling the given ReadStream.  This causes a strong ref to
+  // be held by the control.  The ReadStream must call NoteClosed() or
+  // ForgetReadStream() to release this ref.
+  void
+  AddReadStream(ReadStream::Controllable* aReadStream);
+
+  // Forget the ReadStream without notifying the actor.
+  void
+  ForgetReadStream(ReadStream::Controllable* aReadStream);
+
+  // Forget the ReadStream and then notify the actor the stream is closed.
+  void
+  NoteClosed(ReadStream::Controllable* aReadStream, const nsID& aId);
+
+protected:
+  ~StreamControl();
+
+  void
+  CloseReadStreams(const nsID& aId);
+
+  void
+  CloseAllReadStreams();
+
+  void
+  CloseAllReadStreamsWithoutReporting();
+
+  // protected parts of the abstract interface
+  virtual void
+  NoteClosedAfterForget(const nsID& aId) = 0;
+
+#ifdef DEBUG
+  virtual void
+  AssertOwningThread() = 0;
+#else
+  void AssertOwningThread() { }
+#endif
+
+private:
+  // Hold strong references to ReadStream object.  When the stream is closed
+  // it should call NoteClosed() or ForgetReadStream() to release this ref.
+  typedef nsTObserverArray<nsRefPtr<ReadStream::Controllable>> ReadStreamList;
+  ReadStreamList mReadStreamList;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_StreamControl_h
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -25,16 +25,17 @@ EXPORTS.mozilla.dom.cache += [
     'FileUtils.h',
     'IPCUtils.h',
     'Manager.h',
     'ManagerId.h',
     'PrincipalVerifier.h',
     'QuotaClient.h',
     'ReadStream.h',
     'SavedTypes.h',
+    'StreamControl.h',
     'StreamList.h',
     'StreamUtils.h',
     'Types.h',
     'TypeUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'Action.cpp',
@@ -54,16 +55,17 @@ UNIFIED_SOURCES += [
     'Feature.cpp',
     'FetchPut.cpp',
     'FileUtils.cpp',
     'Manager.cpp',
     'ManagerId.cpp',
     'PrincipalVerifier.cpp',
     'QuotaClient.cpp',
     'ReadStream.cpp',
+    'StreamControl.cpp',
     'StreamList.cpp',
     'StreamUtils.cpp',
     'TypeUtils.cpp',
 ]
 
 IPDL_SOURCES += [
     'CacheInitData.ipdlh',
     'PCache.ipdl',