Bug 885866: Separate deferred finalization from XPConnect so we can use it off the main thread. r=mccr8, peterv, bsmedberg, jorendorff

This commit is contained in:
Kyle Huey 2013-07-09 07:28:15 -07:00
parent aaba06e7d1
commit ab927a2cc9
24 changed files with 450 additions and 323 deletions

View File

@ -125,6 +125,16 @@ namespace layers {
class LayerManager;
} // namespace layers
// Called back from DeferredFinalize. Should add 'thing' to the array of smart
// pointers in 'pointers', creating the array if 'pointers' is null, and return
// the array.
typedef void* (*DeferredFinalizeAppendFunction)(void* pointers, void* thing);
// Called to finalize a number of objects. Slice is the number of objects
// to finalize, or if it's UINT32_MAX, all objects should be finalized.
// Return value indicates whether it finalized all objects in the buffer.
typedef bool (*DeferredFinalizeFunction)(uint32_t slice, void* data);
} // namespace mozilla
#ifdef IBMBIDI
@ -1263,6 +1273,11 @@ public:
static bool AreJSObjectsHeld(void* aScriptObjectHolder);
#endif
static void DeferredFinalize(nsISupports* aSupports);
static void DeferredFinalize(mozilla::DeferredFinalizeAppendFunction aAppendFunc,
mozilla::DeferredFinalizeFunction aFunc,
void* aThing);
static void ReleaseWrapper(void* aScriptObjectHolder,
nsWrapperCache* aCache);

View File

@ -5756,6 +5756,22 @@ nsContentUtils::AllocClassMatchingInfo(nsINode* aRootNode,
return info;
}
// static
void
nsContentUtils::DeferredFinalize(nsISupports* aSupports)
{
cyclecollector::DeferredFinalize(aSupports);
}
// static
void
nsContentUtils::DeferredFinalize(mozilla::DeferredFinalizeAppendFunction aAppendFunc,
mozilla::DeferredFinalizeFunction aFunc,
void* aThing)
{
cyclecollector::DeferredFinalize(aAppendFunc, aFunc, aThing);
}
// static
bool
nsContentUtils::IsFocusedContent(const nsIContent* aContent)

View File

@ -74,7 +74,7 @@ XBLFinalize(JSFreeOp *fop, JSObject *obj)
{
nsXBLDocumentInfo* docInfo =
static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
xpc::DeferredRelease(static_cast<nsIScriptGlobalObjectOwner*>(docInfo));
nsContentUtils::DeferredFinalize(static_cast<nsIScriptGlobalObjectOwner*>(docInfo));
nsXBLJSClass* c = static_cast<nsXBLJSClass*>(::JS_GetClass(obj));
c->Drop();

View File

@ -5339,7 +5339,7 @@ nsHTMLDocumentSH::ReleaseDocument(JSFreeOp *fop, JSObject *obj)
{
nsIHTMLDocument* doc = GetDocument(obj);
if (doc) {
xpc::DeferredRelease(doc);
nsContentUtils::DeferredFinalize(doc);
}
}

View File

@ -1795,17 +1795,6 @@ ReportLenientThisUnwrappingFailure(JSContext* cx, JS::Handle<JSObject*> obj)
return true;
}
bool
RegisterForDeferredFinalization(DeferredFinalizeStartFunction start,
DeferredFinalizeFunction run)
{
XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
NS_ENSURE_TRUE(rt, false);
rt->RegisterDeferredFinalize(start, run);
return true;
}
// Date implementation methods
Date::Date() :
mMsecSinceEpoch(UnspecifiedNaN())

View File

