Commit 21d2dec5 authored by Victor Porof's avatar Victor Porof Committed by V8 LUCI CQ

Create an async stack tagging prototype API

This CL exposes the async stack traces instrumentation on the console
object, behind a --experimental-async-stack-tagging-api flag. It serves
as a prototype that aims to validate whether the debugging experience
can be improved for userland code that uses custom schedulers. The tests
are implemented as Blink web tests in the following CL:
https://chromium-review.googlesource.com/c/chromium/src/+/3226418

Bug: chromium:332624
Change-Id: Ib1ee71de68f7bb9aff5b944812ce681d8711d217
Signed-off-by: 's avatarVictor Porof <victorporof@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3212506Reviewed-by: 's avatarSimon Zünd <szuend@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77491}
parent af1ccea7
......@@ -1241,6 +1241,10 @@ MaybeLocal<Message> GetMessageFromPromise(Local<Promise> p) {
i::Handle<i::JSMessageObject>::cast(maybeMessage));
}
bool isExperimentalAsyncStackTaggingApiEnabled() {
return v8::internal::FLAG_experimental_async_stack_tagging_api;
}
std::unique_ptr<PropertyIterator> PropertyIterator::Create(
Local<Context> context, Local<Object> object, bool skip_indices) {
internal::Isolate* isolate =
......
......@@ -666,6 +666,8 @@ AccessorPair* AccessorPair::Cast(v8::Value* value) {
MaybeLocal<Message> GetMessageFromPromise(Local<Promise> promise);
bool isExperimentalAsyncStackTaggingApiEnabled();
} // namespace debug
} // namespace v8
......
......@@ -1577,6 +1577,9 @@ DEFINE_BOOL(
"print debug messages for side-effect-free debug-evaluate for testing")
DEFINE_BOOL(hard_abort, true, "abort by crashing")
DEFINE_BOOL(experimental_async_stack_tagging_api, false,
"enable experimental async stacks tagging API")
// disassembler
DEFINE_BOOL(log_colour, ENABLE_LOG_COLOUR,
"When logging, try to use coloured output.")
......
......@@ -63,14 +63,25 @@ InspectedContext::InspectedContext(V8InspectorImpl* inspector,
m_context.SetWeak(m_weakCallbackData,
&InspectedContext::WeakCallbackData::resetContext,
v8::WeakCallbackType::kParameter);
if (!info.hasMemoryOnConsole) return;
v8::Context::Scope contextScope(info.context);
v8::HandleScope handleScope(info.context->GetIsolate());
v8::Local<v8::Object> global = info.context->Global();
v8::Local<v8::Value> console;
if (global->Get(info.context, toV8String(m_inspector->isolate(), "console"))
.ToLocal(&console) &&
console->IsObject()) {
if (!global
->Get(info.context,
toV8String(info.context->GetIsolate(), "console"))
.ToLocal(&console) ||
!console->IsObject()) {
return;
}
if (v8::debug::isExperimentalAsyncStackTaggingApiEnabled()) {
m_inspector->console()->installAsyncStackTaggingAPI(
info.context, console.As<v8::Object>());
}
if (info.hasMemoryOnConsole) {
m_inspector->console()->installMemoryGetter(info.context,
console.As<v8::Object>());
}
......
......@@ -446,6 +446,92 @@ void V8Console::memorySetterCallback(
// setter just ignores the passed value. http://crbug.com/468611
}
v8::Maybe<int64_t> V8Console::ValidateAndGetTaskId(
const v8::FunctionCallbackInfo<v8::Value>& info) {
if (info.Length() != 1) {
info.GetIsolate()->ThrowError("Unexpected arguments");
return v8::Nothing<int64_t>();
}
int64_t argId;
if (!info[0]->IsNumber() ||
!v8::Just(info[0].As<v8::Integer>()->Value()).To(&argId)) {
info.GetIsolate()->ThrowError("Task ID should be an integer");
return v8::Nothing<int64_t>();
}
auto it = m_asyncTaskIds.find(argId);
if (it == m_asyncTaskIds.end()) {
info.GetIsolate()->ThrowError("Task with ID doesn't exist");
return v8::Nothing<int64_t>();
}
return v8::Just(argId);
}
void V8Console::scheduleAsyncTask(
const v8::FunctionCallbackInfo<v8::Value>& info) {
if (info.Length() != 1) {
info.GetIsolate()->ThrowError("Unexpected arguments");
return;
}
v8::debug::ConsoleCallArguments args(info);
ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
String16 argName = helper.firstArgToString(String16());
int64_t id = m_taskIdCounter++;
auto it = m_asyncTaskIds.find(id);
if (it != m_asyncTaskIds.end()) {
info.GetIsolate()->ThrowError("Task with ID already exists");
return;
}
int* taskPtr = new int();
m_asyncTaskIds.emplace(id, taskPtr);
StringView taskName = StringView(argName.characters16(), argName.length());
m_inspector->asyncTaskScheduled(taskName, taskPtr, false);
info.GetReturnValue().Set(v8::Number::New(info.GetIsolate(), id));
}
void V8Console::startAsyncTask(
const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Maybe<int64_t> maybeArgId = ValidateAndGetTaskId(info);
if (maybeArgId.IsNothing()) return;
int64_t taskId = maybeArgId.FromJust();
int* taskPtr = m_asyncTaskIds[taskId];
m_inspector->asyncTaskStarted(taskPtr);
}
void V8Console::finishAsyncTask(
const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Maybe<int64_t> maybeArgId = ValidateAndGetTaskId(info);
if (maybeArgId.IsNothing()) return;
int64_t taskId = maybeArgId.FromJust();
int* taskPtr = m_asyncTaskIds[taskId];
m_inspector->asyncTaskFinished(taskPtr);
delete taskPtr;
m_asyncTaskIds.erase(taskId);
}
void V8Console::cancelAsyncTask(
const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Maybe<int64_t> maybeArgId = ValidateAndGetTaskId(info);
if (maybeArgId.IsNothing()) return;
int64_t taskId = maybeArgId.FromJust();
int* taskPtr = m_asyncTaskIds[taskId];
m_inspector->asyncTaskCanceled(taskPtr);
delete taskPtr;
m_asyncTaskIds.erase(taskId);
}
void V8Console::keysCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
int sessionId) {
v8::Isolate* isolate = info.GetIsolate();
......@@ -666,6 +752,48 @@ void V8Console::installMemoryGetter(v8::Local<v8::Context> context,
static_cast<v8::PropertyAttribute>(v8::None), v8::DEFAULT);
}
void V8Console::installAsyncStackTaggingAPI(v8::Local<v8::Context> context,
v8::Local<v8::Object> console) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::External> data = v8::External::New(isolate, this);
v8::MicrotasksScope microtasksScope(isolate,
v8::MicrotasksScope::kDoNotRunMicrotasks);
console
->Set(context, toV8StringInternalized(isolate, "scheduleAsyncTask"),
v8::Function::New(context,
&V8Console::call<&V8Console::scheduleAsyncTask>,
data, 0, v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect)
.ToLocalChecked())
.Check();
console
->Set(context, toV8StringInternalized(isolate, "startAsyncTask"),
v8::Function::New(context,
&V8Console::call<&V8Console::startAsyncTask>,
data, 0, v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect)
.ToLocalChecked())
.Check();
console
->Set(context, toV8StringInternalized(isolate, "finishAsyncTask"),
v8::Function::New(context,
&V8Console::call<&V8Console::finishAsyncTask>,
data, 0, v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect)
.ToLocalChecked())
.Check();
console
->Set(context, toV8StringInternalized(isolate, "cancelAsyncTask"),
v8::Function::New(context,
&V8Console::call<&V8Console::cancelAsyncTask>,
data, 0, v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect)
.ToLocalChecked())
.Check();
}
v8::Local<v8::Object> V8Console::createCommandLineAPI(
v8::Local<v8::Context> context, int sessionId) {
v8::Isolate* isolate = context->GetIsolate();
......
......@@ -5,6 +5,8 @@
#ifndef V8_INSPECTOR_V8_CONSOLE_H_
#define V8_INSPECTOR_V8_CONSOLE_H_
#include <map>
#include "include/v8-array-buffer.h"
#include "include/v8-external.h"
#include "include/v8-local-handle.h"
......@@ -28,6 +30,8 @@ class V8Console : public v8::debug::ConsoleDelegate {
int sessionId);
void installMemoryGetter(v8::Local<v8::Context> context,
v8::Local<v8::Object> console);
void installAsyncStackTaggingAPI(v8::Local<v8::Context> context,
v8::Local<v8::Object> console);
class V8_NODISCARD CommandLineAPIScope {
public:
......@@ -128,6 +132,13 @@ class V8Console : public v8::debug::ConsoleDelegate {
void memoryGetterCallback(const v8::FunctionCallbackInfo<v8::Value>&);
void memorySetterCallback(const v8::FunctionCallbackInfo<v8::Value>&);
v8::Maybe<int64_t> ValidateAndGetTaskId(
const v8::FunctionCallbackInfo<v8::Value>&);
void scheduleAsyncTask(const v8::FunctionCallbackInfo<v8::Value>&);
void startAsyncTask(const v8::FunctionCallbackInfo<v8::Value>&);
void finishAsyncTask(const v8::FunctionCallbackInfo<v8::Value>&);
void cancelAsyncTask(const v8::FunctionCallbackInfo<v8::Value>&);
// CommandLineAPI
void keysCallback(const v8::FunctionCallbackInfo<v8::Value>&, int sessionId);
void valuesCallback(const v8::FunctionCallbackInfo<v8::Value>&,
......@@ -171,6 +182,14 @@ class V8Console : public v8::debug::ConsoleDelegate {
int sessionId);
V8InspectorImpl* m_inspector;
// A map of unique pointers used for the scheduling and joining async stacks.
// The async stack traces instrumentation is exposed on the console object,
// behind a --experimental-async-stack-tagging-api flag. For now, it serves as
// a prototype that aims to validate whether the debugging experience can be
// improved for userland code that uses custom schedulers.
int64_t m_taskIdCounter = 0;
std::map<int64_t, int*> m_asyncTaskIds;
};
} // namespace v8_inspector
......
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