Bug 1110485 P4 Keep Cache Actors alive during async operations. r=baku

This commit is contained in:
Ben Kelly
2015-04-16 12:00:15 -07:00
parent b143e5d19b
commit ccf34d901e
15 changed files with 194 additions and 32 deletions

11
dom/cache/Cache.cpp vendored
View File

@@ -14,7 +14,6 @@
#include "mozilla/dom/CacheBinding.h" #include "mozilla/dom/CacheBinding.h"
#include "mozilla/dom/cache/AutoUtils.h" #include "mozilla/dom/cache/AutoUtils.h"
#include "mozilla/dom/cache/CacheChild.h" #include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheOpChild.h"
#include "mozilla/dom/cache/CachePushStreamChild.h" #include "mozilla/dom/cache/CachePushStreamChild.h"
#include "mozilla/dom/cache/ReadStream.h" #include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/ErrorResult.h" #include "mozilla/ErrorResult.h"
@@ -350,10 +349,7 @@ Cache::CreatePushStream(nsIAsyncInputStream* aStream)
NS_ASSERT_OWNINGTHREAD(Cache); NS_ASSERT_OWNINGTHREAD(Cache);
MOZ_ASSERT(mActor); MOZ_ASSERT(mActor);
MOZ_ASSERT(aStream); MOZ_ASSERT(aStream);
auto actor = mActor->SendPCachePushStreamConstructor( return mActor->CreatePushStream(aStream);
new CachePushStreamChild(mActor->GetFeature(), aStream));
MOZ_ASSERT(actor);
return static_cast<CachePushStreamChild*>(actor);
} }
Cache::~Cache() Cache::~Cache()
@@ -375,10 +371,7 @@ Cache::ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv)
return nullptr; return nullptr;
} }
unused << mActor->SendPCacheOpConstructor( mActor->ExecuteOp(mGlobal, promise, aOpArgs.SendAsOpArgs());
new CacheOpChild(mActor->GetFeature(), mGlobal, this, promise),
aOpArgs.SendAsOpArgs());
return promise.forget(); return promise.forget();
} }

View File

@@ -9,8 +9,8 @@
#include "mozilla/unused.h" #include "mozilla/unused.h"
#include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/cache/ActorUtils.h"
#include "mozilla/dom/cache/Cache.h" #include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/cache/PCacheOpChild.h" #include "mozilla/dom/cache/CacheOpChild.h"
#include "mozilla/dom/cache/PCachePushStreamChild.h" #include "mozilla/dom/cache/CachePushStreamChild.h"
#include "mozilla/dom/cache/StreamUtils.h" #include "mozilla/dom/cache/StreamUtils.h"
namespace mozilla { namespace mozilla {
@@ -33,6 +33,7 @@ DeallocPCacheChild(PCacheChild* aActor)
CacheChild::CacheChild() CacheChild::CacheChild()
: mListener(nullptr) : mListener(nullptr)
, mNumChildActors(0)
{ {
MOZ_COUNT_CTOR(cache::CacheChild); MOZ_COUNT_CTOR(cache::CacheChild);
} }
@@ -42,6 +43,7 @@ CacheChild::~CacheChild()
MOZ_COUNT_DTOR(cache::CacheChild); MOZ_COUNT_DTOR(cache::CacheChild);
NS_ASSERT_OWNINGTHREAD(CacheChild); NS_ASSERT_OWNINGTHREAD(CacheChild);
MOZ_ASSERT(!mListener); MOZ_ASSERT(!mListener);
MOZ_ASSERT(!mNumChildActors);
} }
void void
@@ -61,6 +63,25 @@ CacheChild::ClearListener()
mListener = nullptr; mListener = nullptr;
} }
void
CacheChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
const CacheOpArgs& aArgs)
{
mNumChildActors += 1;
MOZ_ALWAYS_TRUE(SendPCacheOpConstructor(
new CacheOpChild(GetFeature(), aGlobal, aPromise), aArgs));
}
CachePushStreamChild*
CacheChild::CreatePushStream(nsIAsyncInputStream* aStream)
{
mNumChildActors += 1;
auto actor = SendPCachePushStreamConstructor(
new CachePushStreamChild(GetFeature(), aStream));
MOZ_ASSERT(actor);
return static_cast<CachePushStreamChild*>(actor);
}
void void
CacheChild::StartDestroy() CacheChild::StartDestroy()
{ {
@@ -73,13 +94,19 @@ CacheChild::StartDestroy()
return; return;
} }
// TODO: only destroy if there are no ops or push streams still running
listener->DestroyInternal(this); listener->DestroyInternal(this);
// Cache listener should call ClearListener() in DestroyInternal() // Cache listener should call ClearListener() in DestroyInternal()
MOZ_ASSERT(!mListener); MOZ_ASSERT(!mListener);
// If we have outstanding child actors, then don't destroy ourself yet.
// The child actors should be short lived and we should allow them to complete
// if possible. SendTeardown() will be called when the count drops to zero
// in NoteDeletedActor().
if (mNumChildActors) {
return;
}
// Start actor destruction from parent process // Start actor destruction from parent process
unused << SendTeardown(); unused << SendTeardown();
} }
@@ -109,6 +136,7 @@ bool
CacheChild::DeallocPCacheOpChild(PCacheOpChild* aActor) CacheChild::DeallocPCacheOpChild(PCacheOpChild* aActor)
{ {
delete aActor; delete aActor;
NoteDeletedActor();
return true; return true;
} }
@@ -123,9 +151,19 @@ bool
CacheChild::DeallocPCachePushStreamChild(PCachePushStreamChild* aActor) CacheChild::DeallocPCachePushStreamChild(PCachePushStreamChild* aActor)
{ {
delete aActor; delete aActor;
NoteDeletedActor();
return true; return true;
} }
void
CacheChild::NoteDeletedActor()
{
mNumChildActors -= 1;
if (!mNumChildActors && !mListener) {
unused << SendTeardown();
}
}
} // namespace cache } // namespace cache
} // namespace dom } // namespace dom
} // namesapce mozilla } // namesapce mozilla