@ -2003,10 +2003,6 @@ ConstructJSImplementation(JSContext* aCx, const char* aContractId,
JS::MutableHandle<JSObject*> aObject,
ErrorResult& aRv);
bool
RegisterForDeferredFinalization(DeferredFinalizeStartFunction start,
DeferredFinalizeFunction run);
/**
* Convert an nsCString to jsval, returning true on success.
* These functions are intended for ByteString implementations.

View File

@ -964,26 +964,22 @@ def DeferredFinalizeSmartPtr(descriptor):
smartPtr = 'nsRefPtr<%s>'
return smartPtr % descriptor.nativeType
class CGDeferredFinalizePointers(CGThing):
class CGAppendDeferredFinalizePointer(CGAbstractStaticMethod):
def __init__(self, descriptor):
CGThing.__init__(self)
self.descriptor = descriptor
def declare(self):
return ""
def define(self):
return """nsTArray<%s >* sDeferredFinalizePointers;
""" % DeferredFinalizeSmartPtr(self.descriptor)
class CGGetDeferredFinalizePointers(CGAbstractStaticMethod):
def __init__(self, descriptor):
CGAbstractStaticMethod.__init__(self, descriptor, "GetDeferredFinalizePointers", "void*", [])
CGAbstractStaticMethod.__init__(self, descriptor, "AppendDeferredFinalizePointer", "void*", [Argument('void*', 'data'), Argument('void*', 'thing')])
def definition_body(self):
return """ nsTArray<%s >* pointers = sDeferredFinalizePointers;
sDeferredFinalizePointers = nullptr;
return pointers;""" % DeferredFinalizeSmartPtr(self.descriptor)
smartPtr = DeferredFinalizeSmartPtr(self.descriptor)
return """ nsTArray<%(smartPtr)s >* pointers = static_cast<nsTArray<%(smartPtr)s >*>(data);
if (!pointers) {
pointers = new nsTArray<%(smartPtr)s >();
}
%(nativeType)s* self = static_cast<%(nativeType)s*>(thing);
%(smartPtr)s* defer = pointers->AppendElement();
Take(*defer, self);
return pointers;""" % { 'smartPtr': smartPtr, 'nativeType': self.descriptor.nativeType }
class CGDeferredFinalize(CGAbstractStaticMethod):
def __init__(self, descriptor):
@ -1015,29 +1011,12 @@ def finalizeHook(descriptor, hookName, context):
if descriptor.workers:
finalize += "self->Release();"
elif descriptor.nativeOwnership == 'nsisupports':
finalize += "xpc::DeferredRelease(reinterpret_cast<nsISupports*>(self));"
finalize += "nsContentUtils::DeferredFinalize(reinterpret_cast<nsISupports*>(self));"
else:
smartPtr = DeferredFinalizeSmartPtr(descriptor)
finalize += """static bool registered = false;
if (!registered) {
if (!RegisterForDeferredFinalization(GetDeferredFinalizePointers,
DeferredFinalize)) {
%(smartPtr)s dying;
Take(dying, self);
return;
}
registered = true;
}
if (!sDeferredFinalizePointers) {
sDeferredFinalizePointers = new nsAutoTArray<%(smartPtr)s, 16>();
}
%(smartPtr)s* defer = sDeferredFinalizePointers->AppendElement();
if (!defer) {
%(smartPtr)s dying;
Take(dying, self);
return;
}
Take(*defer, self);""" % { 'smartPtr': smartPtr }
finalize += """nsContentUtils::DeferredFinalize(AppendDeferredFinalizePointer,
DeferredFinalize,
self);
"""
return CGIfWrapper(CGGeneric(finalize), "self")
class CGClassFinalizeHook(CGAbstractClassHook):
@ -7583,8 +7562,7 @@ class CGDescriptor(CGThing):
if descriptor.concrete:
if descriptor.nativeOwnership == 'owned' or descriptor.nativeOwnership == 'refcounted':
cgThings.append(CGDeferredFinalizePointers(descriptor))
cgThings.append(CGGetDeferredFinalizePointers(descriptor))
cgThings.append(CGAppendDeferredFinalizePointer(descriptor))
cgThings.append(CGDeferredFinalize(descriptor))
if not descriptor.proxy:

View File

@ -196,7 +196,7 @@ static void
OnWrapperDestroyed();
static void
DelayedReleaseGCCallback(JSRuntime* rt, JSGCStatus status)
DelayedReleaseGCCallback(JSGCStatus status)
{
if (JSGC_END == status) {
// Take ownership of sDelayedReleases and null it out now. The

View File

@ -2784,10 +2784,11 @@ JS_MaybeGC(JSContext *cx)
}
JS_PUBLIC_API(void)
JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb)
JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data)
{
AssertHeapIsIdle(rt);
rt->gcCallback = cb;
rt->gcCallbackData = data;
}
JS_PUBLIC_API(void)

View File

@ -956,7 +956,7 @@ typedef enum JSGCStatus {
} JSGCStatus;
typedef void
(* JSGCCallback)(JSRuntime *rt, JSGCStatus status);
(* JSGCCallback)(JSRuntime *rt, JSGCStatus status, void *data);
typedef enum JSFinalizeStatus {
/*
@ -2629,7 +2629,7 @@ extern JS_PUBLIC_API(void)
JS_MaybeGC(JSContext *cx);
extern JS_PUBLIC_API(void)
JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb);
JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data);
extern JS_PUBLIC_API(void)
JS_SetFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb);

View File

@ -4553,7 +4553,7 @@ Collect(JSRuntime *rt, bool incremental, int64_t budget,
if (rt->gcIncrementalState == NO_INCREMENTAL) {
gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_GC_BEGIN);
if (JSGCCallback callback = rt->gcCallback)
callback(rt, JSGC_BEGIN);
callback(rt, JSGC_BEGIN, rt->gcCallbackData);
}
rt->gcPoke = false;
@ -4562,7 +4562,7 @@ Collect(JSRuntime *rt, bool incremental, int64_t budget,
if (rt->gcIncrementalState == NO_INCREMENTAL) {
gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_GC_END);
if (JSGCCallback callback = rt->gcCallback)
callback(rt, JSGC_END);
callback(rt, JSGC_END, rt->gcCallbackData);
}
/* Need to re-schedule all zones for GC. */

View File

@ -1120,6 +1120,8 @@ struct JSRuntime : public JS::shadow::Runtime,
JS::GCSliceCallback gcSliceCallback;
JSFinalizeCallback gcFinalizeCallback;
void *gcCallbackData;
js::AnalysisPurgeCallback analysisPurgeCallback;
uint64_t analysisPurgeTriggerBytes;

View File

