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: introduce the --inspect-wait flag #52734

Merged
merged 3 commits into from May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/api/cli.md
Expand Up @@ -1233,6 +1233,7 @@ Activate inspector on `host:port`. Default is `127.0.0.1:9229`.
V8 inspector integration allows tools such as Chrome DevTools and IDEs to debug
and profile Node.js instances. The tools attach to Node.js instances via a
tcp port and communicate using the [Chrome DevTools Protocol][].
See [V8 Inspector integration for Node.js][] for further explanation on Node.js debugger.

<!-- Anchor to make sure old links find a target -->

Expand Down Expand Up @@ -1263,6 +1264,8 @@ added: v7.6.0
Activate inspector on `host:port` and break at start of user script.
Default `host:port` is `127.0.0.1:9229`.

See [V8 Inspector integration for Node.js][] for further explanation on Node.js debugger.

### `--inspect-port=[host:]port`

<!-- YAML
Expand All @@ -1284,6 +1287,17 @@ Specify ways of the inspector web socket url exposure.
By default inspector websocket url is available in stderr and under `/json/list`
endpoint on `http://host:port/json/list`.

### `--inspect-wait[=[host:]port]`

<!-- YAML
added: REPLACEME
-->

Activate inspector on `host:port` and wait for debugger to be attached.
Default `host:port` is `127.0.0.1:9229`.
Copy link
Member

Choose a reason for hiding this comment

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

The docs should likely include additional detail explaining the difference between this and --inspect-brk. Just looking at this I would have no idea as an unfamiliar user which to choose and why.


See [V8 Inspector integration for Node.js][] for further explanation on Node.js debugger.

### `-i`, `--interactive`

