Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inspector: added NodeRuntime domain #27600

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/inspector/node_inspector.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeWorker.h',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.cpp',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.h',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.cpp',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h',
],
'node_protocol_files': [
'<(protocol_tool_path)/lib/Allocator_h.template',
Expand Down Expand Up @@ -55,6 +57,8 @@
'../../src/inspector/main_thread_interface.h',
'../../src/inspector/node_string.cc',
'../../src/inspector/node_string.h',
'../../src/inspector/runtime_agent.cc',
'../../src/inspector/runtime_agent.h',
'../../src/inspector/tracing_agent.cc',
'../../src/inspector/tracing_agent.h',
'../../src/inspector/worker_agent.cc',
Expand Down
13 changes: 13 additions & 0 deletions src/inspector/node_protocol.pdl
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,16 @@ experimental domain NodeWorker
# Identifier of a session which sends a message.
SessionID sessionId
string message

# Support for inspecting node process state.
experimental domain NodeRuntime
# Enable the `NodeRuntime.waitingForDisconnect`.
command notifyWhenWaitingForDisconnect
parameters
boolean enabled

# This event is fired instead of `Runtime.executionContextDestroyed` when
# enabled.
# It is fired when the Node process finished all code execution and is
# waiting for all frontends to disconnect.
event waitingForDisconnect
eugeneo marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it needs to be communecated clearly on the purpose of this notification vs the Runtime.executionContextDestroyed - specifically, that the main context is retained and can still be used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated comment.

36 changes: 36 additions & 0 deletions src/inspector/runtime_agent.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "runtime_agent.h"

#include "env-inl.h"
#include "inspector_agent.h"

namespace node {
namespace inspector {
namespace protocol {

RuntimeAgent::RuntimeAgent(Environment* env)
: notify_when_waiting_for_disconnect_(false), env_(env) {}

void RuntimeAgent::Wire(UberDispatcher* dispatcher) {
frontend_ = std::make_unique<NodeRuntime::Frontend>(dispatcher->channel());
NodeRuntime::Dispatcher::wire(dispatcher, this);
}

DispatchResponse RuntimeAgent::notifyWhenWaitingForDisconnect(bool enabled) {
if (!env_->owns_process_state()) {
return DispatchResponse::Error(
"NodeRuntime domain can only be used through main thread sessions");
}
notify_when_waiting_for_disconnect_ = enabled;
return DispatchResponse::OK();
}

bool RuntimeAgent::notifyWaitingForDisconnect() {
if (notify_when_waiting_for_disconnect_) {
frontend_->waitingForDisconnect();
return true;
}
return false;
}
} // namespace protocol
} // namespace inspector
} // namespace node
32 changes: 32 additions & 0 deletions src/inspector/runtime_agent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef SRC_INSPECTOR_RUNTIME_AGENT_H_
#define SRC_INSPECTOR_RUNTIME_AGENT_H_

#include "node/inspector/protocol/NodeRuntime.h"
#include "v8.h"

namespace node {
class Environment;

namespace inspector {
namespace protocol {

class RuntimeAgent : public NodeRuntime::Backend {
public:
explicit RuntimeAgent(Environment* env);

void Wire(UberDispatcher* dispatcher);

DispatchResponse notifyWhenWaitingForDisconnect(bool enabled) override;

bool notifyWaitingForDisconnect();

private:
std::shared_ptr<NodeRuntime::Frontend> frontend_;
bool notify_when_waiting_for_disconnect_;
Environment* env_;
};
} // namespace protocol
} // namespace inspector
} // namespace node

#endif // SRC_INSPECTOR_RUNTIME_AGENT_H_
46 changes: 41 additions & 5 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "inspector/main_thread_interface.h"
#include "inspector/node_string.h"
#include "inspector/runtime_agent.h"
#include "inspector/tracing_agent.h"
#include "inspector/worker_agent.h"
#include "inspector/worker_inspector.h"
Expand Down Expand Up @@ -221,21 +222,26 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
std::unique_ptr<InspectorSessionDelegate> delegate,
std::shared_ptr<MainThreadHandle> main_thread_,
bool prevent_shutdown)
: delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown) {
: delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown),
retaining_context_(false) {
session_ = inspector->connect(1, this, StringView());
node_dispatcher_ = std::make_unique<protocol::UberDispatcher>(this);
tracing_agent_ =
std::make_unique<protocol::TracingAgent>(env, main_thread_);
tracing_agent_->Wire(node_dispatcher_.get());
worker_agent_ = std::make_unique<protocol::WorkerAgent>(worker_manager);
worker_agent_->Wire(node_dispatcher_.get());
runtime_agent_ = std::make_unique<protocol::RuntimeAgent>(env);
runtime_agent_->Wire(node_dispatcher_.get());
}

~ChannelImpl() override {
tracing_agent_->disable();
tracing_agent_.reset(); // Dispose before the dispatchers
worker_agent_->disable();
worker_agent_.reset(); // Dispose before the dispatchers
runtime_agent_->disable();
runtime_agent_.reset(); // Dispose before the dispatchers
}

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

bool notifyWaitingForDisconnect() {
retaining_context_ = runtime_agent_->notifyWaitingForDisconnect();
return retaining_context_;
}

bool retainingContext() {
return retaining_context_;
}

private:
void sendResponse(
int callId,
Expand Down Expand Up @@ -303,12 +318,14 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
DCHECK(false);
}

std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
std::unique_ptr<protocol::TracingAgent> tracing_agent_;
std::unique_ptr<protocol::WorkerAgent> worker_agent_;
std::unique_ptr<InspectorSessionDelegate> delegate_;
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
bool prevent_shutdown_;
bool retaining_context_;
};

class InspectorTimer {
Expand Down Expand Up @@ -511,7 +528,18 @@ class NodeInspectorClient : public V8InspectorClient {
}

void disconnectFrontend(int session_id) {
channels_.erase(session_id);
auto it = channels_.find(session_id);
if (it == channels_.end())
return;
bool retaining_context = it->second->retainingContext();
channels_.erase(it);
if (retaining_context) {
for (const auto& id_channel : channels_) {
if (id_channel.second->retainingContext())
return;
}
contextDestroyed(env_->context());
}
}

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

bool notifyWaitingForDisconnect() {
bool retaining_context = false;
for (const auto& id_channel : channels_) {
if (id_channel.second->notifyWaitingForDisconnect())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this notification will only get sent to the sessions that requested it. Others will get nothing, not even an Runtime.executionContextDestroyed.

I suggest sending other sessions executionContextDestroyed either as al else statement for this if or at a later point, once all sessions that requested a disconnect notification disconnect.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not possible to send executionContextDestroyed without dirty hacks. We need to intercept every Runtime.enable and Runtime.disable calls to maintain runtime enabled state on node side. At the same time we can not just calls executionContextDestroyed on inspector since then inspector will mark context as destroyed and any later calls that uses Runtime will return an error.

Based on how much hacks we need to add, how many clients there are in a wild right now, what is probability of using new client and old client at the same time I believe that we can just send nothing here. Protocol definition stated exactly this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to adopt solution that you proposed and it seems like it works!

retaining_context = true;
}
return retaining_context;
}

std::shared_ptr<MainThreadHandle> getThreadHandle() {
if (interface_ == nullptr) {
interface_.reset(new MainThreadInterface(
Expand Down Expand Up @@ -774,9 +811,8 @@ void Agent::WaitForDisconnect() {
fprintf(stderr, "Waiting for the debugger to disconnect...\n");
fflush(stderr);
}
// TODO(addaleax): Maybe this should use an at-exit hook for the Environment
// or something similar?
client_->contextDestroyed(parent_env_->context());
if (!client_->notifyWaitingForDisconnect())
client_->contextDestroyed(parent_env_->context());
if (io_ != nullptr) {
io_->StopAcceptingNewConnections();
client_->waitForIoShutdown();
Expand Down
4 changes: 4 additions & 0 deletions test/common/inspector-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ class InspectorSession {
}
}

unprocessedNotifications() {
return this._unprocessedNotifications;
}

_sendMessage(message) {
const msg = JSON.parse(JSON.stringify(message)); // Clone!
msg.id = this._nextId++;
Expand Down
45 changes: 45 additions & 0 deletions test/parallel/test-inspector-waiting-for-disconnect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');

common.skipIfInspectorDisabled();

const assert = require('assert');
const { NodeInstance } = require('../common/inspector-helper.js');

function mainContextDestroyed(notification) {
return notification.method === 'Runtime.executionContextDestroyed' &&
notification.params.executionContextId === 1;
}

async function runTest() {
const child = new NodeInstance(['--inspect-brk=0', '-e', 'process.exit(55)']);
const session = await child.connectInspectorSession();
const oldStyleSession = await child.connectInspectorSession();
await oldStyleSession.send([
{ method: 'Runtime.enable' }]);
await session.send([
{ method: 'Runtime.enable' },
{ method: 'NodeRuntime.notifyWhenWaitingForDisconnect',
params: { enabled: true } },
{ method: 'Runtime.runIfWaitingForDebugger' }]);
await session.waitForNotification((notification) => {
return notification.method === 'NodeRuntime.waitingForDisconnect';
});
const receivedExecutionContextDestroyed =
session.unprocessedNotifications().some(mainContextDestroyed);
if (receivedExecutionContextDestroyed) {
assert.fail('When NodeRuntime enabled, ' +
'Runtime.executionContextDestroyed should not be sent');
}
const { result: { value } } = await session.send({
method: 'Runtime.evaluate', params: { expression: '42' }
});
assert.strictEqual(value, 42);
await session.disconnect();
await oldStyleSession.waitForNotification(mainContextDestroyed);
await oldStyleSession.disconnect();
assert.strictEqual((await child.expectShutdown()).exitCode, 55);
}

runTest();