@ -7,11 +7,18 @@
#include "nsISupports.idl"
[ptr] native JSRuntime(JSRuntime);
native JSGCCallback(JSGCCallback);
native xpcGCCallback(xpcGCCallback);
%{C++
typedef void
(* xpcGCCallback)(JSGCStatus status);
%}
interface nsIBackstagePass;
[uuid(991c0749-a22e-476b-9428-a373df037455)]
[uuid(996ef894-88cd-42c8-ac2d-28c60970daaf)]
interface nsIJSRuntimeService : nsISupports
{
readonly attribute JSRuntime runtime;
@ -20,6 +27,6 @@ interface nsIJSRuntimeService : nsISupports
* Register additional GC callback which will run after the
* standard XPConnect callback.
*/
[noscript, notxpcom] void registerGCCallback(in JSGCCallback func);
[noscript, notxpcom] void unregisterGCCallback(in JSGCCallback func);
[noscript, notxpcom] void registerGCCallback(in xpcGCCallback func);
[noscript, notxpcom] void unregisterGCCallback(in xpcGCCallback func);
};

View File

@ -572,149 +572,6 @@ DoDeferredRelease(nsTArray<T> &array)
}
}
struct DeferredFinalizeFunctionHolder
{
DeferredFinalizeFunction run;
void *data;
};
class XPCIncrementalReleaseRunnable : public nsRunnable
{
XPCJSRuntime *runtime;
nsTArray<nsISupports *> items;
nsAutoTArray<DeferredFinalizeFunctionHolder, 16> deferredFinalizeFunctions;
uint32_t finalizeFunctionToRun;
static const PRTime SliceMillis = 10; /* ms */
public:
XPCIncrementalReleaseRunnable(XPCJSRuntime *rt, nsTArray<nsISupports *> &items);
virtual ~XPCIncrementalReleaseRunnable();
void ReleaseNow(bool limited);
NS_DECL_NSIRUNNABLE
};
bool
ReleaseSliceNow(uint32_t slice, void *data)
{
MOZ_ASSERT(slice > 0, "nonsensical/useless call with slice == 0");
nsTArray<nsISupports *> *items = static_cast<nsTArray<nsISupports *>*>(data);
slice = std::min(slice, items->Length());
for (uint32_t i = 0; i < slice; ++i) {
// Remove (and NS_RELEASE) the last entry in "items":
uint32_t lastItemIdx = items->Length() - 1;
nsISupports *wrapper = items->ElementAt(lastItemIdx);
items->RemoveElementAt(lastItemIdx);
NS_RELEASE(wrapper);
}
return items->IsEmpty();
}
XPCIncrementalReleaseRunnable::XPCIncrementalReleaseRunnable(XPCJSRuntime *rt,
nsTArray<nsISupports *> &items)
: runtime(rt),
finalizeFunctionToRun(0)
{
nsLayoutStatics::AddRef();
this->items.SwapElements(items);
DeferredFinalizeFunctionHolder *function = deferredFinalizeFunctions.AppendElement();
function->run = ReleaseSliceNow;
function->data = &this->items;
for (uint32_t i = 0; i < rt->mDeferredFinalizeFunctions.Length(); ++i) {
void *data = (rt->mDeferredFinalizeFunctions[i].start)();
if (data) {
function = deferredFinalizeFunctions.AppendElement();
function->run = rt->mDeferredFinalizeFunctions[i].run;
function->data = data;
}
}
}
XPCIncrementalReleaseRunnable::~XPCIncrementalReleaseRunnable()
{
MOZ_ASSERT(this != runtime->mReleaseRunnable);
nsLayoutStatics::Release();
}
void
XPCIncrementalReleaseRunnable::ReleaseNow(bool limited)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(deferredFinalizeFunctions.Length() != 0,
"We should have at least ReleaseSliceNow to run");
MOZ_ASSERT(finalizeFunctionToRun < deferredFinalizeFunctions.Length(),
"No more finalizers to run?");
TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
TimeStamp started = TimeStamp::Now();
bool timeout = false;
do {
const DeferredFinalizeFunctionHolder &function =
deferredFinalizeFunctions[finalizeFunctionToRun];
if (limited) {
bool done = false;
while (!timeout && !done) {
/*
* We don't want to read the clock too often, so we try to
* release slices of 100 items.
*/
done = function.run(100, function.data);
timeout = TimeStamp::Now() - started >= sliceTime;
}
if (done)
++finalizeFunctionToRun;
if (timeout)
break;
} else {
function.run(UINT32_MAX, function.data);
MOZ_ASSERT(!items.Length());
++finalizeFunctionToRun;
}
} while (finalizeFunctionToRun < deferredFinalizeFunctions.Length());
if (finalizeFunctionToRun == deferredFinalizeFunctions.Length()) {
MOZ_ASSERT(runtime->mReleaseRunnable == this);
runtime->mReleaseRunnable = nullptr;
}
}
NS_IMETHODIMP
XPCIncrementalReleaseRunnable::Run()
{
if (runtime->mReleaseRunnable != this) {
/* These items were already processed synchronously in JSGC_BEGIN. */
MOZ_ASSERT(!items.Length());
return NS_OK;
}
ReleaseNow(true);
if (items.Length()) {
nsresult rv = NS_DispatchToMainThread(this);
if (NS_FAILED(rv))
ReleaseNow(false);
}
return NS_OK;
}
void
XPCJSRuntime::ReleaseIncrementally(nsTArray<nsISupports *> &array)
{
MOZ_ASSERT(!mReleaseRunnable);
mReleaseRunnable = new XPCIncrementalReleaseRunnable(this, array);
nsresult rv = NS_DispatchToMainThread(mReleaseRunnable);
if (NS_FAILED(rv))
mReleaseRunnable->ReleaseNow(false);
}
/* static */ void
XPCJSRuntime::GCSliceCallback(JSRuntime *rt,
JS::GCProgress progress,
@ -733,20 +590,16 @@ XPCJSRuntime::GCSliceCallback(JSRuntime *rt,
(*self->mPrevGCSliceCallback)(rt, progress, desc);
}
/* static */ void
XPCJSRuntime::GCCallback(JSRuntime *rt, JSGCStatus status)
void
XPCJSRuntime::CustomGCCallback(JSGCStatus status)
{
XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
if (!self)
return;
switch (status) {
case JSGC_BEGIN:
{
// We seem to sometime lose the unrooted global flag. Restore it
// here. FIXME: bug 584495.
JSContext *iter = nullptr;
while (JSContext *acx = JS_ContextIterator(rt, &iter)) {
while (JSContext *acx = JS_ContextIterator(Runtime(), &iter)) {
if (!js::HasUnrootedGlobal(acx))
JS_ToggleOptions(acx, JSOPTION_UNROOTED_GLOBAL);
}
@ -754,33 +607,13 @@ XPCJSRuntime::GCCallback(JSRuntime *rt, JSGCStatus status)
}
case JSGC_END:
{
/*
* If the previous GC created a runnable to release objects
* incrementally, and if it hasn't finished yet, finish it now. We
* don't want these to build up. We also don't want to allow any
* existing incremental release runnables to run after a
* non-incremental GC, since they are often used to detect leaks.
*/
if (self->mReleaseRunnable)
self->mReleaseRunnable->ReleaseNow(false);
// Do any deferred releases of native objects.
if (JS::WasIncrementalGC(rt)) {
self->ReleaseIncrementally(self->mNativesToReleaseArray);
} else {
DoDeferredRelease(self->mNativesToReleaseArray);
for (uint32_t i = 0; i < self->mDeferredFinalizeFunctions.Length(); ++i) {
if (void *data = self->mDeferredFinalizeFunctions[i].start())
self->mDeferredFinalizeFunctions[i].run(UINT32_MAX, data);
}
}
break;
}
}
nsTArray<JSGCCallback> callbacks(self->extraGCCallbacks);
nsTArray<xpcGCCallback> callbacks(extraGCCallbacks);
for (uint32_t i = 0; i < callbacks.Length(); ++i)
callbacks[i](rt, status);
callbacks[i](status);
}
/* static */ void
@ -1215,8 +1048,6 @@ void XPCJSRuntime::SystemIsBeingShutDown()
XPCJSRuntime::~XPCJSRuntime()
{
MOZ_ASSERT(!mReleaseRunnable);
JS::SetGCSliceCallback(Runtime(), mPrevGCSliceCallback);
xpc_DelocalizeRuntime(Runtime());
@ -2689,7 +2520,6 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
JS_SetContextCallback(runtime, ContextCallback);
JS_SetDestroyCompartmentCallback(runtime, CompartmentDestroyedCallback);
JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback);
JS_SetGCCallback(runtime, GCCallback);
mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback);
JS_SetFinalizeCallback(runtime, FinalizeCallback);
JS_SetWrapObjectCallbacks(runtime,
@ -2871,20 +2701,6 @@ XPCJSRuntime::NoteCustomGCThingXPCOMChildren(js::Class* clasp, JSObject* obj,
return true;
}
bool
XPCJSRuntime::DeferredRelease(nsISupports *obj)
{
MOZ_ASSERT(obj);
if (mNativesToReleaseArray.IsEmpty()) {
// This array sometimes has 1000's
// of entries, and usually has 50-200 entries. Avoid lots
// of incremental grows. We compact it down when we're done.
mNativesToReleaseArray.SetCapacity(256);
}
return mNativesToReleaseArray.AppendElement(obj) != nullptr;
}
/***************************************************************************/
#ifdef DEBUG
@ -3017,14 +2833,14 @@ XPCRootSetElem::RemoveFromRootSet(XPCLock *lock)
}
void
XPCJSRuntime::AddGCCallback(JSGCCallback cb)
XPCJSRuntime::AddGCCallback(xpcGCCallback cb)
{
NS_ASSERTION(cb, "null callback");
extraGCCallbacks.AppendElement(cb);
}
void
XPCJSRuntime::RemoveGCCallback(JSGCCallback cb)
XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb)
{
NS_ASSERTION(cb, "null callback");
bool found = extraGCCallbacks.RemoveElement(cb);

View File

@ -9,6 +9,7 @@
#include "xpcprivate.h"
#include "nsCxPusher.h"
#include "nsAtomicRefcnt.h"
#include "nsContentUtils.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "nsTextFormatter.h"
@ -508,7 +509,7 @@ nsXPCWrappedJS::Unlink()
if (mOuter) {
XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
if (rt->GetThreadRunningGC()) {
rt->DeferredRelease(mOuter);
nsContentUtils::DeferredFinalize(mOuter);
mOuter = nullptr;
} else {
NS_RELEASE(mOuter);

View File

@ -824,13 +824,8 @@ XPCWrappedNative::Destroy()
if (mIdentity) {
XPCJSRuntime* rt = GetRuntime();
if (rt && rt->GetDoingFinalization()) {
if (rt->DeferredRelease(mIdentity)) {
mIdentity = nullptr;
} else {
NS_WARNING("Failed to append object for deferred release.");
// XXX do we really want to do this???
NS_RELEASE(mIdentity);
}
nsContentUtils::DeferredFinalize(mIdentity);
mIdentity = nullptr;
} else {
NS_RELEASE(mIdentity);
}
@ -1168,11 +1163,7 @@ XPCWrappedNative::FlatJSObjectFinalized()
#endif
XPCJSRuntime* rt = GetRuntime();
if (rt) {
if (!rt->DeferredRelease(obj)) {
NS_WARNING("Failed to append object for deferred release.");
// XXX do we really want to do this???
obj->Release();
}
nsContentUtils::DeferredFinalize(obj);
} else {
obj->Release();
}

View File

@ -1156,16 +1156,16 @@ nsXPConnect::GetRuntime(JSRuntime **runtime)
return NS_OK;
}
/* [noscript, notxpcom] void registerGCCallback(in JSGCCallback func); */
/* [noscript, notxpcom] void registerGCCallback(in xpcGCCallback func); */
NS_IMETHODIMP_(void)
nsXPConnect::RegisterGCCallback(JSGCCallback func)
nsXPConnect::RegisterGCCallback(xpcGCCallback func)
{
mRuntime->AddGCCallback(func);
}
/* [noscript, notxpcom] void unregisterGCCallback(in JSGCCallback func); */
/* [noscript, notxpcom] void unregisterGCCallback(in xpcGCCallback func); */
NS_IMETHODIMP_(void)
nsXPConnect::UnregisterGCCallback(JSGCCallback func)
nsXPConnect::UnregisterGCCallback(xpcGCCallback func)
{
mRuntime->RemoveGCCallback(func);
}
@ -1317,7 +1317,8 @@ namespace xpc {
bool
DeferredRelease(nsISupports *obj)
{
return nsXPConnect::GetRuntimeInstance()->DeferredRelease(obj);
nsContentUtils::DeferredFinalize(obj);
return true;
}
NS_EXPORT_(bool)

View File

@ -596,7 +596,6 @@ public:
// So, xpconnect can only be used on one JSRuntime within the process.
class XPCJSContextStack;
class XPCIncrementalReleaseRunnable;
class XPCJSRuntime : public mozilla::CycleCollectedJSRuntime
{
public:
@ -663,37 +662,13 @@ public:
NoteCustomGCThingXPCOMChildren(js::Class* aClasp, JSObject* aObj,
nsCycleCollectionTraversalCallback& aCb) const;
bool DeferredRelease(nsISupports* obj);
/**
* Infrastructure for classes that need to defer part of the finalization
* until after the GC has run, for example for objects that we don't want to
* destroy during the GC.
*/
private:
struct DeferredFinalizeFunctions
{
DeferredFinalizeStartFunction start;
DeferredFinalizeFunction run;
};
nsAutoTArray<DeferredFinalizeFunctions, 16> mDeferredFinalizeFunctions;
public:
// Register deferred finalization functions. Should only be called once per
// pair of start and run.
bool RegisterDeferredFinalize(DeferredFinalizeStartFunction start,
DeferredFinalizeFunction run)
{
DeferredFinalizeFunctions* item =
mDeferredFinalizeFunctions.AppendElement();
item->start = start;
item->run = run;
return true;
}
JSBool GetDoingFinalization() const {return mDoingFinalization;}
// Mapping of often used strings to jsid atoms that live 'forever'.
@ -744,7 +719,7 @@ public:
void PrepareForForgetSkippable() MOZ_OVERRIDE;
void PrepareForCollection() MOZ_OVERRIDE;
static void GCCallback(JSRuntime *rt, JSGCStatus status);
void CustomGCCallback(JSGCStatus status) MOZ_OVERRIDE;
static void GCSliceCallback(JSRuntime *rt,
JS::GCProgress progress,
const JS::GCDescription &desc);
@ -829,8 +804,8 @@ private:
public:
#endif
void AddGCCallback(JSGCCallback cb);
void RemoveGCCallback(JSGCCallback cb);
void AddGCCallback(xpcGCCallback cb);
void RemoveGCCallback(xpcGCCallback cb);
static void ActivityCallback(void *arg, JSBool active);
static void CTypesActivityCallback(JSContext *cx,
@ -884,11 +859,10 @@ private:
PRLock *mWatchdogLock;
PRCondVar *mWatchdogWakeup;
PRThread *mWatchdogThread;
nsTArray<JSGCCallback> extraGCCallbacks;
nsTArray<xpcGCCallback> extraGCCallbacks;
bool mWatchdogHibernating;
enum { RUNTIME_ACTIVE, RUNTIME_INACTIVE } mRuntimeState;
PRTime mTimeAtLastRuntimeStateChange;
nsRefPtr<XPCIncrementalReleaseRunnable> mReleaseRunnable;
JS::GCSliceCallback mPrevGCSliceCallback;
JSObject* mJunkScope;

View File

@ -240,8 +240,6 @@ private:
namespace xpc {
bool DeferredRelease(nsISupports *obj);
// If these functions return false, then an exception will be set on cx.
NS_EXPORT_(bool) Base64Encode(JSContext *cx, JS::Value val, JS::Value *out);
NS_EXPORT_(bool) Base64Decode(JSContext *cx, JS::Value val, JS::Value *out);
@ -459,14 +457,4 @@ Register(nsScriptNameSpaceManager* aNameSpaceManager);
} // namespace dom
} // namespace mozilla
// Called once before the deferred finalization starts. Should hand off the
// buffer with things to finalize in the return value.
typedef void* (*DeferredFinalizeStartFunction)();
// Called to finalize a number of objects. Slice is the number of objects
// to finalize, or if it's UINT32_MAX, all objects should be finalized.
// data is the pointer returned by DeferredFinalizeStartFunction.
// Return value indicates whether it finalized all objects in the buffer.
typedef bool (*DeferredFinalizeFunction)(uint32_t slice, void* data);
#endif

View File

@ -55,6 +55,7 @@
// traversed.
#include "mozilla/CycleCollectedJSRuntime.h"
#include <algorithm>
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMJSClass.h"
@ -70,6 +71,44 @@
using namespace mozilla;
using namespace mozilla::dom;
namespace mozilla {
struct DeferredFinalizeFunctionHolder
{
DeferredFinalizeFunction run;
void *data;
};
class IncrementalFinalizeRunnable : public nsRunnable
{
typedef nsAutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray;
typedef CycleCollectedJSRuntime::DeferredFinalizerTable DeferredFinalizerTable;
CycleCollectedJSRuntime* mRuntime;
nsTArray<nsISupports*> mSupports;
DeferredFinalizeArray mDeferredFinalizeFunctions;
uint32_t mFinalizeFunctionToRun;
static const PRTime SliceMillis = 10; /* ms */
static PLDHashOperator
DeferredFinalizerEnumerator(DeferredFinalizeFunction& aFunction,
void*& aData,
void* aClosure);
public:
IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
nsTArray<nsISupports*>& mSupports,
DeferredFinalizerTable& aFinalizerTable);
virtual ~IncrementalFinalizeRunnable();
void ReleaseNow(bool aLimited);
NS_DECL_NSIRUNNABLE
};
} // namespace mozilla
inline bool
AddToCCKind(JSGCTraceKind kind)
{
@ -468,14 +507,20 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(uint32_t aMaxbytes,
MOZ_CRASH();
}
JS_SetGrayGCRootsTracer(mJSRuntime, TraceGrayJS, this);
JS_SetGCCallback(mJSRuntime, GCCallback, this);
mJSHolders.Init(512);
nsCycleCollector_registerJSRuntime(this);
mDeferredFinalizerTable.Init();
}
CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
{
MOZ_ASSERT(!mDeferredFinalizerTable.Count());
MOZ_ASSERT(!mDeferredSupports.Length());
nsCycleCollector_forgetJSRuntime();
JS_DestroyRuntime(mJSRuntime);
@ -751,6 +796,18 @@ CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer, void* aData)
self->TraceNativeGrayRoots(aTracer);
}
/* static */ void
CycleCollectedJSRuntime::GCCallback(JSRuntime* aRuntime,
JSGCStatus aStatus,
void* aData)
{
CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
MOZ_ASSERT(aRuntime == self->Runtime());
self->OnGC(aStatus);
}
struct JsGcTracer : public TraceCallbacks
{
virtual void Trace(JS::Heap<JS::Value> *p, const char *name, void *closure) const MOZ_OVERRIDE {
@ -968,3 +1025,197 @@ CycleCollectedJSRuntime::Collect(uint32_t aReason) const
JS::PrepareForFullGC(mJSRuntime);
JS::GCForReason(mJSRuntime, gcreason);
}
void
CycleCollectedJSRuntime::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
DeferredFinalizeFunction aFunc,
void* aThing)
{
void* thingArray = nullptr;
bool hadThingArray = mDeferredFinalizerTable.Get(aFunc, &thingArray);
thingArray = aAppendFunc(thingArray, aThing);
if (!hadThingArray) {
mDeferredFinalizerTable.Put(aFunc, thingArray);
}
}
void
CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports)
{
mDeferredSupports.AppendElement(aSupports);
}
bool
ReleaseSliceNow(uint32_t aSlice, void* aData)
{
MOZ_ASSERT(aSlice > 0, "nonsensical/useless call with slice == 0");
nsTArray<nsISupports*>* items = static_cast<nsTArray<nsISupports*>*>(aData);
uint32_t length = items->Length();
aSlice = std::min(aSlice, length);
for (uint32_t i = length; i > length - aSlice; --i) {
// Remove (and NS_RELEASE) the last entry in "items":
uint32_t lastItemIdx = i - 1;
nsISupports* wrapper = items->ElementAt(lastItemIdx);
items->RemoveElementAt(lastItemIdx);
NS_RELEASE(wrapper);
}
return items->IsEmpty();
}
/* static */ PLDHashOperator
IncrementalFinalizeRunnable::DeferredFinalizerEnumerator(DeferredFinalizeFunction& aFunction,
void*& aData,
void* aClosure)
{
DeferredFinalizeArray* array = static_cast<DeferredFinalizeArray*>(aClosure);
DeferredFinalizeFunctionHolder* function = array->AppendElement();
function->run = aFunction;
function->data = aData;
return PL_DHASH_REMOVE;
}
IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
nsTArray<nsISupports*>& aSupports,
DeferredFinalizerTable& aFinalizers)
: mRuntime(aRt),
mFinalizeFunctionToRun(0)
{
this->mSupports.SwapElements(aSupports);
DeferredFinalizeFunctionHolder* function = mDeferredFinalizeFunctions.AppendElement();
function->run = ReleaseSliceNow;
function->data = &this->mSupports;
// Enumerate the hashtable into our array.
aFinalizers.Enumerate(DeferredFinalizerEnumerator, &mDeferredFinalizeFunctions);
}
IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable()
{
MOZ_ASSERT(this != mRuntime->mFinalizeRunnable);
}
void
IncrementalFinalizeRunnable::ReleaseNow(bool aLimited)
{
//MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0,
"We should have at least ReleaseSliceNow to run");
MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(),
"No more finalizers to run?");
TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
TimeStamp started = TimeStamp::Now();
bool timeout = false;
do {
const DeferredFinalizeFunctionHolder &function =
mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
if (aLimited) {
bool done = false;
while (!timeout && !done) {
/*
* We don't want to read the clock too often, so we try to
* release slices of 100 items.
*/
done = function.run(100, function.data);
timeout = TimeStamp::Now() - started >= sliceTime;
}
if (done) {
++mFinalizeFunctionToRun;
}
if (timeout) {
break;
}
} else {
function.run(UINT32_MAX, function.data);
++mFinalizeFunctionToRun;
}
} while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
mDeferredFinalizeFunctions.Clear();
// NB: This may delete this!
mRuntime->mFinalizeRunnable = nullptr;
}
}
NS_IMETHODIMP
IncrementalFinalizeRunnable::Run()
{
if (mRuntime->mFinalizeRunnable != this) {
/* These items were already processed synchronously in JSGC_END. */
MOZ_ASSERT(!mSupports.Length());
MOZ_ASSERT(!mDeferredFinalizeFunctions.Length());
return NS_OK;
}
ReleaseNow(true);
if (mDeferredFinalizeFunctions.Length()) {
nsresult rv = NS_DispatchToCurrentThread(this);
if (NS_FAILED(rv)) {
ReleaseNow(false);
}
}
return NS_OK;
}
void
CycleCollectedJSRuntime::FinalizeDeferredThings(DeferredFinalizeType aType)
{
MOZ_ASSERT(!mFinalizeRunnable);
mFinalizeRunnable = new IncrementalFinalizeRunnable(this,
mDeferredSupports,
mDeferredFinalizerTable);
// Everything should be gone now.
MOZ_ASSERT(!mDeferredSupports.Length());
MOZ_ASSERT(!mDeferredFinalizerTable.Count());
if (aType == FinalizeIncrementally) {
NS_DispatchToCurrentThread(mFinalizeRunnable);
} else {
mFinalizeRunnable->ReleaseNow(false);
MOZ_ASSERT(!mFinalizeRunnable);
}
}
void
CycleCollectedJSRuntime::OnGC(JSGCStatus aStatus)
{
switch (aStatus) {
case JSGC_BEGIN:
{
break;
}
case JSGC_END:
{
/*
* If the previous GC created a runnable to finalize objects
* incrementally, and if it hasn't finished yet, finish it now. We
* don't want these to build up. We also don't want to allow any
* existing incremental finalize runnables to run after a
* non-incremental GC, since they are often used to detect leaks.
*/
if (mFinalizeRunnable) {
mFinalizeRunnable->ReleaseNow(false);
}
// Do any deferred finalization of native objects.
FinalizeDeferredThings(JS::WasIncrementalGC(mJSRuntime) ? FinalizeIncrementally :
FinalizeNow);
break;
}
default:
MOZ_CRASH();
}
CustomGCCallback(aStatus);
}