View File

@@ -10,11 +10,19 @@
#include "mozilla/dom/cache/ActorChild.h" #include "mozilla/dom/cache/ActorChild.h"
#include "mozilla/dom/cache/PCacheChild.h" #include "mozilla/dom/cache/PCacheChild.h"
class nsIAsyncInputStream;
class nsIGlobalObject;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
class Promise;
namespace cache { namespace cache {
class Cache; class Cache;
class CacheOpArgs;
class CachePushStreamChild;
class CacheChild final : public PCacheChild class CacheChild final : public PCacheChild
, public ActorChild , public ActorChild
@@ -30,6 +38,13 @@ public:
// trigger ActorDestroy() if it has not been called yet. // trigger ActorDestroy() if it has not been called yet.
void ClearListener(); void ClearListener();
void
ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
const CacheOpArgs& aArgs);
CachePushStreamChild*
CreatePushStream(nsIAsyncInputStream* aStream);
// ActorChild methods // ActorChild methods
// Synchronously call ActorDestroy on our Cache listener and then start the // Synchronously call ActorDestroy on our Cache listener and then start the
@@ -53,10 +68,15 @@ private:
virtual bool virtual bool
DeallocPCachePushStreamChild(PCachePushStreamChild* aActor) override; DeallocPCachePushStreamChild(PCachePushStreamChild* aActor) override;
// utility methods
void
NoteDeletedActor();
// Use a weak ref so actor does not hold DOM object alive past content use. // Use a weak ref so actor does not hold DOM object alive past content use.
// The Cache object must call ClearListener() to null this before its // The Cache object must call ClearListener() to null this before its
// destroyed. // destroyed.
Cache* MOZ_NON_OWNING_REF mListener; Cache* MOZ_NON_OWNING_REF mListener;
uint32_t mNumChildActors;
NS_DECL_OWNINGTHREAD NS_DECL_OWNINGTHREAD
}; };

View File

