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 <algorithm>
#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMJSClass.h"
@ -96,6 +97,7 @@ class IncrementalFinalizeRunnable : public nsRunnable
nsTArray<nsISupports*> mSupports;
DeferredFinalizeArray mDeferredFinalizeFunctions;
uint32_t mFinalizeFunctionToRun;
bool mReleasing;
static const PRTime SliceMillis = 10; /* ms */
@ -1124,6 +1126,7 @@ IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSRuntime
DeferredFinalizerTable& aFinalizers)
: mRuntime(aRt)
, mFinalizeFunctionToRun(0)
, mReleasing(false)
{
this->mSupports.SwapElements(aSupports);
DeferredFinalizeFunctionHolder* function =
@ -1143,39 +1146,46 @@ IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable()
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?");
if (mReleasing) {
MOZ_ASSERT(false, "Try to avoid re-entering ReleaseNow!");
return;
}
{
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);
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) {
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;
}
if (timeout) {
break;
}
} else {
function.run(UINT32_MAX, function.data);
++mFinalizeFunctionToRun;
}
} while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
} while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
}
if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
@ -1210,7 +1220,21 @@ IncrementalFinalizeRunnable::Run()
void
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,
mDeferredSupports,
mDeferredFinalizerTable);
@ -1261,17 +1285,6 @@ CycleCollectedJSRuntime::OnGC(JSGCStatus aStatus)
}
#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.
FinalizeDeferredThings(JS::WasIncrementalGC(mJSRuntime) ? FinalizeIncrementally :
FinalizeNow);