View File

@ -11,9 +11,11 @@
#include "jsprvtd.h"
#include "jsapi.h"
#include "nsCycleCollector.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsTArray.h"
class nsCycleCollectionNoteRootCallback;
class nsScriptObjectTracer;
@ -73,10 +75,13 @@ public:
nsCycleCollectionTraversalCallback &cb);
};
class IncrementalFinalizeRunnable;
class CycleCollectedJSRuntime
{
friend class JSGCThingParticipant;
friend class JSZoneParticipant;
friend class IncrementalFinalizeRunnable;
protected:
CycleCollectedJSRuntime(uint32_t aMaxbytes,
JSUseHelperThreads aUseHelperThreads,
@ -95,6 +100,8 @@ protected:
virtual void TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& aCb) = 0;
virtual void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) = 0;
virtual void CustomGCCallback(JSGCStatus aStatus) {}
private:
void
@ -143,10 +150,20 @@ private:
static void TraceBlackJS(JSTracer* aTracer, void* aData);
static void TraceGrayJS(JSTracer* aTracer, void* aData);
static void GCCallback(JSRuntime* aRuntime, JSGCStatus aStatus, void* aData);
virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
void TraceNativeGrayRoots(JSTracer* aTracer);
enum DeferredFinalizeType {
FinalizeIncrementally,
FinalizeNow,
};
void FinalizeDeferredThings(DeferredFinalizeType aType);
void OnGC(JSGCStatus aStatus);
public:
void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
void RemoveJSHolder(void* aHolder);
@ -175,6 +192,11 @@ public:
virtual void PrepareForForgetSkippable() {}
virtual void PrepareForCollection() {}
void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
DeferredFinalizeFunction aFunc,
void* aThing);
void DeferredFinalize(nsISupports* aSupports);
private:
typedef const CCParticipantVTable<JSGCThingParticipant>::Type GCThingParticipantVTable;
const GCThingParticipantVTable mGCThingCycleCollectorGlobal;
@ -186,6 +208,13 @@ private:
nsDataHashtable<nsPtrHashKey<void>, nsScriptObjectTracer*> mJSHolders;
nsTArray<nsISupports*> mDeferredSupports;
typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*>
DeferredFinalizerTable;
DeferredFinalizerTable mDeferredFinalizerTable;
nsRefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
#ifdef DEBUG
void* mObjectToUnlink;
bool mExpectUnrootedGlobals;

View File

@ -106,7 +106,6 @@
#include "nsCycleCollectionParticipant.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "nsCycleCollectorUtils.h"
#include "nsIProgrammingLanguage.h"
#include "nsBaseHashtable.h"
#include "nsHashKeys.h"
#include "nsDeque.h"
@ -118,13 +117,10 @@
#include "nsPrintfCString.h"
#include "nsTArray.h"
#include "nsIConsoleService.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsTArray.h"
#include "mozilla/Attributes.h"
#include "nsICycleCollectorListener.h"
#include "nsIXPConnect.h"
#include "nsIJSRuntimeService.h"
#include "nsIMemoryReporter.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
@ -3030,6 +3026,36 @@ cyclecollector::TestJSHolder(void* aHolder)
}
#endif
void
cyclecollector::DeferredFinalize(nsISupports* aSupports)
{
CollectorData *data = sCollectorData.get();
// We should have started the cycle collector by now, and not completely
// shut down.
MOZ_ASSERT(data);
// And we should have a runtime.
MOZ_ASSERT(data->mRuntime);
data->mRuntime->DeferredFinalize(aSupports);
}
void
cyclecollector::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
DeferredFinalizeFunction aFunc,
void* aThing)
{
CollectorData *data = sCollectorData.get();
// We should have started the cycle collector by now, and not completely
// shut down.
MOZ_ASSERT(data);
// And we should have a runtime.
MOZ_ASSERT(data->mRuntime);
data->mRuntime->DeferredFinalize(aAppendFunc, aFunc, aThing);
}
nsPurpleBufferEntry*
NS_CycleCollectorSuspect2(void *n, nsCycleCollectionParticipant *cp)
{

View File

@ -10,8 +10,17 @@ class nsICycleCollectorListener;
class nsISupports;
class nsScriptObjectTracer;
#include "nsError.h"
#include "nsID.h"
namespace mozilla {
class CycleCollectedJSRuntime;
// See the comments in nsContentUtils.h for explanations of these functions.
typedef void* (*DeferredFinalizeAppendFunction)(void* pointers, void* thing);
typedef bool (*DeferredFinalizeFunction)(uint32_t slice, void* data);
}
// Contains various stats about the cycle collection.
@ -76,6 +85,12 @@ void RemoveJSHolder(void* aHolder);
bool TestJSHolder(void* aHolder);
#endif
void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
DeferredFinalizeFunction aFunc,
void* aThing);
void DeferredFinalize(nsISupports* aSupports);
} // namespace cyclecollector
} // namespace mozilla