@@ -17,13 +17,11 @@ namespace dom {
namespace cache { namespace cache {
CacheOpChild::CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal, CacheOpChild::CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal,
nsISupports* aParent, Promise* aPromise) Promise* aPromise)
: mGlobal(aGlobal) : mGlobal(aGlobal)
, mParent(aParent)
, mPromise(aPromise) , mPromise(aPromise)
{ {
MOZ_ASSERT(mGlobal); MOZ_ASSERT(mGlobal);
MOZ_ASSERT(mParent);
MOZ_ASSERT(mPromise); MOZ_ASSERT(mPromise);
MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature); MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature);

View File

@@ -25,12 +25,15 @@ class CacheOpChild final : public PCacheOpChild
, public ActorChild , public ActorChild
, public TypeUtils , public TypeUtils
{ {
public: friend class CacheChild;
CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal, friend class CacheStorageChild;
nsISupports* aParent, Promise* aPromise);
~CacheOpChild();
private: private:
// This class must be constructed by CacheChild or CacheStorageChild using
// their ExecuteOp() factory method.
CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal, Promise* aPromise);
~CacheOpChild();
// PCacheOpChild methods // PCacheOpChild methods
virtual void virtual void
ActorDestroy(ActorDestroyReason aReason) override; ActorDestroy(ActorDestroyReason aReason) override;
@@ -65,7 +68,6 @@ private:
HandleRequestList(const nsTArray<CacheRequest>& aRequestList); HandleRequestList(const nsTArray<CacheRequest>& aRequestList);
nsCOMPtr<nsIGlobalObject> mGlobal; nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<nsISupports> mParent;
nsRefPtr<Promise> mPromise; nsRefPtr<Promise> mPromise;
NS_DECL_OWNINGTHREAD NS_DECL_OWNINGTHREAD

View File

@@ -20,17 +20,20 @@ namespace cache {
class CachePushStreamChild final : public PCachePushStreamChild class CachePushStreamChild final : public PCachePushStreamChild
, public ActorChild , public ActorChild
{ {
friend class CacheChild;
public: public:
CachePushStreamChild(Feature* aFeature, nsIAsyncInputStream* aStream); void Start();
~CachePushStreamChild();
virtual void StartDestroy() override; virtual void StartDestroy() override;
void Start();
private: private:
class Callback; class Callback;
// This class must be constructed using CacheChild::CreatePushStream()
CachePushStreamChild(Feature* aFeature, nsIAsyncInputStream* aStream);
~CachePushStreamChild();
// PCachePushStreamChild methods // PCachePushStreamChild methods
virtual void virtual void
ActorDestroy(ActorDestroyReason aReason) override; ActorDestroy(ActorDestroyReason aReason) override;

View File

@@ -13,7 +13,6 @@
#include "mozilla/dom/cache/AutoUtils.h" #include "mozilla/dom/cache/AutoUtils.h"
#include "mozilla/dom/cache/Cache.h" #include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/cache/CacheChild.h" #include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheOpChild.h"
#include "mozilla/dom/cache/CacheStorageChild.h" #include "mozilla/dom/cache/CacheStorageChild.h"
#include "mozilla/dom/cache/Feature.h" #include "mozilla/dom/cache/Feature.h"
#include "mozilla/dom/cache/PCacheChild.h" #include "mozilla/dom/cache/PCacheChild.h"
@@ -440,9 +439,7 @@ CacheStorage::MaybeRunPendingRequests()
entry->mPromise->MaybeReject(rv); entry->mPromise->MaybeReject(rv);
continue; continue;
} }
unused << mActor->SendPCacheOpConstructor( mActor->ExecuteOp(mGlobal, entry->mPromise, args.SendAsOpArgs());
new CacheOpChild(mActor->GetFeature(), mGlobal, this, entry->mPromise),
args.SendAsOpArgs());
} }
mPendingRequests.Clear(); mPendingRequests.Clear();
} }

View File

