blob: 0b6b84dce4cca97aadd19421007015c7aee97eca [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 <algorithm>
#include <unordered_map>
#include <unordered_set>
#include "bindings/core/v8/ActiveScriptWrappable.h"
#include "bindings/core/v8/RetainedDOMInfo.h"
#include "bindings/core/v8/ScriptSourceCode.h"
#include "bindings/core/v8/V8AbstractEventListener.h"
#include "bindings/core/v8/V8BindingForCore.h"
#include "bindings/core/v8/V8Node.h"
#include "bindings/core/v8/V8ScriptRunner.h"
#include "core/dom/Attr.h"
#include "core/dom/Element.h"
#include "core/dom/Node.h"
#include "core/html/imports/HTMLImportsController.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "platform/Histogram.h"
#include "platform/bindings/ScriptWrappableVisitor.h"
#include "platform/bindings/WrapperTypeInfo.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/wtf/allocator/Partitions.h"
#include "public/platform/BlameContext.h"
#include "public/platform/Platform.h"
namespace blink {
Node* V8GCController::OpaqueRootForGC(v8::Isolate*, Node* node) {
DCHECK(node);
if (node->isConnected())
return &node->GetDocument().MasterDocument();
if (node->IsAttributeNode()) {
Node* owner_element = ToAttr(node)->ownerElement();
if (!owner_element)
return node;
node = owner_element;
}
while (Node* parent = node->ParentOrShadowHostOrTemplateHostNode())
node = parent;
return node;
}
class MinorGCUnmodifiedWrapperVisitor : public v8::PersistentHandleVisitor {
public:
explicit MinorGCUnmodifiedWrapperVisitor(v8::Isolate* isolate)
: isolate_(isolate) {}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) override {
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId) {
return;
}
// MinorGC does not collect objects because it may be expensive to
// update references during minorGC
if (class_id == WrapperTypeInfo::kObjectClassId) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(
isolate_, v8::Persistent<v8::Object>::Cast(*value));
DCHECK(V8DOMWrapper::HasInternalFieldsSet(wrapper));
if (ToWrapperTypeInfo(wrapper)->IsActiveScriptWrappable() &&
ToScriptWrappable(wrapper)->HasPendingActivity()) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
if (class_id == WrapperTypeInfo::kNodeClassId) {
DCHECK(V8Node::hasInstance(wrapper, 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* isolate_;
};
class HeapSnaphotWrapperVisitor : public ScriptWrappableVisitor,
public v8::PersistentHandleVisitor {
public:
explicit HeapSnaphotWrapperVisitor(v8::Isolate* isolate)
: ScriptWrappableVisitor(isolate),
current_parent_(nullptr),
only_trace_single_level_(false),
first_script_wrappable_traced_(false) {
DCHECK(IsMainThread());
}
// Collect interesting V8 roots for the heap snapshot. Currently these are
// DOM nodes.
void CollectV8Roots() { isolate()->VisitHandlesWithClassIds(this); }
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) override {
if (class_id != WrapperTypeInfo::kNodeClassId)
return;
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(
isolate(), v8::Persistent<v8::Object>::Cast(*value));
DCHECK(V8Node::hasInstance(wrapper, isolate()));
Node* node = V8Node::ToImpl(wrapper);
Node* root = V8GCController::OpaqueRootForGC(isolate(), node);
nodes_requiring_tracing_[root].push_back(node);
}
// Trace through the blink heap to find all V8 wrappers reachable from
// ActiveScriptWrappables. Also collect retainer edges on the way.
void TracePendingActivities() {
CHECK(found_v8_wrappers_.empty());
current_parent_ = nullptr;
TracePrologue();
ActiveScriptWrappableBase::TraceActiveScriptWrappables(isolate(), this);
AdvanceTracing(
0,
v8::EmbedderHeapTracer::AdvanceTracingActions(
v8::EmbedderHeapTracer::ForceCompletionAction::FORCE_COMPLETION));
// Abort instead of Epilogue as we want to finish synchronously.
AbortTracing();
groups_.push_back(
std::make_pair(new PausableObjectsInfo(found_v8_wrappers_.size()),
std::move(found_v8_wrappers_)));
}
// Trace through the blink heap to find all V8 wrappers reachable from any
// of the collected roots. Also collect retainer edges on the way.
void TraceV8Roots() {
for (auto& group_pair : nodes_requiring_tracing_) {
v8::HeapProfiler::RetainerChildren group_children;
for (auto& node : group_pair.second) {
// Ignore the actual wrappers reachable as we only want to create
// groups of DOM nodes. The method is still used to collect edges
// though.
FindV8WrappersDirectlyReachableFrom(node);
group_children.insert(PersistentForWrappable(node));
}
groups_.push_back(std::make_pair(new RetainedDOMInfo(group_pair.first),
std::move(group_children)));
}
}
v8::HeapProfiler::RetainerEdges Edges() { return std::move(edges_); }
v8::HeapProfiler::RetainerGroups Groups() { return std::move(groups_); }
// ScriptWrappableVisitor overrides.
void DispatchTraceWrappers(const TraceWrapperBase* traceable) const final {
if (!only_trace_single_level_ || !traceable->IsScriptWrappable() ||
!reinterpret_cast<const ScriptWrappable*>(traceable)
->ContainsWrapper() ||
!first_script_wrappable_traced_) {
first_script_wrappable_traced_ = true;
traceable->TraceWrappers(this);
}
}
protected:
// ScriptWrappableVisitor override.
void Visit(
const TraceWrapperV8Reference<v8::Value>& traced_wrapper) const final {
const v8::PersistentBase<v8::Value>* value = &traced_wrapper.Get();
if (current_parent_ && current_parent_ != value)
edges_.push_back(std::make_pair(current_parent_, value));
found_v8_wrappers_.insert(value);
}
void Visit(DOMWrapperMap<ScriptWrappable>*,
const ScriptWrappable* key) const final {
// The found_v8_wrappers are PersistentBase pointers. We only mark the
// main world wrapper as we cannot properly record DOMWrapperMap values.
// This means that edges from the isolated worlds are missing in the
// snapshot. This will be fixed with crbug.com/749490.
}
private:
inline v8::PersistentBase<v8::Value>* PersistentForWrappable(
ScriptWrappable* wrappable) {
return &v8::Persistent<v8::Value>::Cast(*wrappable->RawMainWorldWrapper());
}
v8::HeapProfiler::RetainerChildren FindV8WrappersDirectlyReachableFrom(
Node* traceable) {
CHECK(found_v8_wrappers_.empty());
WTF::AutoReset<bool> scope(&only_trace_single_level_, true);
first_script_wrappable_traced_ = false;
current_parent_ = PersistentForWrappable(traceable);
TracePrologue();
traceable->GetWrapperTypeInfo()->TraceWrappers(this, traceable);
AdvanceTracing(
0,
v8::EmbedderHeapTracer::AdvanceTracingActions(
v8::EmbedderHeapTracer::ForceCompletionAction::FORCE_COMPLETION));
// Abort instead of Epilogue as we want to finish synchronously.
AbortTracing();
return std::move(found_v8_wrappers_);
}
// Input obtained from |VisitPersistentHandle|.
std::unordered_map<Node*, std::vector<Node*>> nodes_requiring_tracing_;
// Temporaries used for tracing a single Node.
const v8::PersistentBase<v8::Value>* current_parent_;
bool only_trace_single_level_;
mutable bool first_script_wrappable_traced_;
mutable v8::HeapProfiler::RetainerChildren found_v8_wrappers_;
// Out variables
mutable v8::HeapProfiler::RetainerEdges edges_;
mutable v8::HeapProfiler::RetainerGroups groups_;
};
// The function |getRetainerInfos| processing all handles on the blink heap,
// logically grouping together DOM trees (attached and detached) and pending
// activities, while at the same time finding parent to child relationships
// for non-Node wrappers. Since we are processing *all* handles there is no
// way we miss out on a handle. V8 will figure out the liveness information
// for the provided information itself.
v8::HeapProfiler::RetainerInfos V8GCController::GetRetainerInfos(
v8::Isolate* isolate) {
V8PerIsolateData::TemporaryScriptWrappableVisitorScope scope(
isolate, std::unique_ptr<HeapSnaphotWrapperVisitor>(
new HeapSnaphotWrapperVisitor(isolate)));
HeapSnaphotWrapperVisitor* tracer =
reinterpret_cast<HeapSnaphotWrapperVisitor*>(scope.CurrentVisitor());
tracer->CollectV8Roots();
tracer->TraceV8Roots();
tracer->TracePendingActivities();
return v8::HeapProfiler::RetainerInfos{tracer->Groups(), tracer->Edges()};
}
static unsigned long long UsedHeapSize(v8::Isolate* isolate) {
v8::HeapStatistics heap_statistics;
isolate->GetHeapStatistics(&heap_statistics);
return heap_statistics.used_heap_size();
}
namespace {
void VisitWeakHandlesForMinorGC(v8::Isolate* isolate) {
MinorGCUnmodifiedWrapperVisitor visitor(isolate);
isolate->VisitWeakHandles(&visitor);
}
} // namespace
void V8GCController::GcPrologue(v8::Isolate* isolate,
v8::GCType type,
v8::GCCallbackFlags flags) {
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kGcPrologue);
ScriptForbiddenScope::Enter();
// Attribute garbage collection to the all frames instead of a specific
// frame.
if (BlameContext* blame_context =
Platform::Current()->GetTopLevelBlameContext())
blame_context->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::kV8MinorGC);
TRACE_EVENT_BEGIN1("devtools.timeline,v8", "MinorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate));
VisitWeakHandlesForMinorGC(isolate);
break;
case v8::kGCTypeMarkSweepCompact:
if (ThreadState::Current())
ThreadState::Current()->WillStartV8GC(BlinkGC::kV8MajorGC);
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate), "type",
"atomic pause");
break;
case v8::kGCTypeIncrementalMarking:
if (ThreadState::Current())
ThreadState::Current()->WillStartV8GC(BlinkGC::kV8MajorGC);
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate), "type",
"incremental marking");
break;
case v8::kGCTypeProcessWeakCallbacks:
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate), "type",
"weak processing");
break;
default:
NOTREACHED();
}
}
namespace {
void UpdateCollectedPhantomHandles(v8::Isolate* isolate) {
ThreadHeapStats& heap_stats = ThreadState::Current()->Heap().HeapStats();
size_t count = isolate->NumberOfPhantomHandleResetsSinceLastCall();
heap_stats.DecreaseWrapperCount(count);
heap_stats.IncreaseCollectedWrapperCount(count);
}
} // namespace
void V8GCController::GcEpilogue(v8::Isolate* isolate,
v8::GCType type,
v8::GCCallbackFlags flags) {
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kGcEpilogue);
UpdateCollectedPhantomHandles(isolate);
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::kV8MinorGC);
break;
case v8::kGCTypeMarkSweepCompact:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter",
UsedHeapSize(isolate));
if (ThreadState::Current())
ThreadState::Current()->ScheduleV8FollowupGCIfNeeded(
BlinkGC::kV8MajorGC);
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:
NOTREACHED();
}
ScriptForbiddenScope::Exit();
if (BlameContext* blame_context =
Platform::Current()->GetTopLevelBlameContext())
blame_context->Leave();
ThreadState* current_thread_state = ThreadState::Current();
if (current_thread_state && !current_thread_state->IsGCForbidden()) {
// 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.
current_thread_state->CollectGarbage(BlinkGC::kHeapPointersOnStack,
BlinkGC::kGCWithSweep,
BlinkGC::kForcedGC);
// Forces a precise GC at the end of the current event loop.
CHECK(!current_thread_state->IsInGC());
current_thread_state->SetGCState(ThreadState::kFullGCScheduled);
}
// v8::kGCCallbackFlagCollectAllAvailableGarbage is used when V8 handles
// low memory notifications.
if ((flags & v8::kGCCallbackFlagCollectAllAvailableGarbage) ||
(flags & v8::kGCCallbackFlagCollectAllExternalMemory)) {
// This single GC is not enough. See the above comment.
current_thread_state->CollectGarbage(BlinkGC::kHeapPointersOnStack,
BlinkGC::kGCWithSweep,
BlinkGC::kForcedGC);
// The conservative GC might have left floating garbage. Schedule
// precise GC to ensure that we collect all available garbage.
current_thread_state->SchedulePreciseGC();
}
// Schedules a precise GC for the next idle time period.
if (flags & v8::kGCCallbackScheduleIdleGarbageCollection) {
current_thread_state->ScheduleIdleGC();
}
}
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data",
InspectorUpdateCountersEvent::Data());
}
void V8GCController::CollectGarbage(v8::Isolate* isolate, bool only_minor_gc) {
v8::HandleScope handle_scope(isolate);
scoped_refptr<ScriptState> script_state = ScriptState::Create(
v8::Context::New(isolate),
DOMWrapperWorld::Create(isolate,
DOMWrapperWorld::WorldType::kGarbageCollector));
ScriptState::Scope scope(script_state.get());
StringBuilder builder;
builder.Append("if (gc) gc(");
builder.Append(only_minor_gc ? "true" : "false");
builder.Append(")");
V8ScriptRunner::CompileAndRunInternalScript(
isolate, script_state.get(),
ScriptSourceCode(builder.ToString(), ScriptSourceLocationType::kInternal,
nullptr, KURL(), TextPosition()));
script_state->DisposePerContextData();
}
void V8GCController::CollectAllGarbageForTesting(v8::Isolate* isolate) {
for (unsigned i = 0; i < 5; i++)
isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
}
class DOMWrapperTracer : public v8::PersistentHandleVisitor {
public:
explicit DOMWrapperTracer(Visitor* visitor) : visitor_(visitor) {
DCHECK(visitor_);
}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) override {
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId)
return;
const v8::Persistent<v8::Object>& wrapper =
v8::Persistent<v8::Object>::Cast(*value);
if (ScriptWrappable* script_wrappable = ToScriptWrappable(wrapper))
ToWrapperTypeInfo(wrapper)->Trace(visitor_, script_wrappable);
}
private:
Visitor* 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* execution_context)
: isolate_(isolate),
execution_context_(execution_context),
pending_activity_found_(false) {}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) override {
// If we have already found any wrapper that has a pending activity,
// we don't need to check other wrappers.
if (pending_activity_found_)
return;
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId)
return;
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(
isolate_, v8::Persistent<v8::Object>::Cast(*value));
DCHECK(V8DOMWrapper::HasInternalFieldsSet(wrapper));
// The ExecutionContext check is heavy, so it should be done at the last.
if (ToWrapperTypeInfo(wrapper)->IsActiveScriptWrappable() &&
ToScriptWrappable(wrapper)->HasPendingActivity()) {
// See the comment in MajorGCWrapperVisitor::VisitPersistentHandle.
ExecutionContext* context =
ToExecutionContext(wrapper->CreationContext());
if (context == execution_context_ && context &&
!context->IsContextDestroyed())
pending_activity_found_ = true;
}
}
bool PendingActivityFound() const { return pending_activity_found_; }
private:
v8::Isolate* isolate_;
Persistent<ExecutionContext> execution_context_;
bool pending_activity_found_;
};
bool V8GCController::HasPendingActivity(v8::Isolate* isolate,
ExecutionContext* execution_context) {
// V8GCController::hasPendingActivity is used only when a worker checks if
// the worker contains any wrapper that has pending activities.
DCHECK(!IsMainThread());
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scan_pending_activity_histogram,
("Blink.ScanPendingActivityDuration", 1, 1000, 50));
double start_time = WTF::CurrentTimeMS();
v8::HandleScope scope(isolate);
PendingActivityVisitor visitor(isolate, execution_context);
ToIsolate(execution_context)->VisitHandlesWithClassIds(&visitor);
scan_pending_activity_histogram.Count(
static_cast<int>(WTF::CurrentTimeMS() - start_time));
return visitor.PendingActivityFound();
}
} // namespace blink