DevTools: use a barrier to sync runIfWaitingForDebugger from multiple sessions

This introduces a barrier that ensures that
`V8InspectorClient::runIfWaitingForDebugger()` is only invoked once all
sessions that requested a paused have invoked runIfWaitingForDebugger.

Downstream change: https://chromium-review.googlesource.com/c/chromium/src/+/3977348

Bug: chromium:1352175
Change-Id: I9049c2de6da8e690ad4312cd6cb799619125bb62
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3976353
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84191}
This commit is contained in:
Andrey Kosyakov 2022-10-28 22:23:26 -07:00 committed by V8 LUCI CQ
parent c80394dd3c
commit aa684004d0
20 changed files with 290 additions and 46 deletions

View File

@ -3249,6 +3249,8 @@ filegroup(
"src/inspector/v8-debugger.h",
"src/inspector/v8-debugger-agent-impl.cc",
"src/inspector/v8-debugger-agent-impl.h",
"src/inspector/v8-debugger-barrier.cc",
"src/inspector/v8-debugger-barrier.h",
"src/inspector/v8-debugger-id.cc",
"src/inspector/v8-debugger-id.h",
"src/inspector/v8-debugger-script.cc",

View File

@ -365,9 +365,12 @@ class V8_EXPORT V8Inspector {
virtual void flushProtocolNotifications() = 0;
};
enum ClientTrustLevel { kUntrusted, kFullyTrusted };
enum SessionPauseState { kWaitingForDebugger, kNotWaitingForDebugger };
// TODO(chromium:1352175): remove default value once downstream change lands.
virtual std::unique_ptr<V8InspectorSession> connect(
int contextGroupId, Channel*, StringView state,
ClientTrustLevel client_trust_level) {
ClientTrustLevel client_trust_level,
SessionPauseState = kNotWaitingForDebugger) {
return nullptr;
}

View File

@ -4175,7 +4175,8 @@ class InspectorClient : public v8_inspector::V8InspectorClient {
inspector_ = v8_inspector::V8Inspector::create(isolate_, this);
session_ =
inspector_->connect(1, channel_.get(), v8_inspector::StringView(),
v8_inspector::V8Inspector::kFullyTrusted);
v8_inspector::V8Inspector::kFullyTrusted,
v8_inspector::V8Inspector::kNotWaitingForDebugger);
context->SetAlignedPointerInEmbedderData(kInspectorClientIndex, this);
inspector_->contextCreated(v8_inspector::V8ContextInfo(
context, kContextGroupId, v8_inspector::StringView()));

View File

@ -125,6 +125,8 @@ v8_source_set("inspector") {
"v8-console.h",
"v8-debugger-agent-impl.cc",
"v8-debugger-agent-impl.h",
"v8-debugger-barrier.cc",
"v8-debugger-barrier.h",
"v8-debugger-id.cc",
"v8-debugger-id.h",
"v8-debugger-script.cc",

View File

@ -0,0 +1,19 @@
// Copyright 2022 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/v8-debugger-barrier.h"
#include "include/v8-inspector.h"
namespace v8_inspector {
V8DebuggerBarrier::V8DebuggerBarrier(V8InspectorClient* client,
int contextGroupId)
: m_client(client), m_contextGroupId(contextGroupId) {}
V8DebuggerBarrier::~V8DebuggerBarrier() {
m_client->runIfWaitingForDebugger(m_contextGroupId);
}
} // namespace v8_inspector

View File

@ -0,0 +1,28 @@
// Copyright 2022 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_V8_DEBUGGER_BARRIER_H_
#define V8_INSPECTOR_V8_DEBUGGER_BARRIER_H_
namespace v8_inspector {
class V8InspectorClient;
// This class is used to synchronize multiple sessions issuing
// `Runtime.runIfWaitingForDebbuger` so that the global client
// `runIfWaitingForDebugger` method is only invoked when all
// sessions have invoked `Runtime.runIfWaitingForDebugger`.
class V8DebuggerBarrier {
public:
V8DebuggerBarrier(V8InspectorClient* client, int contextGroupId);
~V8DebuggerBarrier();
private:
V8InspectorClient* const m_client;
int m_contextGroupId;
};
} // namespace v8_inspector
#endif // V8_INSPECTOR_V8_DEBUGGER_BARRIER_H_

View File

@ -44,6 +44,7 @@
#include "src/inspector/v8-console-message.h"
#include "src/inspector/v8-console.h"
#include "src/inspector/v8-debugger-agent-impl.h"
#include "src/inspector/v8-debugger-barrier.h"
#include "src/inspector/v8-debugger-id.h"
#include "src/inspector/v8-debugger.h"
#include "src/inspector/v8-inspector-session-impl.h"
@ -147,11 +148,26 @@ std::unique_ptr<V8StackTrace> V8InspectorImpl::createStackTrace(
std::unique_ptr<V8InspectorSession> V8InspectorImpl::connect(
int contextGroupId, V8Inspector::Channel* channel, StringView state,
ClientTrustLevel client_trust_level) {
ClientTrustLevel client_trust_level, SessionPauseState pause_state) {
int sessionId = ++m_lastSessionId;
std::shared_ptr<V8DebuggerBarrier> debuggerBarrier;
if (pause_state == kWaitingForDebugger) {
auto it = m_debuggerBarriers.find(contextGroupId);
if (it != m_debuggerBarriers.end()) {
// Note this will be empty in case a pre-existent barrier is already
// released. This is by design, as a released throttle is no longer
// efficient.
debuggerBarrier = it->second.lock();
} else {
debuggerBarrier =
std::make_shared<V8DebuggerBarrier>(m_client, contextGroupId);
m_debuggerBarriers.insert(it, {contextGroupId, debuggerBarrier});
}
}
std::unique_ptr<V8InspectorSessionImpl> session =
V8InspectorSessionImpl::create(this, contextGroupId, sessionId, channel,
state, client_trust_level);
state, client_trust_level,
std::move(debuggerBarrier));
m_sessions[contextGroupId][sessionId] = session.get();
return std::move(session);
}
@ -159,7 +175,10 @@ std::unique_ptr<V8InspectorSession> V8InspectorImpl::connect(
void V8InspectorImpl::disconnect(V8InspectorSessionImpl* session) {
auto& map = m_sessions[session->contextGroupId()];
map.erase(session->sessionId());
if (map.empty()) m_sessions.erase(session->contextGroupId());
if (map.empty()) {
m_sessions.erase(session->contextGroupId());
m_debuggerBarriers.erase(session->contextGroupId());
}
}
InspectedContext* V8InspectorImpl::getContext(int groupId,

View File

@ -49,6 +49,7 @@ class V8Console;
class V8ConsoleMessageStorage;
class V8Debugger;
class V8DebuggerAgentImpl;
class V8DebuggerBarrier;
class V8InspectorSessionImpl;
class V8ProfilerAgentImpl;
class V8RuntimeAgentImpl;
@ -81,7 +82,8 @@ class V8InspectorImpl : public V8Inspector {
std::unique_ptr<V8InspectorSession> connect(int contextGroupId,
V8Inspector::Channel*,
StringView state,
ClientTrustLevel) override;
ClientTrustLevel,
SessionPauseState) override;
void contextCreated(const V8ContextInfo&) override;
void contextDestroyed(v8::Local<v8::Context>) override;
v8::MaybeLocal<v8::Context> contextById(int contextId) override;
@ -178,6 +180,8 @@ class V8InspectorImpl : public V8Inspector {
// contextGroupId -> sessionId -> session
std::unordered_map<int, std::map<int, V8InspectorSessionImpl*>> m_sessions;
// contextGroupId -> debugger barrier
std::unordered_map<int, std::weak_ptr<V8DebuggerBarrier>> m_debuggerBarriers;
using ConsoleStorageMap =
std::unordered_map<int, std::unique_ptr<V8ConsoleMessageStorage>>;

View File

@ -17,6 +17,7 @@
#include "src/inspector/string-util.h"
#include "src/inspector/v8-console-agent-impl.h"
#include "src/inspector/v8-debugger-agent-impl.h"
#include "src/inspector/v8-debugger-barrier.h"
#include "src/inspector/v8-debugger.h"
#include "src/inspector/v8-heap-profiler-agent-impl.h"
#include "src/inspector/v8-inspector-impl.h"
@ -90,15 +91,18 @@ int V8ContextInfo::executionContextId(v8::Local<v8::Context> context) {
std::unique_ptr<V8InspectorSessionImpl> V8InspectorSessionImpl::create(
V8InspectorImpl* inspector, int contextGroupId, int sessionId,
V8Inspector::Channel* channel, StringView state,
V8Inspector::ClientTrustLevel clientTrustLevel) {
V8Inspector::ClientTrustLevel clientTrustLevel,
std::shared_ptr<V8DebuggerBarrier> debuggerBarrier) {
return std::unique_ptr<V8InspectorSessionImpl>(new V8InspectorSessionImpl(
inspector, contextGroupId, sessionId, channel, state, clientTrustLevel));
inspector, contextGroupId, sessionId, channel, state, clientTrustLevel,
std::move(debuggerBarrier)));
}
V8InspectorSessionImpl::V8InspectorSessionImpl(
V8InspectorImpl* inspector, int contextGroupId, int sessionId,
V8Inspector::Channel* channel, StringView savedState,
V8Inspector::ClientTrustLevel clientTrustLevel)
V8Inspector::ClientTrustLevel clientTrustLevel,
std::shared_ptr<V8DebuggerBarrier> debuggerBarrier)
: m_contextGroupId(contextGroupId),
m_sessionId(sessionId),
m_inspector(inspector),
@ -116,7 +120,8 @@ V8InspectorSessionImpl::V8InspectorSessionImpl(
m_state->getBoolean("use_binary_protocol", &use_binary_protocol_);
m_runtimeAgent.reset(new V8RuntimeAgentImpl(
this, this, agentState(protocol::Runtime::Metainfo::domainName)));
this, this, agentState(protocol::Runtime::Metainfo::domainName),
std::move(debuggerBarrier)));
protocol::Runtime::Dispatcher::wire(&m_dispatcher, m_runtimeAgent.get());
m_debuggerAgent.reset(new V8DebuggerAgentImpl(

View File

@ -21,6 +21,7 @@ class InjectedScript;
class RemoteObjectIdBase;
class V8ConsoleAgentImpl;
class V8DebuggerAgentImpl;
class V8DebuggerBarrier;
class V8InspectorImpl;
class V8HeapProfilerAgentImpl;
class V8ProfilerAgentImpl;
@ -35,7 +36,8 @@ class V8InspectorSessionImpl : public V8InspectorSession,
static std::unique_ptr<V8InspectorSessionImpl> create(
V8InspectorImpl*, int contextGroupId, int sessionId,
V8Inspector::Channel*, StringView state,
v8_inspector::V8Inspector::ClientTrustLevel);
v8_inspector::V8Inspector::ClientTrustLevel,
std::shared_ptr<V8DebuggerBarrier>);
~V8InspectorSessionImpl() override;
V8InspectorSessionImpl(const V8InspectorSessionImpl&) = delete;
V8InspectorSessionImpl& operator=(const V8InspectorSessionImpl&) = delete;
@ -105,7 +107,8 @@ class V8InspectorSessionImpl : public V8InspectorSession,
private:
V8InspectorSessionImpl(V8InspectorImpl*, int contextGroupId, int sessionId,
V8Inspector::Channel*, StringView state,
V8Inspector::ClientTrustLevel);
V8Inspector::ClientTrustLevel,
std::shared_ptr<V8DebuggerBarrier>);
protocol::DictionaryValue* agentState(const String16& name);
// protocol::FrontendChannel implementation.

View File

@ -241,11 +241,13 @@ Response ensureContext(V8InspectorImpl* inspector, int contextGroupId,
V8RuntimeAgentImpl::V8RuntimeAgentImpl(
V8InspectorSessionImpl* session, protocol::FrontendChannel* FrontendChannel,
protocol::DictionaryValue* state)
protocol::DictionaryValue* state,
std::shared_ptr<V8DebuggerBarrier> debuggerBarrier)
: m_session(session),
m_state(state),
m_frontend(FrontendChannel),
m_inspector(session->inspector()),
m_debuggerBarrier(debuggerBarrier),
m_enabled(false) {}
V8RuntimeAgentImpl::~V8RuntimeAgentImpl() = default;
@ -491,11 +493,13 @@ Response V8RuntimeAgentImpl::releaseObjectGroup(const String16& objectGroup) {
}
Response V8RuntimeAgentImpl::runIfWaitingForDebugger() {
if (m_runIfWaitingForDebuggerCalled) return Response::Success();
m_runIfWaitingForDebuggerCalled = true;
// The client implementation is resposible for checking if the session is
// actually waiting for debugger. m_runIfWaitingForDebuggerCalled only makes
// sure that the client implementation is invoked once per agent instance.
if (m_debuggerBarrier) {
m_debuggerBarrier.reset();
return Response::Success();
}
// TODO(chromium:1352175): the below is provisional until client-side changes
// land. The call should come through the barrier only once client properly
// communicates whether the session is waiting for debugger.
m_inspector->client()->runIfWaitingForDebugger(m_session->contextGroupId());
return Response::Success();
}

View File

@ -49,6 +49,7 @@ class InjectedScript;
class InspectedContext;
class RemoteObjectIdBase;
class V8ConsoleMessage;
class V8DebuggerBarrier;
class V8InspectorImpl;
class V8InspectorSessionImpl;
@ -58,7 +59,8 @@ using protocol::Maybe;
class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
public:
V8RuntimeAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*,
protocol::DictionaryValue* state);
protocol::DictionaryValue* state,
std::shared_ptr<V8DebuggerBarrier>);
~V8RuntimeAgentImpl() override;
V8RuntimeAgentImpl(const V8RuntimeAgentImpl&) = delete;
V8RuntimeAgentImpl& operator=(const V8RuntimeAgentImpl&) = delete;
@ -155,12 +157,12 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
protocol::DictionaryValue* m_state;
protocol::Runtime::Frontend m_frontend;
V8InspectorImpl* m_inspector;
std::shared_ptr<V8DebuggerBarrier> m_debuggerBarrier;
bool m_enabled;
std::unordered_map<String16, std::unique_ptr<v8::Global<v8::Script>>>
m_compiledScripts;
// Binding name -> executionContextIds mapping.
std::unordered_map<String16, std::unordered_set<int>> m_activeBindings;
bool m_runIfWaitingForDebuggerCalled = false;
};
} // namespace v8_inspector

