Commit fc84822a authored by Ulan Degenbaev's avatar Ulan Degenbaev Committed by Commit Bot

Initial handling of bloated V8 heaps near the heap limit.

This patch adds a new Blink feature flag - BloatedRendererDetection.
When the flag is enabled, Blink uses the new V8 API to install
a callback that is invoked when V8's heap reaches the heap limit.

The callback notifies the BloatedRendererDetector controller that in
future will dispatch a message to the browser process for reloading the
tab and showing an infobar explaining that the tab ran out of memory.

The patch also adds a new UMA histograms
- "BloatedRenderer.V8.NearV8HeapLimitHandling"

Bug: 835806
Change-Id: I2f1bcefaaf8a553517de9a1bb2dc9d084f4eca4b
Reviewed-on: https://chromium-review.googlesource.com/1024033
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: default avatarMark Pearson <mpearson@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#558275}
parent 726e197e
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <memory> #include <memory>
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_thread.h" #include "third_party/blink/public/platform/web_thread.h"
#include "third_party/blink/renderer/bindings/core/v8/binding_security.h" #include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
...@@ -87,6 +88,69 @@ static void ReportOOMErrorInMainThread(const char* location, bool is_js_heap) { ...@@ -87,6 +88,69 @@ static void ReportOOMErrorInMainThread(const char* location, bool is_js_heap) {
OOM_CRASH(); OOM_CRASH();
} }
namespace {
// Set to BloatedRendererDetector::OnNearV8HeapLimitOnMainThread during startup.
static NearV8HeapLimitCallback g_near_heap_limit_on_main_thread_callback_ =
nullptr;
void Record(NearV8HeapLimitHandling handling) {
UMA_HISTOGRAM_ENUMERATION("BloatedRenderer.V8.NearV8HeapLimitHandling",
handling);
}
size_t IncreaseV8HeapLimit(size_t v8_heap_limit) {
// The heap limit for a bloated page should be increased to avoid immediate
// OOM crash. The exact amount is not important, it should be sufficiently
// large to give enough time for the browser process to reload the page.
// Increase the heap limit by 25%.
return v8_heap_limit + v8_heap_limit / 4;
}
size_t NearHeapLimitCallbackOnMainThread(void* isolate,
size_t current_heap_limit,
size_t initial_heap_limit) {
V8PerIsolateData* per_isolate_data =
V8PerIsolateData::From(reinterpret_cast<v8::Isolate*>(isolate));
if (per_isolate_data->IsNearV8HeapLimitHandled()) {
// Ignore all calls after the first one.
return current_heap_limit;
}
per_isolate_data->HandledNearV8HeapLimit();
if (current_heap_limit != initial_heap_limit) {
Record(NearV8HeapLimitHandling::kIgnoredDueToChangedHeapLimit);
return current_heap_limit;
}
NearV8HeapLimitHandling handling =
g_near_heap_limit_on_main_thread_callback_();
Record(handling);
return (handling == NearV8HeapLimitHandling::kForwardedToBrowser)
? IncreaseV8HeapLimit(current_heap_limit)
: current_heap_limit;
}
size_t NearHeapLimitCallbackOnWorkerThread(void* isolate,
size_t current_heap_limit,
size_t initial_heap_limit) {
V8PerIsolateData* per_isolate_data =
V8PerIsolateData::From(reinterpret_cast<v8::Isolate*>(isolate));
if (per_isolate_data->IsNearV8HeapLimitHandled()) {
// Ignore all calls after the first one.
return current_heap_limit;
}
per_isolate_data->HandledNearV8HeapLimit();
Record(NearV8HeapLimitHandling::kIgnoredDueToWorker);
return current_heap_limit;
}
} // anonymous namespace
void V8Initializer::SetNearV8HeapLimitOnMainThreadCallback(
NearV8HeapLimitCallback callback) {
g_near_heap_limit_on_main_thread_callback_ = callback;
}
static String ExtractMessageForConsole(v8::Isolate* isolate, static String ExtractMessageForConsole(v8::Isolate* isolate,
v8::Local<v8::Value> data) { v8::Local<v8::Value> data) {
if (V8DOMWrapper::IsWrapper(isolate, data)) { if (V8DOMWrapper::IsWrapper(isolate, data)) {
...@@ -619,6 +683,12 @@ void V8Initializer::InitializeMainThread(const intptr_t* reference_table) { ...@@ -619,6 +683,12 @@ void V8Initializer::InitializeMainThread(const intptr_t* reference_table) {
InitializeV8Common(isolate); InitializeV8Common(isolate);
isolate->SetOOMErrorHandler(ReportOOMErrorInMainThread); isolate->SetOOMErrorHandler(ReportOOMErrorInMainThread);
if (RuntimeEnabledFeatures::BloatedRendererDetectionEnabled()) {
DCHECK(g_near_heap_limit_on_main_thread_callback_);
isolate->AddNearHeapLimitCallback(NearHeapLimitCallbackOnMainThread,
isolate);
}
isolate->SetFatalErrorHandler(ReportFatalErrorInMainThread); isolate->SetFatalErrorHandler(ReportFatalErrorInMainThread);
isolate->AddMessageListenerWithErrorLevel( isolate->AddMessageListenerWithErrorLevel(
MessageHandlerInMainThread, MessageHandlerInMainThread,
...@@ -686,6 +756,10 @@ void V8Initializer::InitializeWorker(v8::Isolate* isolate) { ...@@ -686,6 +756,10 @@ void V8Initializer::InitializeWorker(v8::Isolate* isolate) {
isolate->SetStackLimit(reinterpret_cast<uintptr_t>(&here) - isolate->SetStackLimit(reinterpret_cast<uintptr_t>(&here) -
kWorkerMaxStackSize); kWorkerMaxStackSize);
isolate->SetPromiseRejectCallback(PromiseRejectHandlerInWorker); isolate->SetPromiseRejectCallback(PromiseRejectHandlerInWorker);
if (RuntimeEnabledFeatures::BloatedRendererDetectionEnabled()) {
isolate->AddNearHeapLimitCallback(NearHeapLimitCallbackOnWorkerThread,
isolate);
}
} }
} // namespace blink } // namespace blink
...@@ -32,10 +32,32 @@ ...@@ -32,10 +32,32 @@
namespace blink { namespace blink {
// Specifies how the near V8 heap limit event was handled by the callback.
// This enum is also used for UMA histogram recording. It must be kept in sync
// with the corresponding enum in tools/metrics/histograms/enums.xml. See that
// enum for the detailed description of each case.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class NearV8HeapLimitHandling {
kForwardedToBrowser = 0,
kIgnoredDueToSmallUptime = 1,
kIgnoredDueToChangedHeapLimit = 2,
kIgnoredDueToWorker = 3,
kMaxValue = kIgnoredDueToWorker
};
// A callback function called when V8 reaches the heap limit.
using NearV8HeapLimitCallback = NearV8HeapLimitHandling (*)();
class CORE_EXPORT V8Initializer { class CORE_EXPORT V8Initializer {
STATIC_ONLY(V8Initializer); STATIC_ONLY(V8Initializer);
public: public:
// This must be called before InitializeMainThread.
static void SetNearV8HeapLimitOnMainThreadCallback(
NearV8HeapLimitCallback callback);
static void InitializeMainThread(const intptr_t* reference_table); static void InitializeMainThread(const intptr_t* reference_table);
static void InitializeWorker(v8::Isolate*); static void InitializeWorker(v8::Isolate*);
......
...@@ -38,6 +38,8 @@ component("controller") { ...@@ -38,6 +38,8 @@ component("controller") {
"blink_initializer.h", "blink_initializer.h",
"blink_leak_detector.cc", "blink_leak_detector.cc",
"blink_leak_detector.h", "blink_leak_detector.h",
"bloated_renderer_detector.cc",
"bloated_renderer_detector.h",
"controller_export.h", "controller_export.h",
"dev_tools_frontend_impl.cc", "dev_tools_frontend_impl.cc",
"dev_tools_frontend_impl.h", "dev_tools_frontend_impl.h",
...@@ -92,6 +94,7 @@ jumbo_source_set("webkit_unit_tests_sources") { ...@@ -92,6 +94,7 @@ jumbo_source_set("webkit_unit_tests_sources") {
testonly = true testonly = true
sources = [ sources = [
"bloated_renderer_detector_test.cc",
"oom_intervention_impl_test.cc", "oom_intervention_impl_test.cc",
"tests/run_all_tests.cc", "tests/run_all_tests.cc",
] ]
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h" #include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_external_references.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_external_references.h"
#include "third_party/blink/renderer/controller/blink_leak_detector.h" #include "third_party/blink/renderer/controller/blink_leak_detector.h"
#include "third_party/blink/renderer/controller/bloated_renderer_detector.h"
#include "third_party/blink/renderer/controller/dev_tools_frontend_impl.h" #include "third_party/blink/renderer/controller/dev_tools_frontend_impl.h"
#include "third_party/blink/renderer/controller/oom_intervention_impl.h" #include "third_party/blink/renderer/controller/oom_intervention_impl.h"
#include "third_party/blink/renderer/core/animation/animation_clock.h" #include "third_party/blink/renderer/core/animation/animation_clock.h"
...@@ -106,6 +107,12 @@ void Initialize(Platform* platform, service_manager::BinderRegistry* registry) { ...@@ -106,6 +107,12 @@ void Initialize(Platform* platform, service_manager::BinderRegistry* registry) {
// BlinkInitializer::Initialize() must be called before InitializeMainThread // BlinkInitializer::Initialize() must be called before InitializeMainThread
GetBlinkInitializer().Initialize(); GetBlinkInitializer().Initialize();
if (RuntimeEnabledFeatures::BloatedRendererDetectionEnabled()) {
BloatedRendererDetector::Initialize();
V8Initializer::SetNearV8HeapLimitOnMainThreadCallback(
BloatedRendererDetector::OnNearV8HeapLimitOnMainThread);
}
V8Initializer::InitializeMainThread( V8Initializer::InitializeMainThread(
V8ContextSnapshotExternalReferences::GetTable()); V8ContextSnapshotExternalReferences::GetTable());
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/controller/bloated_renderer_detector.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
namespace blink {
static BloatedRendererDetector* g_bloated_renderer_detector = nullptr;
void BloatedRendererDetector::Initialize() {
g_bloated_renderer_detector =
new BloatedRendererDetector(WTF::CurrentTimeTicks());
}
NearV8HeapLimitHandling
BloatedRendererDetector::OnNearV8HeapLimitOnMainThread() {
return g_bloated_renderer_detector->OnNearV8HeapLimitOnMainThreadImpl();
}
NearV8HeapLimitHandling
BloatedRendererDetector::OnNearV8HeapLimitOnMainThreadImpl() {
WTF::TimeDelta uptime = (WTF::CurrentTimeTicks() - startup_time_);
if (uptime.InMinutes() < kMinimumUptimeInMinutes) {
return NearV8HeapLimitHandling::kIgnoredDueToSmallUptime;
}
// TODO(ulan): Send message to the browser.
return NearV8HeapLimitHandling::kForwardedToBrowser;
}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CONTROLLER_BLOATED_RENDERER_DETECTOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CONTROLLER_BLOATED_RENDERER_DETECTOR_H_
#include "base/gtest_prod_util.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"
#include "third_party/blink/renderer/controller/controller_export.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
// Singleton class for detecting and handling bloated renderer conditions.
// Different parts of the renderer call the corresponding methods of this
// class to notify about potential bloat conditions. It currently works only
// for V8. In future it will be extended to the whole renderer.
class CONTROLLER_EXPORT BloatedRendererDetector {
USING_FAST_MALLOC(BloatedRendererDetector);
public:
// Sets up the global singleton instance.
static void Initialize();
// Called when the main V8 isolate is close to reach its heap limit.
static NearV8HeapLimitHandling OnNearV8HeapLimitOnMainThread();
private:
friend class BloatedRendererDetectorTest;
FRIEND_TEST_ALL_PREFIXES(BloatedRendererDetectorTest, ForwardToBrowser);
FRIEND_TEST_ALL_PREFIXES(BloatedRendererDetectorTest, SmallUptime);
// The minimum uptime after which bloated renderer detection starts.
static const int kMinimumUptimeInMinutes = 10;
explicit BloatedRendererDetector(WTF::TimeTicks startup_time)
: startup_time_(startup_time) {}
NearV8HeapLimitHandling OnNearV8HeapLimitOnMainThreadImpl();
const WTF::TimeTicks startup_time_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CONTROLLER_BLOATED_RENDERER_DETECTOR_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/controller/bloated_renderer_detector.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
class BloatedRendererDetectorTest : public testing::Test {
public:
static double GetMockLargeUptime() {
int large_uptime = BloatedRendererDetector::kMinimumUptimeInMinutes + 1;
return WTF::TimeTicksInSeconds(
WTF::TimeTicksFromSeconds(large_uptime * 60));
}
static double GetMockSmallUptime() {
int small_uptime = BloatedRendererDetector::kMinimumUptimeInMinutes - 1;
return WTF::TimeTicksInSeconds(
WTF::TimeTicksFromSeconds(small_uptime * 60));
}
};
TEST_F(BloatedRendererDetectorTest, ForwardToBrowser) {
BloatedRendererDetector detector(WTF::TimeTicksFromSeconds(0));
WTF::TimeFunction original_time_function =
WTF::SetTimeFunctionsForTesting(GetMockLargeUptime);
EXPECT_EQ(NearV8HeapLimitHandling::kForwardedToBrowser,
detector.OnNearV8HeapLimitOnMainThreadImpl());
WTF::SetTimeFunctionsForTesting(original_time_function);
}
TEST_F(BloatedRendererDetectorTest, SmallUptime) {
BloatedRendererDetector detector(WTF::TimeTicksFromSeconds(0));
WTF::TimeFunction original_time_function =
SetTimeFunctionsForTesting(GetMockSmallUptime);
EXPECT_EQ(NearV8HeapLimitHandling::kIgnoredDueToSmallUptime,
detector.OnNearV8HeapLimitOnMainThreadImpl());
WTF::SetTimeFunctionsForTesting(original_time_function);
}
} // namespace blink
...@@ -71,7 +71,8 @@ V8PerIsolateData::V8PerIsolateData( ...@@ -71,7 +71,8 @@ V8PerIsolateData::V8PerIsolateData(
use_counter_disabled_(false), use_counter_disabled_(false),
is_handling_recursion_level_error_(false), is_handling_recursion_level_error_(false),
is_reporting_exception_(false), is_reporting_exception_(false),
runtime_call_stats_(base::DefaultTickClock::GetInstance()) { runtime_call_stats_(base::DefaultTickClock::GetInstance()),
handled_near_v8_heap_limit_(false) {
// FIXME: Remove once all v8::Isolate::GetCurrent() calls are gone. // FIXME: Remove once all v8::Isolate::GetCurrent() calls are gone.
GetIsolate()->Enter(); GetIsolate()->Enter();
GetIsolate()->AddBeforeCallEnteredCallback(&BeforeCallEnteredCallback); GetIsolate()->AddBeforeCallEnteredCallback(&BeforeCallEnteredCallback);
...@@ -95,7 +96,8 @@ V8PerIsolateData::V8PerIsolateData() ...@@ -95,7 +96,8 @@ V8PerIsolateData::V8PerIsolateData()
use_counter_disabled_(false), use_counter_disabled_(false),
is_handling_recursion_level_error_(false), is_handling_recursion_level_error_(false),
is_reporting_exception_(false), is_reporting_exception_(false),
runtime_call_stats_(base::DefaultTickClock::GetInstance()) { runtime_call_stats_(base::DefaultTickClock::GetInstance()),
handled_near_v8_heap_limit_(false) {
CHECK(IsMainThread()); CHECK(IsMainThread());
// SnapshotCreator enters the isolate, so we don't call Isolate::Enter() here. // SnapshotCreator enters the isolate, so we don't call Isolate::Enter() here.
......
...@@ -234,6 +234,9 @@ class PLATFORM_EXPORT V8PerIsolateData { ...@@ -234,6 +234,9 @@ class PLATFORM_EXPORT V8PerIsolateData {
ScriptWrappableMarkingVisitor* GetScriptWrappableMarkingVisitor() { ScriptWrappableMarkingVisitor* GetScriptWrappableMarkingVisitor() {
return script_wrappable_visitor_.get(); return script_wrappable_visitor_.get();
} }
int IsNearV8HeapLimitHandled() { return handled_near_v8_heap_limit_; }
void HandledNearV8HeapLimit() { handled_near_v8_heap_limit_ = true; }
private: private:
V8PerIsolateData(scoped_refptr<base::SingleThreadTaskRunner>, V8PerIsolateData(scoped_refptr<base::SingleThreadTaskRunner>,
...@@ -305,6 +308,7 @@ class PLATFORM_EXPORT V8PerIsolateData { ...@@ -305,6 +308,7 @@ class PLATFORM_EXPORT V8PerIsolateData {
std::unique_ptr<ScriptWrappableMarkingVisitor> script_wrappable_visitor_; std::unique_ptr<ScriptWrappableMarkingVisitor> script_wrappable_visitor_;
RuntimeCallStats runtime_call_stats_; RuntimeCallStats runtime_call_stats_;
bool handled_near_v8_heap_limit_;
}; };
} // namespace blink } // namespace blink
......
...@@ -130,6 +130,10 @@ ...@@ -130,6 +130,10 @@
{ {
name: "BlinkRuntimeCallStats", name: "BlinkRuntimeCallStats",
}, },
{
name: "BloatedRendererDetection",
status: "experimental",
},
{ {
name: "BlockCredentialedSubresources", name: "BlockCredentialedSubresources",
status: "stable", status: "stable",
......
...@@ -30765,6 +30765,13 @@ Called by update_use_counter_css.py.--> ...@@ -30765,6 +30765,13 @@ Called by update_use_counter_css.py.-->
<int value="2" label="Foreground renderer navigates to other URL"/> <int value="2" label="Foreground renderer navigates to other URL"/>
</enum> </enum>
<enum name="NearV8HeapLimitHandling">
<int value="0" label="Forwarded handling to the browser process"/>
<int value="1" label="Ignored due to small uptime of the renderer process"/>
<int value="2" label="Ignored because V8 heap limit was already changed"/>
<int value="3" label="Ignored worker isolate"/>
</enum>
<enum name="NegativeSampleReason"> <enum name="NegativeSampleReason">
<int value="0" label="Histogram had logged value but no active sample."/> <int value="0" label="Histogram had logged value but no active sample."/>
<int value="1" label="Histogram active sample less than logged value."/> <int value="1" label="Histogram active sample less than logged value."/>
...@@ -8462,6 +8462,17 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. ...@@ -8462,6 +8462,17 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary> </summary>
</histogram> </histogram>
<histogram name="BloatedRenderer.V8.NearV8HeapLimitHandling"
enum="NearV8HeapLimitHandling">
<owner>ulan@chromium.org</owner>
<summary>
Records how a bloated V8 heap was handled in the renderer process. It is
emitted the first time V8 heap reaches the heap limit and invokes the
NearHeapLimitCallback. Subsequent invocations of the callback are ignored.
Thus it is emitted once per renderer process run.
</summary>
</histogram>
<histogram name="Bluetooth.Android.GATTConnection.Disconnected.Result" <histogram name="Bluetooth.Android.GATTConnection.Disconnected.Result"
enum="AndroidGATTConnectionErrorCodes"> enum="AndroidGATTConnectionErrorCodes">
<owner>jyasskin@chromium.org</owner> <owner>jyasskin@chromium.org</owner>
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