Bug 1107775 - Make deferred finalization more re-entrant safe, r=mccr8

This commit is contained in:
Olli Pettay 2014-12-05 08:53:00 -08:00
parent 11a9491dc2
commit 428fcb7c71

View File

@ -57,6 +57,7 @@
#include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/CycleCollectedJSRuntime.h"
#include <algorithm> #include <algorithm>
#include "mozilla/ArrayUtils.h" #include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/DOMJSClass.h"
@ -96,6 +97,7 @@ class IncrementalFinalizeRunnable : public nsRunnable
nsTArray<nsISupports*> mSupports; nsTArray<nsISupports*> mSupports;
DeferredFinalizeArray mDeferredFinalizeFunctions; DeferredFinalizeArray mDeferredFinalizeFunctions;
uint32_t mFinalizeFunctionToRun; uint32_t mFinalizeFunctionToRun;
bool mReleasing;
static const PRTime SliceMillis = 10; /* ms */ static const PRTime SliceMillis = 10; /* ms */
@ -1124,6 +1126,7 @@ IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSRuntime
DeferredFinalizerTable& aFinalizers) DeferredFinalizerTable& aFinalizers)
: mRuntime(aRt) : mRuntime(aRt)
, mFinalizeFunctionToRun(0) , mFinalizeFunctionToRun(0)
, mReleasing(false)
{ {
this->mSupports.SwapElements(aSupports); this->mSupports.SwapElements(aSupports);
DeferredFinalizeFunctionHolder* function = DeferredFinalizeFunctionHolder* function =
@ -1143,39 +1146,46 @@ IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable()
void void
IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) IncrementalFinalizeRunnable::ReleaseNow(bool aLimited)
{ {
//MOZ_ASSERT(NS_IsMainThread()); if (mReleasing) {
MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0, MOZ_ASSERT(false, "Try to avoid re-entering ReleaseNow!");
"We should have at least ReleaseSliceNow to run"); return;
MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(), }
"No more finalizers to run?"); {
mozilla::AutoRestore<bool> ar(mReleasing);
mReleasing = true;
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); TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
TimeStamp started = TimeStamp::Now(); TimeStamp started = TimeStamp::Now();
bool timeout = false; bool timeout = false;
do { do {
const DeferredFinalizeFunctionHolder& function = const DeferredFinalizeFunctionHolder& function =
mDeferredFinalizeFunctions[mFinalizeFunctionToRun]; mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
if (aLimited) { if (aLimited) {
bool done = false; bool done = false;
while (!timeout && !done) { while (!timeout && !done) {
/* /*
* We don't want to read the clock too often, so we try to * We don't want to read the clock too often, so we try to
* release slices of 100 items. * release slices of 100 items.
*/ */
done = function.run(100, function.data); done = function.run(100, function.data);
timeout = TimeStamp::Now() - started >= sliceTime; timeout = TimeStamp::Now() - started >= sliceTime;
} }
if (done) { if (done) {
++mFinalizeFunctionToRun;
}
if (timeout) {
break;
}
} else {
function.run(UINT32_MAX, function.data);
++mFinalizeFunctionToRun; ++mFinalizeFunctionToRun;
} }
if (timeout) { } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
break; }
}
} else {
function.run(UINT32_MAX, function.data);
++mFinalizeFunctionToRun;
}
} while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) { if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
MOZ_ASSERT(mRuntime->mFinalizeRunnable == this); MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
@ -1210,7 +1220,21 @@ IncrementalFinalizeRunnable::Run()
void void
CycleCollectedJSRuntime::FinalizeDeferredThings(DeferredFinalizeType aType) CycleCollectedJSRuntime::FinalizeDeferredThings(DeferredFinalizeType aType)
{ {
MOZ_ASSERT(!mFinalizeRunnable); /*
* 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);
if (mFinalizeRunnable) {
// If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
// we need to just continue processing it.
return;
}
}
mFinalizeRunnable = new IncrementalFinalizeRunnable(this, mFinalizeRunnable = new IncrementalFinalizeRunnable(this,
mDeferredSupports, mDeferredSupports,
mDeferredFinalizerTable); mDeferredFinalizerTable);
@ -1261,17 +1285,6 @@ CycleCollectedJSRuntime::OnGC(JSGCStatus aStatus)
} }
#endif #endif
/*
* 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. // Do any deferred finalization of native objects.
FinalizeDeferredThings(JS::WasIncrementalGC(mJSRuntime) ? FinalizeIncrementally : FinalizeDeferredThings(JS::WasIncrementalGC(mJSRuntime) ? FinalizeIncrementally :
FinalizeNow); FinalizeNow);