Commit c42915f0 authored by kozyatinskiy's avatar kozyatinskiy Committed by Commit bot

[inspector] introduce limit for amount of stored async stacks

BUG=v8:5738
R=dgozman@chromium.org

Review-Url: https://codereview.chromium.org/2579403002
Cr-Commit-Position: refs/heads/master@{#41783}
parent 083a5dcd
...@@ -149,6 +149,8 @@ v8_source_set("inspector") { ...@@ -149,6 +149,8 @@ v8_source_set("inspector") {
"string-16.h", "string-16.h",
"string-util.cc", "string-util.cc",
"string-util.h", "string-util.h",
"test-interface.cc",
"test-interface.h",
"v8-console-agent-impl.cc", "v8-console-agent-impl.cc",
"v8-console-agent-impl.h", "v8-console-agent-impl.h",
"v8-console-message.cc", "v8-console-message.cc",
......
...@@ -53,6 +53,8 @@ ...@@ -53,6 +53,8 @@
'inspector/string-16.h', 'inspector/string-16.h',
'inspector/string-util.cc', 'inspector/string-util.cc',
'inspector/string-util.h', 'inspector/string-util.h',
'inspector/test-interface.cc',
'inspector/test-interface.h',
'inspector/v8-console.cc', 'inspector/v8-console.cc',
'inspector/v8-console.h', 'inspector/v8-console.h',
'inspector/v8-console-agent-impl.cc', 'inspector/v8-console-agent-impl.cc',
......
// Copyright 2016 the V8 project 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 "src/inspector/test-interface.h"
#include "src/inspector/v8-debugger.h"
#include "src/inspector/v8-inspector-impl.h"
namespace v8_inspector {
void SetMaxAsyncTaskStacksForTest(V8Inspector* inspector, int limit) {
static_cast<V8InspectorImpl*>(inspector)
->debugger()
->setMaxAsyncTaskStacksForTest(limit);
}
} // v8_inspector
// Copyright 2016 the V8 project 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 V8_INSPECTOR_TEST_INTERFACE_H_
#define V8_INSPECTOR_TEST_INTERFACE_H_
#include "include/v8.h"
namespace v8_inspector {
class V8Inspector;
V8_EXPORT void SetMaxAsyncTaskStacksForTest(V8Inspector* inspector, int limit);
} // v8_inspector
#endif // V8_INSPECTOR_TEST_INTERFACE_H_
...@@ -26,6 +26,11 @@ static const char v8AsyncTaskEventWillHandle[] = "willHandle"; ...@@ -26,6 +26,11 @@ static const char v8AsyncTaskEventWillHandle[] = "willHandle";
static const char v8AsyncTaskEventDidHandle[] = "didHandle"; static const char v8AsyncTaskEventDidHandle[] = "didHandle";
static const char v8AsyncTaskEventCancel[] = "cancel"; static const char v8AsyncTaskEventCancel[] = "cancel";
// Based on DevTools frontend measurement, with asyncCallStackDepth = 4,
// average async call stack tail requires ~1 Kb. Let's reserve ~ 128 Mb
// for async stacks.
static const int kMaxAsyncTaskStacks = 128 * 1024;
inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) { inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) {
return value ? v8::True(isolate) : v8::False(isolate); return value ? v8::True(isolate) : v8::False(isolate);
} }
...@@ -55,6 +60,8 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) ...@@ -55,6 +60,8 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
m_breakpointsActivated(true), m_breakpointsActivated(true),
m_runningNestedMessageLoop(false), m_runningNestedMessageLoop(false),
m_ignoreScriptParsedEventsCounter(0), m_ignoreScriptParsedEventsCounter(0),
m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
m_lastTaskId(0),
m_maxAsyncCallStackDepth(0), m_maxAsyncCallStackDepth(0),
m_pauseOnExceptionsState(v8::debug::NoBreakOnException), m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
m_wasmTranslation(isolate) {} m_wasmTranslation(isolate) {}
...@@ -901,6 +908,13 @@ void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task, ...@@ -901,6 +908,13 @@ void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
if (chain) { if (chain) {
m_asyncTaskStacks[task] = std::move(chain); m_asyncTaskStacks[task] = std::move(chain);
if (recurring) m_recurringTasks.insert(task); if (recurring) m_recurringTasks.insert(task);
int id = ++m_lastTaskId;
m_taskToId[task] = id;
m_idToTask[id] = task;
if (static_cast<int>(m_idToTask.size()) > m_maxAsyncCallStacks) {
void* taskToRemove = m_idToTask.begin()->second;
asyncTaskCanceled(taskToRemove);
}
} }
} }
...@@ -908,6 +922,10 @@ void V8Debugger::asyncTaskCanceled(void* task) { ...@@ -908,6 +922,10 @@ void V8Debugger::asyncTaskCanceled(void* task) {
if (!m_maxAsyncCallStackDepth) return; if (!m_maxAsyncCallStackDepth) return;
m_asyncTaskStacks.erase(task); m_asyncTaskStacks.erase(task);
m_recurringTasks.erase(task); m_recurringTasks.erase(task);
auto it = m_taskToId.find(task);
if (it == m_taskToId.end()) return;
m_idToTask.erase(it->second);
m_taskToId.erase(it);
} }
void V8Debugger::asyncTaskStarted(void* task) { void V8Debugger::asyncTaskStarted(void* task) {
...@@ -936,8 +954,13 @@ void V8Debugger::asyncTaskFinished(void* task) { ...@@ -936,8 +954,13 @@ void V8Debugger::asyncTaskFinished(void* task) {
m_currentTasks.pop_back(); m_currentTasks.pop_back();
m_currentStacks.pop_back(); m_currentStacks.pop_back();
if (m_recurringTasks.find(task) == m_recurringTasks.end()) if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
m_asyncTaskStacks.erase(task); m_asyncTaskStacks.erase(task);
auto it = m_taskToId.find(task);
if (it == m_taskToId.end()) return;
m_idToTask.erase(it->second);
m_taskToId.erase(it);
}
} }
void V8Debugger::allAsyncTasksCanceled() { void V8Debugger::allAsyncTasksCanceled() {
...@@ -945,6 +968,9 @@ void V8Debugger::allAsyncTasksCanceled() { ...@@ -945,6 +968,9 @@ void V8Debugger::allAsyncTasksCanceled() {
m_recurringTasks.clear(); m_recurringTasks.clear();
m_currentStacks.clear(); m_currentStacks.clear();
m_currentTasks.clear(); m_currentTasks.clear();
m_idToTask.clear();
m_taskToId.clear();
m_lastTaskId = 0;
} }
void V8Debugger::muteScriptParsedEvents() { void V8Debugger::muteScriptParsedEvents() {
......
...@@ -93,6 +93,8 @@ class V8Debugger { ...@@ -93,6 +93,8 @@ class V8Debugger {
WasmTranslation* wasmTranslation() { return &m_wasmTranslation; } WasmTranslation* wasmTranslation() { return &m_wasmTranslation; }
void setMaxAsyncTaskStacksForTest(int limit) { m_maxAsyncCallStacks = limit; }
private: private:
void compileDebuggerScript(); void compileDebuggerScript();
v8::MaybeLocal<v8::Value> callDebuggerMethod(const char* functionName, v8::MaybeLocal<v8::Value> callDebuggerMethod(const char* functionName,
...@@ -150,6 +152,10 @@ class V8Debugger { ...@@ -150,6 +152,10 @@ class V8Debugger {
using AsyncTaskToStackTrace = using AsyncTaskToStackTrace =
protocol::HashMap<void*, std::unique_ptr<V8StackTraceImpl>>; protocol::HashMap<void*, std::unique_ptr<V8StackTraceImpl>>;
AsyncTaskToStackTrace m_asyncTaskStacks; AsyncTaskToStackTrace m_asyncTaskStacks;
int m_maxAsyncCallStacks;
std::map<int, void*> m_idToTask;
std::unordered_map<void*, int> m_taskToId;
int m_lastTaskId;
protocol::HashSet<void*> m_recurringTasks; protocol::HashSet<void*> m_recurringTasks;
int m_maxAsyncCallStackDepth; int m_maxAsyncCallStackDepth;
std::vector<void*> m_currentTasks; std::vector<void*> m_currentTasks;
......
...@@ -4,6 +4,7 @@ include_rules = [ ...@@ -4,6 +4,7 @@ include_rules = [
"+src/base/macros.h", "+src/base/macros.h",
"+src/base/platform/platform.h", "+src/base/platform/platform.h",
"+src/flags.h", "+src/flags.h",
"+src/inspector/test-interface.h",
"+src/locked-queue-inl.h", "+src/locked-queue-inl.h",
"+src/utils.h", "+src/utils.h",
"+src/vector.h", "+src/vector.h",
......
Checks that async stacks works good with different limits
Running test: testZeroLimit
foo1 (test.js:11:2)
Running test: testOneLimit
foo1 (test.js:11:2)
-- Promise.resolve --
promise (test.js:23:2)
(anonymous) (expr.js:0:0)
Running test: testOneLimitTwoPromises
foo1 (test.js:11:2)
foo2 (test.js:15:2)
Running test: testTwoLimitTwoPromises
foo1 (test.js:11:2)
-- Promise.resolve --
twoPromises (test.js:34:2)
(anonymous) (expr.js:0:0)
foo2 (test.js:15:2)
-- Promise.resolve --
twoPromises (test.js:35:2)
(anonymous) (expr.js:0:0)
Running test: testOneLimitTwoSetTimeouts
foo1 (test.js:11:2)
foo2 (test.js:15:2)
-- setTimeout --
twoSetTimeout (test.js:41:2)
(anonymous) (expr.js:0:0)
Running test: testTwoLimitTwoSetTimeouts
foo1 (test.js:11:2)
-- setTimeout --
twoSetTimeout (test.js:40:2)
(anonymous) (expr.js:0:0)
foo2 (test.js:15:2)
-- setTimeout --
twoSetTimeout (test.js:41:2)
(anonymous) (expr.js:0:0)
Running test: testTenLimitTwentySetTimeouts
foo1 (:0:17)
(anonymous) (:0:28)
foo2 (:0:17)
(anonymous) (:0:28)
foo3 (:0:17)
(anonymous) (:0:28)
foo4 (:0:17)
(anonymous) (:0:28)
foo5 (:0:17)
(anonymous) (:0:28)
foo6 (:0:17)
(anonymous) (:0:28)
foo7 (:0:17)
(anonymous) (:0:28)
foo8 (:0:17)
(anonymous) (:0:28)
foo9 (:0:17)
(anonymous) (:0:28)
foo10 (:0:18)
(anonymous) (:0:29)
foo11 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo12 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo13 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo14 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo15 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo16 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo17 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo18 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
foo19 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:55:4)
(anonymous) (expr.js:0:0)
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
print('Checks that async stacks works good with different limits');
InspectorTest.addScript(`
var resolveTest;
function foo1() {
debugger;
}
function foo2() {
debugger;
if (resolveTest) resolveTest();
}
function promise() {
var resolve1;
var p1 = new Promise(resolve => resolve1 = resolve);
var p2 = p1.then(foo1);
resolve1();
return p2;
}
function twoPromises() {
var resolve1;
var resolve2;
var p1 = new Promise(resolve => resolve1 = resolve);
var p2 = new Promise(resolve => resolve2 = resolve);
var p3 = p1.then(foo1);
var p4 = p2.then(foo2);
resolve1();
resolve2();
return Promise.all([p3, p4]);
}
function twoSetTimeout() {
setTimeout(foo1, 0);
setTimeout(foo2, 0);
return new Promise(resolve => resolveTest = resolve);
}
function threeSetTimeout() {
setTimeout(foo1, 0);
setTimeout(foo2, 0);
return new Promise(resolve => resolveTest = resolve);
}
function twentySetTimeout() {
var resolve1;
var p1 = new Promise(resolve => resolve1 = resolve);
for (var i = 1; i <= 19; ++i)
setTimeout('(function foo' + i + '(){debugger;})()',0);
setTimeout(resolve1, 0);
return p1;
}
//# sourceURL=test.js`, 7, 26);
InspectorTest.setupScriptMap();
Protocol.Debugger.onPaused(message => {
InspectorTest.logCallFrames(message.params.callFrames);
var asyncStackTrace = message.params.asyncStackTrace;
while (asyncStackTrace) {
InspectorTest.log(`-- ${asyncStackTrace.description} --`);
InspectorTest.logCallFrames(asyncStackTrace.callFrames);
asyncStackTrace = asyncStackTrace.parent;
}
InspectorTest.log('');
Protocol.Debugger.resume();
});
Protocol.Debugger.enable();
Protocol.Debugger.setAsyncCallStackDepth({ maxDepth: 128 });
InspectorTest.runTestSuite([
function testZeroLimit(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(0)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'promise()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testOneLimit(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(1)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'promise()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testOneLimitTwoPromises(next) {
// Should be no async stacks because when first microtask is finished
// it will resolve and schedule p3 - will remove async stack for scheduled
// p2.
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(1)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoPromises()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testTwoLimitTwoPromises(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(2)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoPromises()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testOneLimitTwoSetTimeouts(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(1)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoSetTimeout()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testTwoLimitTwoSetTimeouts(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(2)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoSetTimeout()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testTenLimitTwentySetTimeouts(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(10)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twentySetTimeout()//# sourceURL=expr.js',
awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
}
]);
function cancelAllAsyncTasks() {
return Protocol.Debugger.setAsyncCallStackDepth({ maxDepth: 0 })
.then(() => Protocol.Debugger.setAsyncCallStackDepth({ maxDepth: 128 }));
}
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "src/base/platform/platform.h" #include "src/base/platform/platform.h"
#include "src/flags.h" #include "src/flags.h"
#include "src/inspector/test-interface.h"
#include "src/utils.h" #include "src/utils.h"
#include "src/vector.h" #include "src/vector.h"
...@@ -257,7 +258,8 @@ class InspectorExtension : public v8::Extension { ...@@ -257,7 +258,8 @@ class InspectorExtension : public v8::Extension {
InspectorExtension() InspectorExtension()
: v8::Extension("v8_inspector/inspector", : v8::Extension("v8_inspector/inspector",
"native function attachInspector();" "native function attachInspector();"
"native function detachInspector();") {} "native function detachInspector();"
"native function setMaxAsyncTaskStacks();") {}
virtual v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate( virtual v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
v8::Isolate* isolate, v8::Local<v8::String> name) { v8::Isolate* isolate, v8::Local<v8::String> name) {
...@@ -274,6 +276,13 @@ class InspectorExtension : public v8::Extension { ...@@ -274,6 +276,13 @@ class InspectorExtension : public v8::Extension {
.ToLocalChecked()) .ToLocalChecked())
.FromJust()) { .FromJust()) {
return v8::FunctionTemplate::New(isolate, InspectorExtension::Detach); return v8::FunctionTemplate::New(isolate, InspectorExtension::Detach);
} else if (name->Equals(context, v8::String::NewFromUtf8(
isolate, "setMaxAsyncTaskStacks",
v8::NewStringType::kNormal)
.ToLocalChecked())
.FromJust()) {
return v8::FunctionTemplate::New(
isolate, InspectorExtension::SetMaxAsyncTaskStacks);
} }
return v8::Local<v8::FunctionTemplate>(); return v8::Local<v8::FunctionTemplate>();
} }
...@@ -303,6 +312,20 @@ class InspectorExtension : public v8::Extension { ...@@ -303,6 +312,20 @@ class InspectorExtension : public v8::Extension {
} }
inspector->contextDestroyed(context); inspector->contextDestroyed(context);
} }
static void SetMaxAsyncTaskStacks(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1 || !args[0]->IsInt32()) {
fprintf(stderr, "Internal error: setMaxAsyncTaskStacks(max).");
Exit();
}
v8_inspector::V8Inspector* inspector =
InspectorClientImpl::InspectorFromContext(
args.GetIsolate()->GetCurrentContext());
CHECK(inspector);
v8_inspector::SetMaxAsyncTaskStacksForTest(
inspector, args[0].As<v8::Int32>()->Value());
}
}; };
v8::Local<v8::String> ToString(v8::Isolate* isolate, v8::Local<v8::String> ToString(v8::Isolate* isolate,
......
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