<!-- YAML
Expand Down Expand Up @@ -2622,6 +2636,7 @@ one is included in the list below.
* `--inspect-brk`
* `--inspect-port`, `--debug-port`
* `--inspect-publish-uid`
* `--inspect-wait`
* `--inspect`
* `--max-http-header-size`
* `--napi-modules`
Expand Down Expand Up @@ -3112,6 +3127,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
[ShadowRealm]: https://github.com/tc39/proposal-shadowrealm
[Source Map]: https://sourcemaps.info/spec.html
[V8 Inspector integration for Node.js]: debugger.md#v8-inspector-integration-for-nodejs
[V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html
[V8 code cache]: https://v8.dev/blog/code-caching-for-devs
[`"type"`]: packages.md#type
Expand Down
17 changes: 15 additions & 2 deletions doc/api/debugger.md
Expand Up @@ -234,8 +234,21 @@ V8 Inspector can be enabled by passing the `--inspect` flag when starting a
Node.js application. It is also possible to supply a custom port with that flag,
e.g. `--inspect=9222` will accept DevTools connections on port 9222.

To break on the first line of the application code, pass the `--inspect-brk`
flag instead of `--inspect`.
Using the `--inspect` flag will execute the code immediately before debugger is connected.
This means that the code will start running before you can start debugging, which might
not be ideal if you want to debug from the very beginning.

In such cases, you have two alternatives:

1. `--inspect-wait` flag: This flag will wait for debugger to be attached before executing the code.
This allows you to start debugging right from the beginning of the execution.
2. `--inspect-brk` flag: Unlike `--inspect`, this flag will break on the first line of the code
as soon as debugger is attached. This is useful when you want to debug the code step by step
from the very beginning, without any code execution prior to debugging.

So, when deciding between `--inspect`, `--inspect-wait`, and `--inspect-brk`, consider whether you want
the code to start executing immediately, wait for debugger to be attached before execution,
or break on the first line for step-by-step debugging.

```console
$ node --inspect index.js
Expand Down
5 changes: 5 additions & 0 deletions doc/node.1
Expand Up @@ -267,6 +267,11 @@ and
Default is
.Sy stderr,http .
.
.It Fl -inspect-wait Ns = Ns Ar [host:]port
Activate inspector on
.Ar host:port
and wait for debugger to be attached.
.
.It Fl -inspect Ns = Ns Ar [host:]port
Activate inspector on
.Ar host:port .
Expand Down
20 changes: 12 additions & 8 deletions src/inspector_agent.cc
Expand Up @@ -743,20 +743,24 @@ bool Agent::Start(const std::string& path,
}, parent_env_);

bool wait_for_connect = options.wait_for_connect();
bool should_break_first_line = options.should_break_first_line();
if (parent_handle_) {
wait_for_connect = parent_handle_->WaitForConnect();
parent_handle_->WorkerStarted(client_->getThreadHandle(), wait_for_connect);
should_break_first_line = parent_handle_->WaitForConnect();
parent_handle_->WorkerStarted(client_->getThreadHandle(),
should_break_first_line);
} else if (!options.inspector_enabled || !options.allow_attaching_debugger ||
!StartIoThread()) {
return false;
}

// Patch the debug options to implement waitForDebuggerOnStart for
// the NodeWorker.enable method.
if (wait_for_connect) {
CHECK(!parent_env_->has_serialized_options());
debug_options_.EnableBreakFirstLine();
parent_env_->options()->get_debug_options()->EnableBreakFirstLine();
if (wait_for_connect || should_break_first_line) {
// Patch the debug options to implement waitForDebuggerOnStart for
// the NodeWorker.enable method.
if (should_break_first_line) {
CHECK(!parent_env_->has_serialized_options());
debug_options_.EnableBreakFirstLine();
parent_env_->options()->get_debug_options()->EnableBreakFirstLine();
}
client_->waitForFrontend();
}
return true;
Expand Down
8 changes: 8 additions & 0 deletions src/node_options.cc
Expand Up @@ -331,6 +331,14 @@ DebugOptionsParser::DebugOptionsParser() {
Implies("--inspect-brk-node", "--inspect");
AddAlias("--inspect-brk-node=", { "--inspect-port", "--inspect-brk-node" });

AddOption(
"--inspect-wait",
"activate inspector on host:port and wait for debugger to be attached",
&DebugOptions::inspect_wait,
kAllowedInEnvvar);
Implies("--inspect-wait", "--inspect");
AddAlias("--inspect-wait=", {"--inspect-port", "--inspect-wait"});

AddOption("--inspect-publish-uid",
"comma separated list of destinations for inspector uid"
"(default: stderr,http)",
Expand Down
6 changes: 6 additions & 0 deletions src/node_options.h
Expand Up @@ -71,6 +71,8 @@ class DebugOptions : public Options {
bool allow_attaching_debugger = true;
// --inspect
bool inspector_enabled = false;
// --inspect-wait
bool inspect_wait = false;
// --debug
bool deprecated_debug = false;
// --inspect-brk
Expand All @@ -93,6 +95,10 @@ class DebugOptions : public Options {
}

bool wait_for_connect() const {
return break_first_line || break_node_first_line || inspect_wait;
daeyeon marked this conversation as resolved.
Show resolved Hide resolved
}

bool should_break_first_line() const {
return break_first_line || break_node_first_line;
}

Expand Down
28 changes: 28 additions & 0 deletions test/parallel/test-inspector-wait.mjs
@@ -0,0 +1,28 @@
import * as common from '../common/index.mjs';

common.skipIfInspectorDisabled();

import assert from 'node:assert';
import { NodeInstance } from '../common/inspector-helper.js';


async function runTests() {
const child = new NodeInstance(['--inspect-wait=0'], 'console.log(0);');
const session = await child.connectInspectorSession();
await session.send({ method: 'NodeRuntime.enable' });
await session.waitForNotification('NodeRuntime.waitingForDebugger');

// The execution should be paused until the debugger is attached
while (await child.nextStderrString() !== 'Debugger attached.');

await session.send({ 'method': 'Runtime.runIfWaitingForDebugger' });

// Wait for the execution to finish
while (await child.nextStderrString() !== 'Waiting for the debugger to disconnect...');

await session.send({ method: 'NodeRuntime.disable' });
session.disconnect();
assert.strictEqual((await child.expectShutdown()).exitCode, 0);
}

runTests().then(common.mustCall());