Bug 1143223 Teach Cache ReadStream not to AddRef() itself in its destructor. r=ehsan

This commit is contained in:
Ben Kelly 2015-03-20 11:01:57 -07:00
parent 5da1c39c61
commit 63da054263
9 changed files with 743 additions and 492 deletions

View File

@ -9,13 +9,22 @@
#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()
@ -42,30 +51,6 @@ CacheStreamControlChild::~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()
{
@ -83,19 +68,73 @@ CacheStreamControlChild::StartDestroy()
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();
}
@ -103,20 +142,7 @@ 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;
}
@ -124,11 +150,7 @@ bool
CacheStreamControlChild::RecvCloseAll()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
ReadStreamList::ForwardIterator iter(mListeners);
while (iter.HasMore()) {
nsRefPtr<ReadStream> stream = iter.GetNext();
stream->CloseStream();
}
CloseAllReadStreams();
return true;
}

View File

@ -9,6 +9,7 @@
#include "mozilla/dom/cache/ActorChild.h"
#include "mozilla/dom/cache/PCacheStreamControlChild.h"
#include "mozilla/dom/cache/StreamControl.h"
#include "nsTObserverArray.h"
namespace mozilla {
@ -18,29 +19,42 @@ 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

View File

@ -8,14 +8,23 @@
#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)
@ -36,33 +45,75 @@ CacheStreamControlParent::~CacheStreamControlParent()
}
void
CacheStreamControlParent::AddListener(ReadStream* aListener)
CacheStreamControlParent::SerializeControl(PCacheReadStream* aReadStreamOut)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_ASSERT(aListener);
MOZ_ASSERT(!mListeners.Contains(aListener));
mListeners.AppendElement(aListener);
aReadStreamOut->controlChild() = nullptr;
aReadStreamOut->controlParent() = this;
}
void
CacheStreamControlParent::RemoveListener(ReadStream* aListener)
CacheStreamControlParent::SerializeFds(PCacheReadStream* aReadStreamOut,
const nsTArray<FileDescriptor>& aFds)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_ASSERT(aListener);
DebugOnly<bool> removed = mListeners.RemoveElement(aListener);
MOZ_ASSERT(removed);
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::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);
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;
@ -116,30 +167,14 @@ 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

View File

@ -8,6 +8,7 @@
#define mozilla_dom_cache_CacheStreamControlParent_h
#include "mozilla/dom/cache/PCacheStreamControlParent.h"
#include "mozilla/dom/cache/StreamControl.h"
#include "nsTObserverArray.h"
namespace mozilla {
@ -18,24 +19,42 @@ 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();
@ -44,9 +63,6 @@ private:
// StreamList::RemoveStreamControl() to clear the weak ref.
nsRefPtr<StreamList> mStreamList;
typedef nsTObserverArray<ReadStream*> ReadStreamList;
ReadStreamList mListeners;
NS_DECL_OWNINGTHREAD
};

View File

@ -9,234 +9,121 @@
#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;
}
@ -251,24 +138,27 @@ public:
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;
}
@ -283,11 +173,216 @@ public:
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>
@ -311,33 +406,20 @@ ReadStream::Create(const PCacheReadStream& aReadStream)
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());
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.");
}
// 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);
nsAutoTArray<FileDescriptor, 4> fds;
control->DeserializeFds(aReadStream, fds);
nsCOMPtr<nsIInputStream> stream =
DeserializeInputStream(aReadStream.params(), fds);
@ -349,16 +431,8 @@ ReadStream::Create(const PCacheReadStream& aReadStream)
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();
}
@ -367,170 +441,67 @@ 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()
ReadStream::ReadStream(ReadStream::Inner* aInner)
: mInner(aInner)
{
Close();
}
void
ReadStream::CloseStreamWithoutReporting()
{
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

View File

@ -12,6 +12,7 @@
#include "nsID.h"
#include "nsIInputStream.h"
#include "nsISupportsImpl.h"
#include "nsRefPtr.h"
#include "nsTArrayForwardDeclare.h"
class nsIThread;
@ -29,6 +30,7 @@ class PCacheStreamControlParent;
{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
@ -40,9 +42,34 @@ class PCacheStreamControlParent;
// 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);
@ -56,39 +83,21 @@ public:
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;
explicit ReadStream(Inner* aInner);
~ReadStream();
ReadStream(const nsID& aId, nsIInputStream* aStream);
virtual ~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
};

89
dom/cache/StreamControl.cpp vendored Normal file
View File

@ -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

93
dom/cache/StreamControl.h vendored Normal file
View File

@ -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

2
dom/cache/moz.build vendored
View File

@ -30,6 +30,7 @@ EXPORTS.mozilla.dom.cache += [
'QuotaClient.h',
'ReadStream.h',
'SavedTypes.h',
'StreamControl.h',
'StreamList.h',
'StreamUtils.h',
'Types.h',
@ -59,6 +60,7 @@ UNIFIED_SOURCES += [
'PrincipalVerifier.cpp',
'QuotaClient.cpp',
'ReadStream.cpp',
'StreamControl.cpp',
'StreamList.cpp',
'StreamUtils.cpp',
'TypeUtils.cpp',