@@ -8,8 +8,8 @@
#include "mozilla/unused.h" #include "mozilla/unused.h"
#include "mozilla/dom/cache/CacheChild.h" #include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheOpChild.h"
#include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/cache/PCacheOpChild.h"
#include "mozilla/dom/cache/StreamUtils.h" #include "mozilla/dom/cache/StreamUtils.h"
namespace mozilla { namespace mozilla {
@@ -25,6 +25,7 @@ DeallocPCacheStorageChild(PCacheStorageChild* aActor)
CacheStorageChild::CacheStorageChild(CacheStorage* aListener, Feature* aFeature) CacheStorageChild::CacheStorageChild(CacheStorage* aListener, Feature* aFeature)
: mListener(aListener) : mListener(aListener)
, mNumChildActors(0)
{ {
MOZ_COUNT_CTOR(cache::CacheStorageChild); MOZ_COUNT_CTOR(cache::CacheStorageChild);
MOZ_ASSERT(mListener); MOZ_ASSERT(mListener);
@@ -47,6 +48,15 @@ CacheStorageChild::ClearListener()
mListener = nullptr; mListener = nullptr;
} }
void
CacheStorageChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
const CacheOpArgs& aArgs)
{
mNumChildActors += 1;
unused << SendPCacheOpConstructor(
new CacheOpChild(GetFeature(), aGlobal, aPromise), aArgs);
}
void void
CacheStorageChild::StartDestroy() CacheStorageChild::StartDestroy()
{ {
@@ -61,13 +71,19 @@ CacheStorageChild::StartDestroy()
return; return;
} }
// TODO: don't destroy if we have outstanding ops
listener->DestroyInternal(this); listener->DestroyInternal(this);
// CacheStorage listener should call ClearListener() in DestroyInternal() // CacheStorage listener should call ClearListener() in DestroyInternal()
MOZ_ASSERT(!mListener); MOZ_ASSERT(!mListener);
// If we have outstanding child actors, then don't destroy ourself yet.
// The child actors should be short lived and we should allow them to complete
// if possible. SendTeardown() will be called when the count drops to zero
// in NoteDeletedActor().
if (mNumChildActors) {
return;
}
// Start actor destruction from parent process // Start actor destruction from parent process
unused << SendTeardown(); unused << SendTeardown();
} }
@@ -97,9 +113,20 @@ bool
CacheStorageChild::DeallocPCacheOpChild(PCacheOpChild* aActor) CacheStorageChild::DeallocPCacheOpChild(PCacheOpChild* aActor)
{ {
delete aActor; delete aActor;
NoteDeletedActor();
return true; return true;
} }
void
CacheStorageChild::NoteDeletedActor()
{
MOZ_ASSERT(mNumChildActors);
mNumChildActors -= 1;
if (!mNumChildActors && !mListener) {
unused << SendTeardown();
}
}
} // namespace cache } // namespace cache
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View File

@@ -11,10 +11,16 @@
#include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/cache/PCacheStorageChild.h" #include "mozilla/dom/cache/PCacheStorageChild.h"
class nsIGlobalObject;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
class Promise;
namespace cache { namespace cache {
class CacheOpArgs;
class CacheStorage; class CacheStorage;
class PCacheChild; class PCacheChild;
class Feature; class Feature;
@@ -32,6 +38,10 @@ public:
// called yet. // called yet.
void ClearListener(); void ClearListener();
void
ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
const CacheOpArgs& aArgs);
// ActorChild methods // ActorChild methods
// Synchronously call ActorDestroy on our CacheStorage listener and then start // Synchronously call ActorDestroy on our CacheStorage listener and then start
@@ -48,10 +58,15 @@ private:
virtual bool virtual bool
DeallocPCacheOpChild(PCacheOpChild* aActor) override; DeallocPCacheOpChild(PCacheOpChild* aActor) override;
// utility methods
void
NoteDeletedActor();
// Use a weak ref so actor does not hold DOM object alive past content use. // Use a weak ref so actor does not hold DOM object alive past content use.
// The CacheStorage object must call ClearListener() to null this before its // The CacheStorage object must call ClearListener() to null this before its
// destroyed. // destroyed.
CacheStorage* MOZ_NON_OWNING_REF mListener; CacheStorage* MOZ_NON_OWNING_REF mListener;
uint32_t mNumChildActors;
NS_DECL_OWNINGTHREAD NS_DECL_OWNINGTHREAD
}; };

View File

@@ -41,6 +41,7 @@ DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor)
CacheStreamControlChild::CacheStreamControlChild() CacheStreamControlChild::CacheStreamControlChild()
: mDestroyStarted(false) : mDestroyStarted(false)
, mDestroyDelayed(false)
{ {
MOZ_COUNT_CTOR(cache::CacheStreamControlChild); MOZ_COUNT_CTOR(cache::CacheStreamControlChild);
} }
@@ -63,6 +64,21 @@ CacheStreamControlChild::StartDestroy()
} }
mDestroyStarted = true; mDestroyStarted = true;
// If any of the streams have started to be read, then wait for them to close
// naturally.
if (HasEverBeenRead()) {
// Note that we are delaying so that we can re-check for active streams
// in NoteClosedAfterForget().
mDestroyDelayed = true;
return;
}
// Otherwise, if the streams have not been touched then just pre-emptively
// close them now. This handles the case where someone retrieves a Response
// from the Cache, but never accesses the body. We should not keep the
// Worker alive until that Response is GC'd just because of its ignored
// body stream.
// Begin shutting down all streams. This is the same as if the parent had // Begin shutting down all streams. This is the same as if the parent had
// asked us to shutdown. So simulate the CloseAll IPC message. // asked us to shutdown. So simulate the CloseAll IPC message.
RecvCloseAll(); RecvCloseAll();
@@ -120,6 +136,15 @@ CacheStreamControlChild::NoteClosedAfterForget(const nsID& aId)
{ {
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild); NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
unused << SendNoteClosed(aId); unused << SendNoteClosed(aId);
// A stream has closed. If we delayed StartDestry() due to this stream
// being read, then we should check to see if any of the remaining streams
// are active. If none of our other streams have been read, then we can
// proceed with the shutdown now.
if (mDestroyDelayed && !HasEverBeenRead()) {
mDestroyDelayed = false;
RecvCloseAll();
}
} }
#ifdef DEBUG #ifdef DEBUG

