Commit 3ac21492 authored by ager@chromium.org's avatar ager@chromium.org

Oilpan: introduce sticky forcedForTesting flag to ensure that a precise

GC is performed at the next return to the event loop.

Use this in svg animation tests to be able to reliably check that
objects die when expected.

R=erik.corry@gmail.com, haraken@chromium.org, kouhei@chromium.org, oilpan-reviews@chromium.org, vegorov@chromium.org
NOTRY=true

Review URL: https://codereview.chromium.org/220203005

git-svn-id: svn://svn.chromium.org/blink/trunk@170648 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent d0639256
...@@ -12,7 +12,3 @@ crbug.com/342574 [ Mac Debug ] fast/css-generated-content/crash-selection-editin ...@@ -12,7 +12,3 @@ crbug.com/342574 [ Mac Debug ] fast/css-generated-content/crash-selection-editin
crbug.com/350316 [ Linux Win Debug ] http/tests/eventsource/workers/eventsource-simple.html [ Crash ] crbug.com/350316 [ Linux Win Debug ] http/tests/eventsource/workers/eventsource-simple.html [ Crash ]
crbug.com/350316 [ Mac Debug ] http/tests/eventsource/workers/eventsource-simple.html [ Timeout ] crbug.com/350316 [ Mac Debug ] http/tests/eventsource/workers/eventsource-simple.html [ Timeout ]
crbug.com/356900 svg/animations/smil-leak-element-instances-noBaseValRef.svg [ Pass Failure ]
crbug.com/356900 svg/animations/smil-leak-element-instances.svg [ Pass Failure ]
crbug.com/356900 svg/animations/smil-leak-elements.svg [ Pass Failure ]
...@@ -8,19 +8,26 @@ ...@@ -8,19 +8,26 @@
if (!window.internals) { if (!window.internals) {
debug("This test only runs on \"content_shell --dump-render-tree\", as it requires existence of window.internals."); debug("This test only runs on \"content_shell --dump-render-tree\", as it requires existence of window.internals.");
} else { } else {
gc(); testRunner.waitUntilDone();
var documentsBefore = window.internals.numberOfLiveDocuments(); window.jsTestIsAsync = true;
var documentsBefore;
var documentsAfter;
collectGarbage(function() {
documentsBefore = window.internals.numberOfLiveDocuments();
var frame = document.getElementById('frame'); var frame = document.getElementById('frame');
frame.contentDocument.body.innerHTML = '<form></form>'; frame.contentDocument.body.innerHTML = '<form></form>';
document.body.removeChild(frame); document.body.removeChild(frame);
frame = null; frame = null;
gc(); collectGarbage(function() {
var documentsAfter = window.internals.numberOfLiveDocuments(); documentsAfter = window.internals.numberOfLiveDocuments();
// -1 is from removing frame itself. // -1 is from removing frame itself.
shouldBe('documentsBefore - 1', 'documentsAfter'); shouldBe('documentsBefore - 1', 'documentsAfter');
finishJSTest();
});
});
} }
</script> </script>
</body> </body>
......
...@@ -657,6 +657,30 @@ function shouldHaveHadError(message) ...@@ -657,6 +657,30 @@ function shouldHaveHadError(message)
testFailed("expectError() not called before shouldHaveHadError()"); testFailed("expectError() not called before shouldHaveHadError()");
} }
// With Oilpan tests that rely on garbage collection need to go through
// the event loop in order to get precise garbage collections. Oilpan
// uses conservative stack scanning when not at the event loop and that
// can artificially keep objects alive. Therefore, tests that need to check
// that something is dead need to use this asynchronous collectGarbage
// function.
function collectGarbage(callback) {
// Perform multiple GCs to break sequences of Oilpan Persistent handles
// or RefPtrs that will keep objects alive until the next GC.
// FIXME: Oilpan: Once everything is moved to the oilpan heap we can
// reduce the number of garbage collections.
GCController.collect();
setTimeout(function() {
GCController.collect();
setTimeout(function() {
GCController.collect();
setTimeout(function() {
GCController.collect();
setTimeout(callback, 0);
}, 0);
}, 0);
}, 0);
}
function gc() { function gc() {
if (typeof GCController !== "undefined") if (typeof GCController !== "undefined")
GCController.collectAll(); GCController.collectAll();
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
</defs> </defs>
<g id="g"/> <g id="g"/>
<text x="50" y="50" id="log"/> <text x="50" y="50" id="log"/>
<script xlink:href="../../resources/js-test.js"></script>
<script id="script"> <script id="script">
<![CDATA[ <![CDATA[
...@@ -33,25 +34,23 @@ function createAnimatedRectInstance() { ...@@ -33,25 +34,23 @@ function createAnimatedRectInstance() {
} }
function cleanup() { function cleanup() {
// Collect garbage before recording starting live node count, in case there are live elements from previous tests. collectGarbage(function() {
// FIXME: Unclear why two calls to collect() are required, see crbug.com/307614 var originalLiveElements = internals.numberOfLiveNodes();
GCController.collect();
GCController.collect();
var originalLiveElements = internals.numberOfLiveNodes();
while (g.hasChildNodes()) while (g.hasChildNodes())
g.removeChild(g.lastChild); g.removeChild(g.lastChild);
GCController.collect(); collectGarbage(function() {
// FIXME: Why 400 and not 200?
var liveDelta = originalLiveElements - internals.numberOfLiveNodes() - 400;
if (liveDelta == 0)
log("PASS");
else
log("FAIL: " + liveDelta + " extra live node(s)");
// FIXME: Why 400 and not 200? testRunner.notifyDone();
var liveDelta = originalLiveElements - internals.numberOfLiveNodes() - 400; });
if (liveDelta == 0) });
log("PASS");
else
log("FAIL: " + liveDelta + " extra live node(s)");
testRunner.notifyDone();
} }
function addMoreInstances() { function addMoreInstances() {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
</defs> </defs>
<g id="g"/> <g id="g"/>
<text x="50" y="50" id="log"/> <text x="50" y="50" id="log"/>
<script xlink:href="../../resources/js-test.js"></script>
<script id="script"> <script id="script">
<![CDATA[ <![CDATA[
...@@ -34,22 +35,23 @@ function createAnimatedRectInstance() { ...@@ -34,22 +35,23 @@ function createAnimatedRectInstance() {
function cleanup() { function cleanup() {
// Collect garbage before recording starting live node count, in case there are live elements from previous tests. // Collect garbage before recording starting live node count, in case there are live elements from previous tests.
GCController.collectAll(); collectGarbage(function() {
var originalLiveElements = internals.numberOfLiveNodes(); var originalLiveElements = internals.numberOfLiveNodes();
while (g.hasChildNodes()) while (g.hasChildNodes())
g.removeChild(g.lastChild); g.removeChild(g.lastChild);
GCController.collectAll(); collectGarbage(function() {
// This is 400 instead of 200 as it creates shadow tree elements.
var liveDelta = originalLiveElements - internals.numberOfLiveNodes() - 400;
if (liveDelta == 0)
log("PASS");
else
log("FAIL: " + liveDelta + " extra live node(s)");
// This is 400 instead of 200 as it creates shadow tree elements. testRunner.notifyDone();
var liveDelta = originalLiveElements - internals.numberOfLiveNodes() - 400; });
if (liveDelta == 0) });
log("PASS");
else
log("FAIL: " + liveDelta + " extra live node(s)");
testRunner.notifyDone();
} }
function startTest() { function startTest() {
......
<svg id="svg" xmlns="http://www.w3.org/2000/svg" onload="load()"> <svg id="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="load()">
<g id="g"/> <g id="g"/>
<text x="50" y="50" id="log"/> <text x="50" y="50" id="log"/>
<script xlink:href="../../resources/js-test.js"></script>
<script id="script"> <script id="script">
<![CDATA[ <![CDATA[
...@@ -32,21 +33,20 @@ function createAnimatedRect() { ...@@ -32,21 +33,20 @@ function createAnimatedRect() {
function cleanup() { function cleanup() {
// Collect garbage before recording starting live node count, in case there are live elements from previous tests. // Collect garbage before recording starting live node count, in case there are live elements from previous tests.
GCController.collectAll(); collectGarbage(function() {
var originalLiveElements = internals.numberOfLiveNodes(); var originalLiveElements = internals.numberOfLiveNodes();
while (g.hasChildNodes()) while (g.hasChildNodes())
g.removeChild(g.lastChild); g.removeChild(g.lastChild);
collectGarbage(function() {
GCController.collectAll(); var liveDelta = originalLiveElements - internals.numberOfLiveNodes() - 200;
if (liveDelta == 0)
var liveDelta = originalLiveElements - internals.numberOfLiveNodes() - 200; log("PASS");
if (liveDelta == 0) else
log("PASS"); log("FAIL: " + liveDelta + " extra live node(s)");
else testRunner.notifyDone();
log("FAIL: " + liveDelta + " extra live node(s)"); });
});
testRunner.notifyDone();
} }
function startTest() { function startTest() {
......
...@@ -1305,6 +1305,8 @@ void Heap::prepareForGC() ...@@ -1305,6 +1305,8 @@ void Heap::prepareForGC()
void Heap::collectGarbage(ThreadState::StackState stackState, GCType gcType) void Heap::collectGarbage(ThreadState::StackState stackState, GCType gcType)
{ {
if (gcType == ForcedForTesting && stackState != ThreadState::NoHeapPointersOnStack)
ThreadState::current()->setForcedForTesting(true);
ThreadState::current()->clearGCRequested(); ThreadState::current()->clearGCRequested();
GCScope gcScope(stackState); GCScope gcScope(stackState);
......
...@@ -235,6 +235,7 @@ ThreadState::ThreadState() ...@@ -235,6 +235,7 @@ ThreadState::ThreadState()
, m_atSafePoint(false) , m_atSafePoint(false)
, m_interruptors() , m_interruptors()
, m_gcRequested(false) , m_gcRequested(false)
, m_forcePreciseGCForTesting(false)
, m_sweepRequested(0) , m_sweepRequested(0)
, m_sweepInProgress(false) , m_sweepInProgress(false)
, m_noAllocationCount(0) , m_noAllocationCount(0)
...@@ -497,6 +498,26 @@ void ThreadState::clearGCRequested() ...@@ -497,6 +498,26 @@ void ThreadState::clearGCRequested()
m_gcRequested = false; m_gcRequested = false;
} }
void ThreadState::performPendingGC(StackState stackState)
{
if (stackState == NoHeapPointersOnStack && (gcRequested() || forcePreciseGCForTesting())) {
setForcedForTesting(false);
Heap::collectGarbage(NoHeapPointersOnStack);
}
}
void ThreadState::setForcedForTesting(bool value)
{
checkThread();
m_forcePreciseGCForTesting = value;
}
bool ThreadState::forcePreciseGCForTesting()
{
checkThread();
return m_forcePreciseGCForTesting;
}
bool ThreadState::isConsistentForGC() bool ThreadState::isConsistentForGC()
{ {
for (int i = 0; i < NumberOfHeaps; i++) { for (int i = 0; i < NumberOfHeaps; i++) {
...@@ -589,8 +610,7 @@ void ThreadState::resumeThreads() ...@@ -589,8 +610,7 @@ void ThreadState::resumeThreads()
void ThreadState::safePoint(StackState stackState) void ThreadState::safePoint(StackState stackState)
{ {
checkThread(); checkThread();
if (stackState == NoHeapPointersOnStack && gcRequested()) performPendingGC(stackState);
Heap::collectGarbage(NoHeapPointersOnStack);
m_stackState = stackState; m_stackState = stackState;
s_safePointBarrier->checkAndPark(this); s_safePointBarrier->checkAndPark(this);
m_stackState = HeapPointersOnStack; m_stackState = HeapPointersOnStack;
...@@ -628,8 +648,7 @@ void ThreadState::enterSafePoint(StackState stackState, void* scopeMarker) ...@@ -628,8 +648,7 @@ void ThreadState::enterSafePoint(StackState stackState, void* scopeMarker)
scopeMarker = adjustScopeMarkerForAdressSanitizer(scopeMarker); scopeMarker = adjustScopeMarkerForAdressSanitizer(scopeMarker);
#endif #endif
ASSERT(stackState == NoHeapPointersOnStack || scopeMarker); ASSERT(stackState == NoHeapPointersOnStack || scopeMarker);
if (stackState == NoHeapPointersOnStack && gcRequested()) performPendingGC(stackState);
Heap::collectGarbage(NoHeapPointersOnStack);
checkThread(); checkThread();
ASSERT(!m_atSafePoint); ASSERT(!m_atSafePoint);
m_atSafePoint = true; m_atSafePoint = true;
......
...@@ -278,6 +278,15 @@ public: ...@@ -278,6 +278,15 @@ public:
void setGCRequested(); void setGCRequested();
void clearGCRequested(); void clearGCRequested();
// Was the last GC forced for testing? This is set when garbage collection
// is forced for testing and there are pointers on the stack. It remains
// set until a garbage collection is triggered with no pointers on the stack.
// This is used for layout tests that trigger GCs and check if objects are
// dead at a given point in time. That only reliably works when we get
// precise GCs with no conservative stack scanning.
void setForcedForTesting(bool);
bool forcePreciseGCForTesting();
bool sweepRequested(); bool sweepRequested();
void setSweepRequested(); void setSweepRequested();
void clearSweepRequested(); void clearSweepRequested();
...@@ -497,6 +506,8 @@ private: ...@@ -497,6 +506,8 @@ private:
m_safePointScopeMarker = 0; m_safePointScopeMarker = 0;
} }
void performPendingGC(StackState);
// Finds the Blink HeapPage in this thread-specific heap // Finds the Blink HeapPage in this thread-specific heap
// corresponding to a given address. Return 0 if the address is // corresponding to a given address. Return 0 if the address is
// not contained in any of the pages. This does not consider // not contained in any of the pages. This does not consider
...@@ -540,6 +551,7 @@ private: ...@@ -540,6 +551,7 @@ private:
bool m_atSafePoint; bool m_atSafePoint;
Vector<Interruptor*> m_interruptors; Vector<Interruptor*> m_interruptors;
bool m_gcRequested; bool m_gcRequested;
bool m_forcePreciseGCForTesting;
volatile int m_sweepRequested; volatile int m_sweepRequested;
bool m_sweepInProgress; bool m_sweepInProgress;
size_t m_noAllocationCount; size_t m_noAllocationCount;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment