Skip to content

Commit 700459e

Browse files
alexkozytargos
authored andcommitted
inspector: added NodeRuntime domain
Historically Node process sends Runtime.executionContextDestroyed with main context as argument when it is finished. This approach has some disadvantages. V8 prevents running some protocol command on destroyed contexts, e.g. Runtime.evaluate will return an error or Debugger.enable won't return a list of scripts. Both command might be useful for different tools, e.g. tool runs Profiler.startPreciseCoverage and at the end of node process it would like to get list of all scripts to match data to source code. Or some tooling frontend would like to provide capabilities to run commands in console when node process is finished to allow user to inspect state of the program at exit. This PR adds new domain: NodeRuntime. After NodeRuntime.notifyWhenWaitingForDisconnect is enabled by at least one client, node will send NodeRuntime.waitingForDebuggerToDisconnect event instead of Runtime.executionContextDestroyed. Based on this signal any protocol client can capture all required information and then disconnect its session. PR-URL: #27600 Reviewed-By: Eugene Ostroukhov <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 6f23816 commit 700459e

7 files changed

+175
-5
lines changed

src/inspector/node_inspector.gypi

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeWorker.h',
1010
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.cpp',
1111
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.h',
12+
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.cpp',
13+
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h',
1214
],
1315
'node_protocol_files': [
1416
'<(protocol_tool_path)/lib/Allocator_h.template',
@@ -55,6 +57,8 @@
5557
'../../src/inspector/main_thread_interface.h',
5658
'../../src/inspector/node_string.cc',
5759
'../../src/inspector/node_string.h',
60+
'../../src/inspector/runtime_agent.cc',
61+
'../../src/inspector/runtime_agent.h',
5862
'../../src/inspector/tracing_agent.cc',
5963
'../../src/inspector/tracing_agent.h',
6064
'../../src/inspector/worker_agent.cc',

src/inspector/node_protocol.pdl

+13
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,16 @@ experimental domain NodeWorker
9292
# Identifier of a session which sends a message.
9393
SessionID sessionId
9494
string message
95+
96+
# Support for inspecting node process state.
97+
experimental domain NodeRuntime
98+
# Enable the `NodeRuntime.waitingForDisconnect`.
99+
command notifyWhenWaitingForDisconnect
100+
parameters
101+
boolean enabled
102+
103+
# This event is fired instead of `Runtime.executionContextDestroyed` when
104+
# enabled.
105+
# It is fired when the Node process finished all code execution and is
106+
# waiting for all frontends to disconnect.
107+
event waitingForDisconnect

src/inspector/runtime_agent.cc

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include "runtime_agent.h"
2+
3+
#include "env-inl.h"
4+
#include "inspector_agent.h"
5+
6+
namespace node {
7+
namespace inspector {
8+
namespace protocol {
9+
10+
RuntimeAgent::RuntimeAgent(Environment* env)
11+
: notify_when_waiting_for_disconnect_(false), env_(env) {}
12+
13+
void RuntimeAgent::Wire(UberDispatcher* dispatcher) {
14+
frontend_ = std::make_unique<NodeRuntime::Frontend>(dispatcher->channel());
15+
NodeRuntime::Dispatcher::wire(dispatcher, this);
16+
}
17+
18+
DispatchResponse RuntimeAgent::notifyWhenWaitingForDisconnect(bool enabled) {
19+
if (!env_->owns_process_state()) {
20+
return DispatchResponse::Error(
21+
"NodeRuntime domain can only be used through main thread sessions");
22+
}
23+
notify_when_waiting_for_disconnect_ = enabled;
24+
return DispatchResponse::OK();
25+
}
26+
27+
bool RuntimeAgent::notifyWaitingForDisconnect() {
28+
if (notify_when_waiting_for_disconnect_) {
29+
frontend_->waitingForDisconnect();
30+
return true;
31+
}
32+
return false;
33+
}
34+
} // namespace protocol
35+
} // namespace inspector
36+
} // namespace node

src/inspector/runtime_agent.h

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndef SRC_INSPECTOR_RUNTIME_AGENT_H_
2+
#define SRC_INSPECTOR_RUNTIME_AGENT_H_
3+
4+
#include "node/inspector/protocol/NodeRuntime.h"
5+
#include "v8.h"
6+
7+
namespace node {
8+
class Environment;
9+
10+
namespace inspector {
11+
namespace protocol {
12+
13+
class RuntimeAgent : public NodeRuntime::Backend {
14+
public:
15+
explicit RuntimeAgent(Environment* env);
16+
17+
void Wire(UberDispatcher* dispatcher);
18+
19+
DispatchResponse notifyWhenWaitingForDisconnect(bool enabled) override;
20+
21+
bool notifyWaitingForDisconnect();
22+
23+
private:
24+
std::shared_ptr<NodeRuntime::Frontend> frontend_;
25+
bool notify_when_waiting_for_disconnect_;
26+
Environment* env_;
27+
};
28+
} // namespace protocol
29+
} // namespace inspector
30+
} // namespace node
31+
32+
#endif // SRC_INSPECTOR_RUNTIME_AGENT_H_

src/inspector_agent.cc

+41-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "inspector/main_thread_interface.h"
44
#include "inspector/node_string.h"
5+
#include "inspector/runtime_agent.h"
56
#include "inspector/tracing_agent.h"
67
#include "inspector/worker_agent.h"
78
#include "inspector/worker_inspector.h"
@@ -221,21 +222,26 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
221222
std::unique_ptr<InspectorSessionDelegate> delegate,
222223
std::shared_ptr<MainThreadHandle> main_thread_,
223224
bool prevent_shutdown)
224-
: delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown) {
225+
: delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown),
226+
retaining_context_(false) {
225227
session_ = inspector->connect(1, this, StringView());
226228
node_dispatcher_ = std::make_unique<protocol::UberDispatcher>(this);
227229
tracing_agent_ =
228230
std::make_unique<protocol::TracingAgent>(env, main_thread_);
229231
tracing_agent_->Wire(node_dispatcher_.get());
230232
worker_agent_ = std::make_unique<protocol::WorkerAgent>(worker_manager);
231233
worker_agent_->Wire(node_dispatcher_.get());
234+
runtime_agent_ = std::make_unique<protocol::RuntimeAgent>(env);
235+
runtime_agent_->Wire(node_dispatcher_.get());
232236
}
233237

234238
~ChannelImpl() override {
235239
tracing_agent_->disable();
236240
tracing_agent_.reset(); // Dispose before the dispatchers
237241
worker_agent_->disable();
238242
worker_agent_.reset(); // Dispose before the dispatchers
243+
runtime_agent_->disable();
244+
runtime_agent_.reset(); // Dispose before the dispatchers
239245
}
240246

241247
void dispatchProtocolMessage(const StringView& message) {
@@ -264,6 +270,15 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
264270
return prevent_shutdown_;
265271
}
266272

273+
bool notifyWaitingForDisconnect() {
274+
retaining_context_ = runtime_agent_->notifyWaitingForDisconnect();
275+
return retaining_context_;
276+
}
277+
278+
bool retainingContext() {
279+
return retaining_context_;
280+
}
281+
267282
private:
268283
void sendResponse(
269284
int callId,
@@ -303,12 +318,14 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
303318
DCHECK(false);
304319
}
305320

321+
std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
306322
std::unique_ptr<protocol::TracingAgent> tracing_agent_;
307323
std::unique_ptr<protocol::WorkerAgent> worker_agent_;
308324
std::unique_ptr<InspectorSessionDelegate> delegate_;
309325
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
310326
std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
311327
bool prevent_shutdown_;
328+
bool retaining_context_;
312329
};
313330

314331
class InspectorTimer {
@@ -511,7 +528,18 @@ class NodeInspectorClient : public V8InspectorClient {
511528
}
512529

513530
void disconnectFrontend(int session_id) {
514-
channels_.erase(session_id);
531+
auto it = channels_.find(session_id);
532+
if (it == channels_.end())
533+
return;
534+
bool retaining_context = it->second->retainingContext();
535+
channels_.erase(it);
536+
if (retaining_context) {
537+
for (const auto& id_channel : channels_) {
538+
if (id_channel.second->retainingContext())
539+
return;
540+
}
541+
contextDestroyed(env_->context());
542+
}
515543
}
516544

517545
void dispatchMessageFromFrontend(int session_id, const StringView& message) {
@@ -608,6 +636,15 @@ class NodeInspectorClient : public V8InspectorClient {
608636
return false;
609637
}
610638

639+
bool notifyWaitingForDisconnect() {
640+
bool retaining_context = false;
641+
for (const auto& id_channel : channels_) {
642+
if (id_channel.second->notifyWaitingForDisconnect())
643+
retaining_context = true;
644+
}
645+
return retaining_context;
646+
}
647+
611648
std::shared_ptr<MainThreadHandle> getThreadHandle() {
612649
if (interface_ == nullptr) {
613650
interface_.reset(new MainThreadInterface(
@@ -774,9 +811,8 @@ void Agent::WaitForDisconnect() {
774811
fprintf(stderr, "Waiting for the debugger to disconnect...\n");
775812
fflush(stderr);
776813
}
777-
// TODO(addaleax): Maybe this should use an at-exit hook for the Environment
778-
// or something similar?
779-
client_->contextDestroyed(parent_env_->context());
814+
if (!client_->notifyWaitingForDisconnect())
815+
client_->contextDestroyed(parent_env_->context());
780816
if (io_ != nullptr) {
781817
io_->StopAcceptingNewConnections();
782818
client_->waitForIoShutdown();

test/common/inspector-helper.js

+4
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ class InspectorSession {
191191
}
192192
}
193193

194+
unprocessedNotifications() {
195+
return this._unprocessedNotifications;
196+
}
197+
194198
_sendMessage(message) {
195199
const msg = JSON.parse(JSON.stringify(message)); // Clone!
196200
msg.id = this._nextId++;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
const common = require('../common');
4+
5+
common.skipIfInspectorDisabled();
6+
7+
const assert = require('assert');
8+
const { NodeInstance } = require('../common/inspector-helper.js');
9+
10+
function mainContextDestroyed(notification) {
11+
return notification.method === 'Runtime.executionContextDestroyed' &&
12+
notification.params.executionContextId === 1;
13+
}
14+
15+
async function runTest() {
16+
const child = new NodeInstance(['--inspect-brk=0', '-e', 'process.exit(55)']);
17+
const session = await child.connectInspectorSession();
18+
const oldStyleSession = await child.connectInspectorSession();
19+
await oldStyleSession.send([
20+
{ method: 'Runtime.enable' }]);
21+
await session.send([
22+
{ method: 'Runtime.enable' },
23+
{ method: 'NodeRuntime.notifyWhenWaitingForDisconnect',
24+
params: { enabled: true } },
25+
{ method: 'Runtime.runIfWaitingForDebugger' }]);
26+
await session.waitForNotification((notification) => {
27+
return notification.method === 'NodeRuntime.waitingForDisconnect';
28+
});
29+
const receivedExecutionContextDestroyed =
30+
session.unprocessedNotifications().some(mainContextDestroyed);
31+
if (receivedExecutionContextDestroyed) {
32+
assert.fail('When NodeRuntime enabled, ' +
33+
'Runtime.executionContextDestroyed should not be sent');
34+
}
35+
const { result: { value } } = await session.send({
36+
method: 'Runtime.evaluate', params: { expression: '42' }
37+
});
38+
assert.strictEqual(value, 42);
39+
await session.disconnect();
40+
await oldStyleSession.waitForNotification(mainContextDestroyed);
41+
await oldStyleSession.disconnect();
42+
assert.strictEqual((await child.expectShutdown()).exitCode, 55);
43+
}
44+
45+
runTest();

0 commit comments

Comments
 (0)