Skip to content

Commit 1490164

Browse files
Eugene OstroukhovMylesBorins
Eugene Ostroukhov
authored andcommitted
inspector: allow concurrent inspector sessions
This change enables concurrent inspector sessions, through WebSocket interface as well as JS interface, in any combination. PR-URL: #20137 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 14188b1 commit 1490164

15 files changed

+290
-254
lines changed

src/inspector_agent.cc

+70-66
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ const int CONTEXT_GROUP_ID = 1;
189189

190190
class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
191191
public:
192-
explicit ChannelImpl(V8Inspector* inspector,
193-
InspectorSessionDelegate* delegate)
194-
: delegate_(delegate) {
192+
explicit ChannelImpl(const std::unique_ptr<V8Inspector>& inspector,
193+
std::unique_ptr<InspectorSessionDelegate> delegate)
194+
: delegate_(std::move(delegate)) {
195195
session_ = inspector->connect(1, this, StringView());
196196
}
197197

@@ -201,19 +201,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
201201
session_->dispatchProtocolMessage(message);
202202
}
203203

204-
bool waitForFrontendMessage() {
205-
return delegate_->WaitForFrontendMessageWhilePaused();
206-
}
207-
208204
void schedulePauseOnNextStatement(const std::string& reason) {
209205
std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason);
210206
session_->schedulePauseOnNextStatement(buffer->string(), buffer->string());
211207
}
212208

213-
InspectorSessionDelegate* delegate() {
214-
return delegate_;
215-
}
216-
217209
private:
218210
void sendResponse(
219211
int callId,
@@ -232,7 +224,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
232224
delegate_->SendMessageToFrontend(message);
233225
}
234226

235-
InspectorSessionDelegate* const delegate_;
227+
std::unique_ptr<InspectorSessionDelegate> delegate_;
236228
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
237229
};
238230

