[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}
This commit is contained in:
parent
083a5dcdfe
commit
c42915f02d
@ -149,6 +149,8 @@ v8_source_set("inspector") {
|
||||
"string-16.h",
|
||||
"string-util.cc",
|
||||
"string-util.h",
|
||||
"test-interface.cc",
|
||||
"test-interface.h",
|
||||
"v8-console-agent-impl.cc",
|
||||
"v8-console-agent-impl.h",
|
||||
"v8-console-message.cc",
|
||||
|
@ -53,6 +53,8 @@
|
||||
'inspector/string-16.h',
|
||||
'inspector/string-util.cc',
|
||||
'inspector/string-util.h',
|
||||
'inspector/test-interface.cc',
|
||||
'inspector/test-interface.h',
|
||||
'inspector/v8-console.cc',
|
||||
'inspector/v8-console.h',
|
||||
'inspector/v8-console-agent-impl.cc',
|
||||
|
18
src/inspector/test-interface.cc
Normal file
18
src/inspector/test-interface.cc
Normal file
@ -0,0 +1,18 @@
|
||||
// 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
|
18
src/inspector/test-interface.h
Normal file
18
src/inspector/test-interface.h
Normal file
@ -0,0 +1,18 @@
|
||||
// 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";
|
||||
static const char v8AsyncTaskEventDidHandle[] = "didHandle";
|
||||
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) {
|
||||
return value ? v8::True(isolate) : v8::False(isolate);
|
||||
}
|
||||
@ -55,6 +60,8 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
|
||||
m_breakpointsActivated(true),
|
||||
m_runningNestedMessageLoop(false),
|
||||
m_ignoreScriptParsedEventsCounter(0),
|
||||
m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
|
||||
m_lastTaskId(0),
|
||||
m_maxAsyncCallStackDepth(0),
|
||||
m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
|
||||
m_wasmTranslation(isolate) {}
|
||||
@ -901,6 +908,13 @@ void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
|
||||
if (chain) {
|
||||
m_asyncTaskStacks[task] = std::move(chain);
|
||||
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) {
|
||||
if (!m_maxAsyncCallStackDepth) return;
|
||||
m_asyncTaskStacks.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) {
|
||||
@ -936,8 +954,13 @@ void V8Debugger::asyncTaskFinished(void* task) {
|
||||
m_currentTasks.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);
|
||||
auto it = m_taskToId.find(task);
|
||||
if (it == m_taskToId.end()) return;
|
||||
m_idToTask.erase(it->second);
|
||||
m_taskToId.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void V8Debugger::allAsyncTasksCanceled() {
|
||||
@ -945,6 +968,9 @@ void V8Debugger::allAsyncTasksCanceled() {
|
||||
m_recurringTasks.clear();
|
||||
m_currentStacks.clear();
|
||||
m_currentTasks.clear();
|
||||
m_idToTask.clear();
|
||||
m_taskToId.clear();
|
||||
m_lastTaskId = 0;
|
||||
}
|
||||
|
||||
void V8Debugger::muteScriptParsedEvents() {
|
||||
|
@ -93,6 +93,8 @@ class V8Debugger {
|
||||
|
||||
WasmTranslation* wasmTranslation() { return &m_wasmTranslation; }
|
||||
|
||||
void setMaxAsyncTaskStacksForTest(int limit) { m_maxAsyncCallStacks = limit; }
|
||||
|
||||
private:
|
||||
void compileDebuggerScript();
|
||||
v8::MaybeLocal<v8::Value> callDebuggerMethod(const char* functionName,
|
||||
@ -150,6 +152,10 @@ class V8Debugger {
|
||||
using AsyncTaskToStackTrace =
|
||||
protocol::HashMap<void*, std::unique_ptr<V8StackTraceImpl>>;
|
||||
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;
|
||||
int m_maxAsyncCallStackDepth;
|
||||
std::vector<void*> m_currentTasks;
|
||||
|
@ -4,6 +4,7 @@ include_rules = [
|
||||
"+src/base/macros.h",
|
||||
"+src/base/platform/platform.h",
|
||||
"+src/flags.h",
|
||||
"+src/inspector/test-interface.h",
|
||||
"+src/locked-queue-inl.h",
|
||||
"+src/utils.h",
|
||||
"+src/vector.h",
|
||||
|
137
test/inspector/debugger/async-stacks-limit-expected.txt
Normal file
137
test/inspector/debugger/async-stacks-limit-expected.txt
Normal file
@ -0,0 +1,137 @@
|
||||
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)
|
||||
|
156
test/inspector/debugger/async-stacks-limit.js
Normal file
156
test/inspector/debugger/async-stacks-limit.js
Normal file
@ -0,0 +1,156 @@
|
||||
// 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 @@
|
||||
|
||||
#include "src/base/platform/platform.h"
|
||||
#include "src/flags.h"
|
||||
#include "src/inspector/test-interface.h"
|
||||
#include "src/utils.h"
|
||||
#include "src/vector.h"
|
||||
|
||||
@ -257,7 +258,8 @@ class InspectorExtension : public v8::Extension {
|
||||
InspectorExtension()
|
||||
: v8::Extension("v8_inspector/inspector",
|
||||
"native function attachInspector();"
|
||||
"native function detachInspector();") {}
|
||||
"native function detachInspector();"
|
||||
"native function setMaxAsyncTaskStacks();") {}
|
||||
|
||||
virtual v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
|
||||
v8::Isolate* isolate, v8::Local<v8::String> name) {
|
||||
@ -274,6 +276,13 @@ class InspectorExtension : public v8::Extension {
|
||||
.ToLocalChecked())
|
||||
.FromJust()) {
|
||||
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>();
|
||||
}
|
||||
@ -303,6 +312,20 @@ class InspectorExtension : public v8::Extension {
|
||||
}
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user