View File

@ -104,6 +104,9 @@ class UtilsExtension : public InspectorIsolateData::SetupGlobalTask {
utils->Set(isolate, "interruptForMessages",
v8::FunctionTemplate::New(
isolate, &UtilsExtension::InterruptForMessages));
utils->Set(
isolate, "waitForDebugger",
v8::FunctionTemplate::New(isolate, &UtilsExtension::WaitForDebugger));
global->Set(isolate, "utils", utils);
}
@ -404,6 +407,19 @@ class UtilsExtension : public InspectorIsolateData::SetupGlobalTask {
backend_runner_->InterruptForMessages();
}
static void WaitForDebugger(const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 2 || !args[0]->IsInt32() || !args[1]->IsFunction()) {
FATAL("Internal error: waitForDebugger(context_group_id, callback).");
}
int context_group_id = args[0].As<v8::Int32>()->Value();
RunSimpleAsyncTask(
backend_runner_,
[context_group_id](InspectorIsolateData* data) {
data->WaitForDebugger(context_group_id);
},
args[1].As<v8::Function>());
}
static std::map<int, std::unique_ptr<FrontendChannelImpl>> channels_;
};

View File

@ -163,9 +163,12 @@ int InspectorIsolateData::ConnectSession(
v8_inspector::V8Inspector::Channel* channel) {
v8::SealHandleScope seal_handle_scope(isolate());
int session_id = ++last_session_id_;
sessions_[session_id] =
inspector_->connect(context_group_id, channel, state,
v8_inspector::V8Inspector::kFullyTrusted);
sessions_[session_id] = inspector_->connect(
context_group_id, channel, state,
v8_inspector::V8Inspector::kFullyTrusted,
waiting_for_debugger_
? v8_inspector::V8Inspector::kWaitingForDebugger
: v8_inspector::V8Inspector::kNotWaitingForDebugger);
context_group_by_session_[sessions_[session_id].get()] = context_group_id;
return session_id;
}
@ -438,6 +441,10 @@ void InspectorIsolateData::runMessageLoopOnPause(int) {
task_runner_->RunMessageLoop(true);
}
void InspectorIsolateData::runIfWaitingForDebugger(int) {
quitMessageLoopOnPause();
}
void InspectorIsolateData::quitMessageLoopOnPause() {
v8::SealHandleScope seal_handle_scope(isolate());
task_runner_->QuitMessageLoop();
@ -507,6 +514,13 @@ bool InspectorIsolateData::AssociateExceptionData(
this->isolate()->GetCurrentContext(), exception, key, value);
}
void InspectorIsolateData::WaitForDebugger(int context_group_id) {
DCHECK(!waiting_for_debugger_);
waiting_for_debugger_ = true;
runMessageLoopOnPause(context_group_id);
waiting_for_debugger_ = false;
}
namespace {
class StringBufferImpl : public v8_inspector::StringBuffer {
public:

View File

@ -106,6 +106,7 @@ class InspectorIsolateData : public v8_inspector::V8InspectorClient {
bool AssociateExceptionData(v8::Local<v8::Value> exception,
v8::Local<v8::Name> key,
v8::Local<v8::Value> value);
void WaitForDebugger(int context_group_id);
private:
static v8::MaybeLocal<v8::Module> ModuleResolveCallback(
@ -126,6 +127,7 @@ class InspectorIsolateData : public v8_inspector::V8InspectorClient {
v8::MaybeLocal<v8::Value> memoryInfo(v8::Isolate* isolate,
v8::Local<v8::Context>) override;
void runMessageLoopOnPause(int context_group_id) override;
void runIfWaitingForDebugger(int context_group_id) override;
void quitMessageLoopOnPause() override;
void installAdditionalCommandLineAPI(v8::Local<v8::Context>,
v8::Local<v8::Object>) override;
@ -169,6 +171,7 @@ class InspectorIsolateData : public v8_inspector::V8InspectorClient {
double current_time_ = 0.0;
bool log_console_api_message_calls_ = false;
bool log_max_async_call_stack_depth_changed_ = false;
bool waiting_for_debugger_ = false;
v8::Global<v8::Private> not_inspectable_private_;
v8::Global<v8::String> resource_name_prefix_;
v8::Global<v8::String> additional_console_api_;

View File

@ -142,6 +142,12 @@ InspectorTest.ContextGroup = class {
this.id = utils.createContextGroup();
}
waitForDebugger() {
return new Promise(resolve => {
utils.waitForDebugger(this.id, resolve);
});
}
createContext(name) {
utils.createContext(this.id, name || '');
}

View File

@ -0,0 +1,12 @@
Running test: testTwoSessions
Tests Runtime.runIfWaitingForDebugger
session 1 resumed
session 2 resumed
execution resumed
Running test: testSessionDisconnect
Tests Runtime.runIfWaitingForDebugger
session 1 resumed
session 2 disconnected
execution resumed

View File

@ -0,0 +1,35 @@
// Copyright 2022 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.
InspectorTest.runAsyncTestSuite([
async function testTwoSessions() {
InspectorTest.log('Tests Runtime.runIfWaitingForDebugger');
const contextGroup = new InspectorTest.ContextGroup();
const resumed = contextGroup.waitForDebugger().then(() => InspectorTest.log('execution resumed'));
const session1 = contextGroup.connect();
const session2 = contextGroup.connect();
await session1.Protocol.Runtime.runIfWaitingForDebugger();
InspectorTest.log('session 1 resumed');
await session2.Protocol.Runtime.runIfWaitingForDebugger();
InspectorTest.log('session 2 resumed');
await resumed;
},
async function testSessionDisconnect() {
InspectorTest.log('Tests Runtime.runIfWaitingForDebugger');
const contextGroup = new InspectorTest.ContextGroup();
const resumed = contextGroup.waitForDebugger().then(() => InspectorTest.log('execution resumed'));
const session1 = contextGroup.connect();
const session2 = contextGroup.connect();
await session1.Protocol.Runtime.runIfWaitingForDebugger();
InspectorTest.log('session 1 resumed');
session2.disconnect();
InspectorTest.log('session 2 disconnected');
await resumed;
}
]);

View File

@ -14,6 +14,90 @@
namespace v8 {
namespace internal {
void RunSyncTask(TaskRunner* task_runner,
std::function<void(InspectorIsolateData*)> callback) {
class SyncTask : public TaskRunner::Task {
public:
SyncTask(v8::base::Semaphore* ready_semaphore,
std::function<void(InspectorIsolateData*)> callback)
: ready_semaphore_(ready_semaphore), callback_(callback) {}
~SyncTask() override = default;
bool is_priority_task() final { return true; }
private:
void Run(InspectorIsolateData* data) override {
callback_(data);
if (ready_semaphore_) ready_semaphore_->Signal();
}
v8::base::Semaphore* ready_semaphore_;
std::function<void(InspectorIsolateData*)> callback_;
};
v8::base::Semaphore ready_semaphore(0);
task_runner->Append(std::make_unique<SyncTask>(&ready_semaphore, callback));
ready_semaphore.Wait();
}
void RunSimpleAsyncTask(TaskRunner* task_runner,
std::function<void(InspectorIsolateData* data)> task,
v8::Local<v8::Function> callback) {
class DispatchResponseTask : public TaskRunner::Task {
public:
explicit DispatchResponseTask(v8::Local<v8::Function> callback)
: context_(callback->GetIsolate(),
callback->GetIsolate()->GetCurrentContext()),
client_callback_(callback->GetIsolate(), callback) {}
~DispatchResponseTask() override = default;
private:
bool is_priority_task() final { return true; }
void Run(InspectorIsolateData* data) override {
v8::HandleScope handle_scope(data->isolate());
v8::Local<v8::Context> context = context_.Get(data->isolate());
v8::MicrotasksScope microtasks_scope(context,
v8::MicrotasksScope::kRunMicrotasks);
v8::Context::Scope context_scope(context);
(void)client_callback_.Get(data->isolate())
->Call(context, context->Global(), 0, nullptr);
}
v8::Global<v8::Context> context_;
v8::Global<v8::Function> client_callback_;
};
using TaskCallback = std::function<void(InspectorIsolateData * data)>;
class TaskWrapper : public TaskRunner::Task {
public:
TaskWrapper(TaskCallback task, TaskRunner* client_task_runner,
std::unique_ptr<TaskRunner::Task> response_task)
: task_(std::move(task)),
client_task_runner_(client_task_runner),
response_task_(std::move(response_task)) {}
~TaskWrapper() override = default;
private:
bool is_priority_task() final { return true; }
void Run(InspectorIsolateData* data) override {
task_(data);
client_task_runner_->Append(std::move(response_task_));
}
TaskCallback task_;
TaskRunner* client_task_runner_;
std::unique_ptr<TaskRunner::Task> response_task_;
};
v8::Local<v8::Context> context = callback->GetIsolate()->GetCurrentContext();
TaskRunner* response_task_runner =
InspectorIsolateData::FromContext(context)->task_runner();
auto response_task = std::make_unique<DispatchResponseTask>(callback);
task_runner->Append(std::make_unique<TaskWrapper>(
std::move(task), response_task_runner, std::move(response_task)));
}
void ExecuteStringTask::Run(InspectorIsolateData* data) {
v8::HandleScope handle_scope(data->isolate());
v8::Local<v8::Context> context = data->GetDefaultContext(context_group_id_);

View File

@ -20,29 +20,11 @@
namespace v8 {
namespace internal {
template <typename T>
void RunSyncTask(TaskRunner* task_runner, T callback) {
class SyncTask : public TaskRunner::Task {
public:
SyncTask(v8::base::Semaphore* ready_semaphore, T callback)
: ready_semaphore_(ready_semaphore), callback_(callback) {}
~SyncTask() override = default;
bool is_priority_task() final { return true; }
private:
void Run(InspectorIsolateData* data) override {
callback_(data);
if (ready_semaphore_) ready_semaphore_->Signal();
}
v8::base::Semaphore* ready_semaphore_;
T callback_;
};
v8::base::Semaphore ready_semaphore(0);
task_runner->Append(std::make_unique<SyncTask>(&ready_semaphore, callback));
ready_semaphore.Wait();
}
void RunSyncTask(TaskRunner* task_runner,
std::function<void(InspectorIsolateData*)> callback);
void RunSimpleAsyncTask(TaskRunner* task_runner,
std::function<void(InspectorIsolateData* data)> task,
v8::Local<v8::Function> callback);
class SendMessageToBackendTask : public TaskRunner::Task {
public: