blob: 71fddaffd08549c83ef34e86cdd31e7cf03dc327 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "bindings/core/v8/V8GCController.h"
#include "bindings/core/v8/ActiveScriptWrappable.h"
#include "bindings/core/v8/RetainedDOMInfo.h"
#include "bindings/core/v8/V8AbstractEventListener.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8MutationObserver.h"
#include "bindings/core/v8/V8Node.h"
#include "bindings/core/v8/V8ScriptRunner.h"
#include "bindings/core/v8/WrapperTypeInfo.h"
#include "core/dom/Attr.h"
#include "core/dom/Document.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/TemplateContentDocumentFragment.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/html/HTMLTemplateElement.h"
#include "core/html/imports/HTMLImportsController.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "core/svg/SVGElement.h"
#include "platform/Histogram.h"
#include "platform/TraceEvent.h"
#include "wtf/Partitions.h"
#include "wtf/Vector.h"
#include <algorithm>
namespace blink {
// FIXME: This should use opaque GC roots.
static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
{
ASSERT(node->hasEventListeners());
EventListenerIterator iterator(node);
while (EventListener* listener = iterator.nextListener()) {
if (listener->type() != EventListener::JSEventListenerType)
continue;
V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
if (!v8listener->hasExistingListenerObject())
continue;
isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle()));
}
}
Node* V8GCController::opaqueRootForGC(v8::Isolate*, Node* node)
{
ASSERT(node);
if (node->inDocument()) {
Document& document = node->document();
if (HTMLImportsController* controller = document.importsController())
return controller->master();
return &document;
}
if (node->isAttributeNode()) {
Node* ownerElement = toAttr(node)->ownerElement();
if (!ownerElement)
return node;
node = ownerElement;
}
while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
node = parent;
return node;
}
class MinorGCUnmodifiedWrapperVisitor : public v8::PersistentHandleVisitor {
public:
explicit MinorGCUnmodifiedWrapperVisitor(v8::Isolate* isolate)
: m_isolate(isolate)
{ }
void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) override
{
if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId) {
return;
}
// MinorGC does not collect objects because it may be expensive to
// update references during minorGC
if (classId == WrapperTypeInfo::ObjectClassId) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(m_isolate, v8::Persistent<v8::Object>::Cast(*value));
ASSERT(V8DOMWrapper::hasInternalFieldsSet(wrapper));
if (toWrapperTypeInfo(wrapper)->hasPendingActivity(wrapper)) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
if (classId == WrapperTypeInfo::NodeClassId) {
ASSERT(V8Node::hasInstance(wrapper, m_isolate));
Node* node = V8Node::toImpl(wrapper);
if (node->hasEventListeners()) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
// FIXME: Remove the special handling for SVG elements.
// We currently can't collect SVG Elements from minor gc, as we have
// strong references from SVG property tear-offs keeping context SVG element alive.
if (node->isSVGElement()) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
}
}
private:
v8::Isolate* m_isolate;
};
class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
public:
explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
: m_isolate(isolate)
, m_domObjectsWithPendingActivity(0)
, m_liveRootGroupIdSet(false)
, m_constructRetainedObjectInfos(constructRetainedObjectInfos)
{
}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) override
{
if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
return;
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(m_isolate, v8::Persistent<v8::Object>::Cast(*value));
ASSERT(V8DOMWrapper::hasInternalFieldsSet(wrapper));
const WrapperTypeInfo* type = toWrapperTypeInfo(wrapper);
if (type->hasPendingActivity(wrapper)) {
// If you hit this assert, you'll need to add a [DependentiLifetime]
// extended attribute to the DOM interface. A DOM interface that
// overrides hasPendingActivity must be marked as [DependentiLifetime].
RELEASE_ASSERT(!value->IsIndependent());
m_isolate->SetObjectGroupId(*value, liveRootId());
++m_domObjectsWithPendingActivity;
}
if (value->IsIndependent())
return;
if (classId == WrapperTypeInfo::NodeClassId) {
ASSERT(V8Node::hasInstance(wrapper, m_isolate));
Node* node = V8Node::toImpl(wrapper);
if (node->hasEventListeners())
addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
Node* root = V8GCController::opaqueRootForGC(m_isolate, node);
m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
if (m_constructRetainedObjectInfos)
m_groupsWhichNeedRetainerInfo.append(root);
} else if (classId == WrapperTypeInfo::ObjectClassId) {
type->visitDOMWrapper(m_isolate, toScriptWrappable(wrapper), v8::Persistent<v8::Object>::Cast(*value));
} else {
ASSERT_NOT_REACHED();
}
}
void notifyFinished()
{
if (!m_constructRetainedObjectInfos)
return;
std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
Node* alreadyAdded = 0;
v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
Node* root = m_groupsWhichNeedRetainerInfo[i];
if (root != alreadyAdded) {
profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
alreadyAdded = root;
}
}
if (m_liveRootGroupIdSet)
profiler->SetRetainedObjectInfo(liveRootId(), new ActiveDOMObjectsInfo(m_domObjectsWithPendingActivity));
}
private:
v8::UniqueId liveRootId()
{
const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
v8::UniqueId id(*idPointer);
if (!m_liveRootGroupIdSet) {
m_isolate->SetObjectGroupId(liveRoot, id);
m_liveRootGroupIdSet = true;
++m_domObjectsWithPendingActivity;
}
return id;
}
v8::Isolate* m_isolate;
// v8 guarantees that Blink will not regain control while a v8 GC runs
// (=> no Oilpan GCs will be triggered), hence raw, untraced members
// can safely be kept here.
Vector<RawPtrWillBeUntracedMember<Node>> m_groupsWhichNeedRetainerInfo;
int m_domObjectsWithPendingActivity;
bool m_liveRootGroupIdSet;
bool m_constructRetainedObjectInfos;
};
static unsigned long long usedHeapSize(v8::Isolate* isolate)
{
v8::HeapStatistics heapStatistics;
isolate->GetHeapStatistics(&heapStatistics);
return heapStatistics.used_heap_size();
}
namespace {
void visitWeakHandlesForMinorGC(v8::Isolate* isolate)
{
MinorGCUnmodifiedWrapperVisitor visitor(isolate);
isolate->VisitWeakHandles(&visitor);
}
void objectGroupingForMajorGC(v8::Isolate* isolate, bool constructRetainedObjectInfos)
{
MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
isolate->VisitHandlesWithClassIds(&visitor);
visitor.notifyFinished();
}
void gcPrologueForMajorGC(v8::Isolate* isolate, bool constructRetainedObjectInfos)
{
objectGroupingForMajorGC(isolate, constructRetainedObjectInfos);
}
} // namespace
void V8GCController::gcPrologue(v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags)
{
if (isMainThread())
ScriptForbiddenScope::enter();
// TODO(haraken): A GC callback is not allowed to re-enter V8. This means
// that it's unsafe to run Oilpan's GC in the GC callback because it may
// run finalizers that call into V8. To avoid the risk, we should post
// a task to schedule the Oilpan's GC.
// (In practice, there is no finalizer that calls into V8 and thus is safe.)
v8::HandleScope scope(isolate);
switch (type) {
case v8::kGCTypeScavenge:
if (ThreadState::current())
ThreadState::current()->willStartV8GC(BlinkGC::V8MinorGC);
TRACE_EVENT_BEGIN1("devtools.timeline,v8", "MinorGC", "usedHeapSizeBefore", usedHeapSize(isolate));
visitWeakHandlesForMinorGC(isolate);
break;
case v8::kGCTypeMarkSweepCompact:
if (ThreadState::current())
ThreadState::current()->willStartV8GC(BlinkGC::V8MajorGC);
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC", "usedHeapSizeBefore", usedHeapSize(isolate), "type", "atomic pause");
gcPrologueForMajorGC(isolate, flags & v8::kGCCallbackFlagConstructRetainedObjectInfos);
break;
case v8::kGCTypeIncrementalMarking:
if (ThreadState::current())
ThreadState::current()->willStartV8GC(BlinkGC::V8MajorGC);
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC", "usedHeapSizeBefore", usedHeapSize(isolate), "type", "incremental marking");
gcPrologueForMajorGC(isolate, flags & v8::kGCCallbackFlagConstructRetainedObjectInfos);
break;
case v8::kGCTypeProcessWeakCallbacks:
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC", "usedHeapSizeBefore", usedHeapSize(isolate), "type", "weak processing");
break;
default:
ASSERT_NOT_REACHED();
}
}
void V8GCController::gcEpilogue(v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags)
{
switch (type) {
case v8::kGCTypeScavenge:
TRACE_EVENT_END1("devtools.timeline,v8", "MinorGC", "usedHeapSizeAfter", usedHeapSize(isolate));
// TODO(haraken): Remove this. See the comment in gcPrologue.
if (ThreadState::current())
ThreadState::current()->scheduleV8FollowupGCIfNeeded(BlinkGC::V8MinorGC);
break;
case v8::kGCTypeMarkSweepCompact:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter", usedHeapSize(isolate));
if (ThreadState::current())
ThreadState::current()->scheduleV8FollowupGCIfNeeded(BlinkGC::V8MajorGC);
break;
case v8::kGCTypeIncrementalMarking:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter", usedHeapSize(isolate));
break;
case v8::kGCTypeProcessWeakCallbacks:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter", usedHeapSize(isolate));
break;
default:
ASSERT_NOT_REACHED();
}
if (isMainThread())
ScriptForbiddenScope::exit();
// v8::kGCCallbackFlagForced forces a Blink heap garbage collection
// when a garbage collection was forced from V8. This is either used
// for tests that force GCs from JavaScript to verify that objects die
// when expected.
if (flags & v8::kGCCallbackFlagForced) {
// This single GC is not enough for two reasons:
// (1) The GC is not precise because the GC scans on-stack pointers conservatively.
// (2) One GC is not enough to break a chain of persistent handles. It's possible that
// some heap allocated objects own objects that contain persistent handles
// pointing to other heap allocated objects. To break the chain, we need multiple GCs.
//
// Regarding (1), we force a precise GC at the end of the current event loop. So if you want
// to collect all garbage, you need to wait until the next event loop.
// Regarding (2), it would be OK in practice to trigger only one GC per gcEpilogue, because
// GCController.collectAll() forces multiple V8's GC.
Heap::collectGarbage(BlinkGC::HeapPointersOnStack, BlinkGC::GCWithSweep, BlinkGC::ForcedGC);
// Forces a precise GC at the end of the current event loop.
if (ThreadState::current()) {
RELEASE_ASSERT(!ThreadState::current()->isInGC());
ThreadState::current()->setGCState(ThreadState::FullGCScheduled);
}
}
// v8::kGCCallbackFlagCollectAllAvailableGarbage is used when V8 handles
// low memory notifications.
if (flags & v8::kGCCallbackFlagCollectAllAvailableGarbage) {
// This single GC is not enough. See the above comment.
Heap::collectGarbage(BlinkGC::HeapPointersOnStack, BlinkGC::GCWithSweep, BlinkGC::ForcedGC);
// Do not force a precise GC at the end of the current event loop.
// According to UMA stats, the collection rate of the precise GC
// scheduled at the end of the low memory handling is extremely low,
// because the above conservative GC is sufficient for collecting
// most objects. So we intentionally don't schedule a precise GC here.
}
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data", InspectorUpdateCountersEvent::data());
}
void V8GCController::collectGarbage(v8::Isolate* isolate)
{
v8::HandleScope handleScope(isolate);
RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create(isolate));
ScriptState::Scope scope(scriptState.get());
V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
scriptState->disposePerContextData();
}
void V8GCController::collectAllGarbageForTesting(v8::Isolate* isolate)
{
for (unsigned i = 0; i < 5; i++)
isolate->RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection);
}
void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate)
{
// TODO(haraken): Oilpan should report the amount of memory used
// by DOM nodes as well. Currently Partitions::currentDOMMemoryUsage()
// just returns 0.
#if !ENABLE(OILPAN)
if (!isMainThread())
return;
static size_t lastUsageReportedToV8 = 0;
size_t currentUsage = WTF::Partitions::currentDOMMemoryUsage();
int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8);
isolate->AdjustAmountOfExternalAllocatedMemory(diff);
lastUsageReportedToV8 = currentUsage;
#endif
}
class DOMWrapperTracer : public v8::PersistentHandleVisitor {
public:
explicit DOMWrapperTracer(Visitor* visitor)
: m_visitor(visitor)
{
}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) override
{
if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
return;
const v8::Persistent<v8::Object>& wrapper = v8::Persistent<v8::Object>::Cast(*value);
if (m_visitor)
toWrapperTypeInfo(wrapper)->trace(m_visitor, toScriptWrappable(wrapper));
}
private:
Visitor* m_visitor;
};
void V8GCController::traceDOMWrappers(v8::Isolate* isolate, Visitor* visitor)
{
DOMWrapperTracer tracer(visitor);
isolate->VisitHandlesWithClassIds(&tracer);
}
class PendingActivityVisitor : public v8::PersistentHandleVisitor {
public:
PendingActivityVisitor(v8::Isolate* isolate, ExecutionContext* executionContext)
: m_isolate(isolate)
, m_executionContext(executionContext)
, m_pendingActivityFound(false)
{
}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) override
{
// If we have already found any wrapper that has a pending activity,
// we don't need to check other wrappers.
if (m_pendingActivityFound)
return;
if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
return;
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(m_isolate, v8::Persistent<v8::Object>::Cast(*value));
ASSERT(V8DOMWrapper::hasInternalFieldsSet(wrapper));
// The ExecutionContext check is heavy, so it should be done at the last.
if (toWrapperTypeInfo(wrapper)->hasPendingActivity(wrapper)
// TODO(haraken): Currently we don't have a way to get a creation
// context from a wrapper. We should implement the way and enable
// the following condition.
//
// This condition affects only compositor workers, where one isolate
// is shared by multiple workers. If we don't have the condition,
// a worker object for a compositor worker doesn't get collected
// until all compositor workers in the same isolate lose pending
// activities. In other words, not having the condition delays
// destruction of a worker object of a compositor worker.
//
/* && toExecutionContext(wrapper->creationContext()) == m_executionContext */
)
m_pendingActivityFound = true;
}
bool pendingActivityFound() const { return m_pendingActivityFound; }
private:
v8::Isolate* m_isolate;
RawPtrWillBePersistent<ExecutionContext> m_executionContext;
bool m_pendingActivityFound;
};
bool V8GCController::hasPendingActivity(v8::Isolate* isolate, ExecutionContext* executionContext)
{
// V8GCController::hasPendingActivity is used only when a worker checks if
// the worker contains any wrapper that has pending activities.
ASSERT(!isMainThread());
DEFINE_THREAD_SAFE_STATIC_LOCAL(CustomCountHistogram, scanPendingActivityHistogram, new CustomCountHistogram("Blink.ScanPendingActivityDuration", 1, 1000, 50));
double startTime = WTF::currentTimeMS();
v8::HandleScope scope(isolate);
PendingActivityVisitor visitor(isolate, executionContext);
toIsolate(executionContext)->VisitHandlesWithClassIds(&visitor);
scanPendingActivityHistogram.count(static_cast<int>(WTF::currentTimeMS() - startTime));
return visitor.pendingActivityFound();
}
} // namespace blink