View File

@ -361,6 +361,37 @@ class nsClearingPtrHashKey : public nsPtrHashKey<T>
typedef nsPtrHashKey<const void> nsVoidPtrHashKey;
typedef nsClearingPtrHashKey<const void> nsClearingVoidPtrHashKey;
/**
* hashkey wrapper using a function pointer KeyType
*
* @see nsTHashtable::EntryType for specification
*/
template<class T>
class nsFuncPtrHashKey : public PLDHashEntryHdr
{
public:
typedef T &KeyType;
typedef const T *KeyTypePointer;
nsFuncPtrHashKey(const T *key) : mKey(*const_cast<T*>(key)) {}
nsFuncPtrHashKey(const nsFuncPtrHashKey<T> &toCopy) : mKey(toCopy.mKey) {}
~nsFuncPtrHashKey() {}
KeyType GetKey() const { return const_cast<T&>(mKey); }
bool KeyEquals(KeyTypePointer key) const { return *key == mKey; }
static KeyTypePointer KeyToPointer(KeyType key) { return &key; }
static PLDHashNumber HashKey(KeyTypePointer key)
{
return NS_PTR_TO_INT32(*key) >> 2;
}
enum { ALLOW_MEMMOVE = true };
protected:
T mKey;
};
/**
* hashkey wrapper using nsID KeyType
*