@@ -300,8 +292,7 @@ class InspectorTimerHandle {
300292
class NodeInspectorClient : public V8InspectorClient {
301293
public:
302294
NodeInspectorClient(node::Environment* env, node::NodePlatform* platform)
303-
: env_(env), platform_(platform), terminated_(false),
304-
running_nested_loop_(false) {
295+
: env_(env), platform_(platform) {
305296
client_ = V8Inspector::create(env->isolate(), this);
306297
// TODO(bnoordhuis) Make name configurable from src/node.cc.
307298
ContextInfo info(GetHumanReadableProcessName());
@@ -310,18 +301,28 @@ class NodeInspectorClient : public V8InspectorClient {
310301
}
311302

312303
void runMessageLoopOnPause(int context_group_id) override {
313-
CHECK_NE(channel_, nullptr);
304+
runMessageLoop(false);
305+
}
306+
307+
void runMessageLoop(bool ignore_terminated) {
314308
if (running_nested_loop_)
315309
return;
316310
terminated_ = false;
317311
running_nested_loop_ = true;
318-
while (!terminated_ && channel_->waitForFrontendMessage()) {
312+
while ((ignore_terminated || !terminated_) && waitForFrontendEvent()) {
319313
while (platform_->FlushForegroundTasks(env_->isolate())) {}
320314
}
321315
terminated_ = false;
322316
running_nested_loop_ = false;
323317
}
324318

319+
bool waitForFrontendEvent() {
320+
InspectorIo* io = env_->inspector_agent()->io();
321+
if (io == nullptr)
322+
return false;
323+
return io->WaitForFrontendEvent();
324+
}
325+
325326
double currentTimeMS() override {
326327
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
327328
}
@@ -363,20 +364,22 @@ class NodeInspectorClient : public V8InspectorClient {
363364
terminated_ = true;
364365
}
365366

366-
void connectFrontend(InspectorSessionDelegate* delegate) {
367-
CHECK_EQ(channel_, nullptr);
368-
channel_ = std::unique_ptr<ChannelImpl>(
369-
new ChannelImpl(client_.get(), delegate));
367+
int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate) {
368+
events_dispatched_ = true;
369+
int session_id = next_session_id_++;
370+
channels_[session_id] =
371+
std::make_unique<ChannelImpl>(client_, std::move(delegate));
372+
return session_id;
370373
}
371374

372-
void disconnectFrontend() {
373-
quitMessageLoopOnPause();
374-
channel_.reset();
375+
void disconnectFrontend(int session_id) {
376+
events_dispatched_ = true;
377+
channels_.erase(session_id);
375378
}
376379

377-
void dispatchMessageFromFrontend(const StringView& message) {
378-
CHECK_NE(channel_, nullptr);
379-
channel_->dispatchProtocolMessage(message);
380+
void dispatchMessageFromFrontend(int session_id, const StringView& message) {
381+
events_dispatched_ = true;
382+
channels_[session_id]->dispatchProtocolMessage(message);
380383
}
381384

382385
Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
@@ -426,10 +429,6 @@ class NodeInspectorClient : public V8InspectorClient {
426429
script_id);
427430
}
428431

429-
ChannelImpl* channel() {
430-
return channel_.get();
431-
}
432-
433432
void startRepeatingTimer(double interval_s,
434433
TimerCallback callback,
435434
void* data) override {
@@ -464,20 +463,31 @@ class NodeInspectorClient : public V8InspectorClient {
464463
client_->allAsyncTasksCanceled();
465464
}
466465

466+
void schedulePauseOnNextStatement(const std::string& reason) {
467+
for (const auto& id_channel : channels_) {
468+
id_channel.second->schedulePauseOnNextStatement(reason);
469+
}
470+
}
471+
472+
bool hasConnectedSessions() {
473+
return !channels_.empty();
474+
}
475+
467476
private:
468477
node::Environment* env_;
469478
node::NodePlatform* platform_;
470-
bool terminated_;
471-
bool running_nested_loop_;
479+
bool terminated_ = false;
480+
bool running_nested_loop_ = false;
472481
std::unique_ptr<V8Inspector> client_;
473-
std::unique_ptr<ChannelImpl> channel_;
482+
std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
474483
std::unordered_map<void*, InspectorTimerHandle> timers_;
484+
int next_session_id_ = 1;
485+
bool events_dispatched_ = false;
475486
};
476487

477488
Agent::Agent(Environment* env) : parent_env_(env),
478489
client_(nullptr),
479490
platform_(nullptr),
480-
enabled_(false),
481491
pending_enable_async_hook_(false),
482492
pending_disable_async_hook_(false) {}
483493

@@ -491,7 +501,7 @@ bool Agent::Start(node::NodePlatform* platform, const char* path,
491501
path_ = path == nullptr ? "" : path;
492502
debug_options_ = options;
493503
client_ =
494-
std::unique_ptr<NodeInspectorClient>(
504+
std::shared_ptr<NodeInspectorClient>(
495505
new NodeInspectorClient(parent_env_, platform));
496506
platform_ = platform;
497507
CHECK_EQ(0, uv_async_init(uv_default_loop(),
@@ -515,7 +525,6 @@ bool Agent::StartIoThread(bool wait_for_connect) {
515525

516526
CHECK_NE(client_, nullptr);
517527

518-
enabled_ = true;
519528
io_ = std::unique_ptr<InspectorIo>(
520529
new InspectorIo(parent_env_, platform_, path_, debug_options_,
521530
wait_for_connect));
@@ -554,20 +563,25 @@ void Agent::Stop() {
554563
if (io_ != nullptr) {
555564
io_->Stop();
556565
io_.reset();
557-
enabled_ = false;
558566
}
559567
}
560568

561-
void Agent::Connect(InspectorSessionDelegate* delegate) {
562-
enabled_ = true;
563-
client_->connectFrontend(delegate);
569+
std::unique_ptr<InspectorSession> Agent::Connect(
570+
std::unique_ptr<InspectorSessionDelegate> delegate) {
571+
int session_id = client_->connectFrontend(std::move(delegate));
572+
return std::make_unique<InspectorSession>(session_id, client_);
564573
}
565574

566575
void Agent::WaitForDisconnect() {
567576
CHECK_NE(client_, nullptr);
568577
client_->contextDestroyed(parent_env_->context());
569578
if (io_ != nullptr) {
570579
io_->WaitForDisconnect();
580+
// There is a bug in V8 Inspector (https://crbug.com/834056) that
581+
// calls V8InspectorClient::quitMessageLoopOnPause when a session
582+
// disconnects. We are using this flag to ignore those calls so the message
583+
// loop is spinning as long as there's a reason to expect inspector messages
584+
client_->runMessageLoop(true);
571585
}
572586
}
573587

@@ -578,33 +592,8 @@ void Agent::FatalException(Local<Value> error, Local<v8::Message> message) {
578592
WaitForDisconnect();
579593
}
580594

581-
void Agent::Dispatch(const StringView& message) {
582-
CHECK_NE(client_, nullptr);
583-
client_->dispatchMessageFromFrontend(message);
584-
}
585-
586-
void Agent::Disconnect() {
587-
CHECK_NE(client_, nullptr);
588-
client_->disconnectFrontend();
589-
}
590-
591-
void Agent::RunMessageLoop() {
592-
CHECK_NE(client_, nullptr);
593-
client_->runMessageLoopOnPause(CONTEXT_GROUP_ID);
594-
}
595-
596-
InspectorSessionDelegate* Agent::delegate() {
597-
CHECK_NE(client_, nullptr);
598-
ChannelImpl* channel = client_->channel();
599-
if (channel == nullptr)
600-
return nullptr;
601-
return channel->delegate();
602-
}
603-
604595
void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
605-
ChannelImpl* channel = client_->channel();
606-
if (channel != nullptr)
607-
channel->schedulePauseOnNextStatement(reason);
596+
client_->schedulePauseOnNextStatement(reason);
608597
}
609598

610599
void Agent::RegisterAsyncHook(Isolate* isolate,
@@ -699,5 +688,20 @@ bool Agent::IsWaitingForConnect() {
699688
return debug_options_.wait_for_connect();
700689
}
701690

691+
bool Agent::HasConnectedSessions() {
692+
return client_->hasConnectedSessions();
693+
}
694+
695+
InspectorSession::InspectorSession(int session_id,
696+
std::shared_ptr<NodeInspectorClient> client)
697+
: session_id_(session_id), client_(client) {}
698+
699+
InspectorSession::~InspectorSession() {
700+
client_->disconnectFrontend(session_id_);
701+
}
702+
703+
void InspectorSession::Dispatch(const StringView& message) {
704+
client_->dispatchMessageFromFrontend(session_id_, message);
705+
}
702706
} // namespace inspector
703707
} // namespace node

src/inspector_agent.h

+22-18
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,26 @@ class Environment;
2323
struct ContextInfo;
2424

2525
namespace inspector {
26+
class InspectorIo;
27+
class NodeInspectorClient;
28+
29+
class InspectorSession {
30+
public:
31+
InspectorSession(int session_id, std::shared_ptr<NodeInspectorClient> client);
32+
~InspectorSession();
33+
void Dispatch(const v8_inspector::StringView& message);
34+
private:
35+
int session_id_;
36+
std::shared_ptr<NodeInspectorClient> client_;
37+
};
2638

2739
class InspectorSessionDelegate {
2840
public:
2941
virtual ~InspectorSessionDelegate() = default;
30-
virtual bool WaitForFrontendMessageWhilePaused() = 0;
3142
virtual void SendMessageToFrontend(const v8_inspector::StringView& message)
3243
= 0;
3344
};
3445

35-
class InspectorIo;
36-
class NodeInspectorClient;
37-
3846
class Agent {
3947
public:
4048
explicit Agent(node::Environment* env);
@@ -66,19 +74,19 @@ class Agent {
6674
void RegisterAsyncHook(v8::Isolate* isolate,
6775
v8::Local<v8::Function> enable_function,
6876
v8::Local<v8::Function> disable_function);
77+
void EnableAsyncHook();
78+
void DisableAsyncHook();
6979

70-
// These methods are called by the WS protocol and JS binding to create
71-
// inspector sessions. The inspector responds by using the delegate to send
72-
// messages back.
73-
void Connect(InspectorSessionDelegate* delegate);
74-
void Disconnect();
75-
void Dispatch(const v8_inspector::StringView& message);
76-
InspectorSessionDelegate* delegate();
80+
// Called by the WS protocol and JS binding to create inspector sessions.
81+
// The inspector responds by using the delegate to send messages back.
82+
std::unique_ptr<InspectorSession> Connect(
83+
std::unique_ptr<InspectorSessionDelegate> delegate);
7784

78-
void RunMessageLoop();
79-
bool enabled() { return enabled_; }
8085
void PauseOnNextJavascriptStatement(const std::string& reason);
8186

87+
// Returns true as long as there is at least one connected session.
88+
bool HasConnectedSessions();
89+
8290
InspectorIo* io() {
8391
return io_.get();
8492
}
@@ -92,18 +100,14 @@ class Agent {
92100
DebugOptions& options() { return debug_options_; }
93101
void ContextCreated(v8::Local<v8::Context> context, const ContextInfo& info);
94102

95-
void EnableAsyncHook();
96-
void DisableAsyncHook();
97-
98103
private:
99104
void ToggleAsyncHook(v8::Isolate* isolate,
100105
const Persistent<v8::Function>& fn);
101106

102107
node::Environment* parent_env_;
103-
std::unique_ptr<NodeInspectorClient> client_;
108+
std::shared_ptr<NodeInspectorClient> client_;
104109
std::unique_ptr<InspectorIo> io_;
105110
v8::Platform* platform_;
106-
bool enabled_;
107111
std::string path_;
108112
DebugOptions debug_options_;
109113

0 commit comments

Comments
 (0)