View File

@@ -56,6 +56,7 @@ private:
virtual bool RecvCloseAll() override; virtual bool RecvCloseAll() override;
bool mDestroyStarted; bool mDestroyStarted;
bool mDestroyDelayed;
NS_DECL_OWNINGTHREAD NS_DECL_OWNINGTHREAD
}; };

View File

@@ -50,6 +50,9 @@ public:
virtual bool virtual bool
MatchId(const nsID& aId) const override; MatchId(const nsID& aId) const override;
virtual bool
HasEverBeenRead() const override;
// Simulate nsIInputStream methods, but we don't actually inherit from it // Simulate nsIInputStream methods, but we don't actually inherit from it
NS_METHOD NS_METHOD
Close(); Close();
@@ -103,6 +106,7 @@ private:
NumStates NumStates
}; };
Atomic<State> mState; Atomic<State> mState;
Atomic<bool> mHasEverBeenRead;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::ReadStream::Inner, override) NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::ReadStream::Inner, override)
}; };
@@ -249,6 +253,13 @@ ReadStream::Inner::MatchId(const nsID& aId) const
return mId.Equals(aId); return mId.Equals(aId);
} }
bool
ReadStream::Inner::HasEverBeenRead() const
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
return mHasEverBeenRead;
}
NS_IMETHODIMP NS_IMETHODIMP
ReadStream::Inner::Close() ReadStream::Inner::Close()
{ {
@@ -284,6 +295,8 @@ ReadStream::Inner::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut)
Close(); Close();
} }
mHasEverBeenRead = true;
return rv; return rv;
} }
@@ -294,6 +307,10 @@ ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
// stream ops can happen on any thread // stream ops can happen on any thread
MOZ_ASSERT(aNumReadOut); MOZ_ASSERT(aNumReadOut);
if (aCount) {
mHasEverBeenRead = true;
}
nsresult rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount, nsresult rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount,
aNumReadOut); aNumReadOut);
@@ -302,6 +319,14 @@ ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
Close(); Close();
} }
// Verify bytes were actually read before marking as being ever read. For
// example, code can test if the stream supports ReadSegments() by calling
// this method with a dummy callback which doesn't read anything. We don't
// want to trigger on that.
if (*aNumReadOut) {
mHasEverBeenRead = true;
}
return rv; return rv;
} }

View File

@@ -63,6 +63,9 @@ public:
virtual bool virtual bool
MatchId(const nsID& aId) const = 0; MatchId(const nsID& aId) const = 0;
virtual bool
HasEverBeenRead() const = 0;
NS_IMETHOD_(MozExternalRefCountType) NS_IMETHOD_(MozExternalRefCountType)
AddRef(void) = 0; AddRef(void) = 0;

View File

@@ -84,6 +84,18 @@ StreamControl::CloseAllReadStreamsWithoutReporting()
} }
} }
bool
StreamControl::HasEverBeenRead() const
{
ReadStreamList::ForwardIterator iter(mReadStreamList);
while (iter.HasMore()) {
if (iter.GetNext()->HasEverBeenRead()) {
return true;
}
}
return false;
}
} // namespace cache } // namespace cache
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View File

@@ -68,6 +68,9 @@ protected:
void void
CloseAllReadStreamsWithoutReporting(); CloseAllReadStreamsWithoutReporting();
bool
HasEverBeenRead() const;
// protected parts of the abstract interface // protected parts of the abstract interface
virtual void virtual void
NoteClosedAfterForget(const nsID& aId) = 0; NoteClosedAfterForget(const nsID& aId) = 0;