Skip to content

Commit dc33ae1

Browse files
alexkozygntem
authored andcommitted
inspector: add inspector.waitForDebugger()
This method blocks current node process until a client sends Runtime.runifWaitingForDebugger. It can be useful when we need to report inspector.url() before waiting for connection: ``` inspector.open(0, undefined, false); fs.writeFileSync(someFileName, inspector.url()); inspector.waitForDebugger(); ``` PR-URL: nodejs#28453 Reviewed-By: Eugene Ostroukhov <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent ad54edb commit dc33ae1

File tree

6 files changed

+107
-7
lines changed

6 files changed

+107
-7
lines changed

doc/api/errors.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,11 @@ after the session had already closed.
11881188

11891189
An error occurred while issuing a command via the `inspector` module.
11901190

1191+
<a id="ERR_INSPECTOR_NOT_ACTIVE"></a>
1192+
### ERR_INSPECTOR_NOT_ACTIVE
1193+
1194+
The `inspector` is not active when `inspector.waitForDebugger()` is called.
1195+
11911196
<a id="ERR_INSPECTOR_NOT_AVAILABLE"></a>
11921197
### ERR_INSPECTOR_NOT_AVAILABLE
11931198

doc/api/inspector.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ parameter usage.
5252

5353
Return the URL of the active inspector, or `undefined` if there is none.
5454

55+
## inspector.waitForDebugger()
56+
<!-- YAML
57+
added: REPLACEME
58+
-->
59+
60+
Blocks until a client (existing or connected later) has sent
61+
`Runtime.runIfWaitingForDebugger` command.
62+
63+
An exception will be thrown if there is no active inspector.
64+
5565
## Class: inspector.Session
5666

5767
The `inspector.Session` is used for dispatching messages to the V8 inspector

lib/inspector.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
ERR_INSPECTOR_COMMAND,
99
ERR_INSPECTOR_NOT_AVAILABLE,
1010
ERR_INSPECTOR_NOT_CONNECTED,
11+
ERR_INSPECTOR_NOT_ACTIVE,
1112
ERR_INVALID_ARG_TYPE,
1213
ERR_INVALID_CALLBACK
1314
} = require('internal/errors').codes;
@@ -19,7 +20,12 @@ if (!hasInspector)
1920
const EventEmitter = require('events');
2021
const { validateString } = require('internal/validators');
2122
const util = require('util');
22-
const { Connection, open, url } = internalBinding('inspector');
23+
const {
24+
Connection,
25+
open,
26+
url,
27+
waitForDebugger
28+
} = internalBinding('inspector');
2329

2430
const connectionSymbol = Symbol('connectionProperty');
2531
const messageCallbacksSymbol = Symbol('messageCallbacks');
@@ -105,10 +111,22 @@ class Session extends EventEmitter {
105111
}
106112
}
107113

114+
function inspectorOpen(port, host, wait) {
115+
open(port, host);
116+
if (wait)
117+
waitForDebugger();
118+
}
119+
120+
function inspectorWaitForDebugger() {
121+
if (!waitForDebugger())
122+
throw new ERR_INSPECTOR_NOT_ACTIVE();
123+
}
124+
108125
module.exports = {
109-
open: (port, host, wait) => open(port, host, !!wait),
126+
open: inspectorOpen,
110127
close: process._debugEnd,
111128
url: url,
129+
waitForDebugger: inspectorWaitForDebugger,
112130
// This is dynamically added during bootstrap,
113131
// where the console from the VM is still available
114132
console: require('internal/util/inspector').consoleFromVM,

lib/internal/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' +
887887
E('ERR_INSPECTOR_ALREADY_CONNECTED', '%s is already connected', Error);
888888
E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
889889
E('ERR_INSPECTOR_COMMAND', 'Inspector error %d: %s', Error);
890+
E('ERR_INSPECTOR_NOT_ACTIVE', 'Inspector is not active', Error);
890891
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error);
891892
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error);
892893
E('ERR_INTERNAL_ASSERTION', (message) => {

src/inspector_js_api.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ void IsEnabled(const FunctionCallbackInfo<Value>& args) {
237237
void Open(const FunctionCallbackInfo<Value>& args) {
238238
Environment* env = Environment::GetCurrent(args);
239239
Agent* agent = env->inspector_agent();
240-
bool wait_for_connect = false;
241240

242241
if (args.Length() > 0 && args[0]->IsUint32()) {
243242
uint32_t port = args[0].As<Uint32>()->Value();
@@ -249,12 +248,15 @@ void Open(const FunctionCallbackInfo<Value>& args) {
249248
agent->host_port()->set_host(*host);
250249
}
251250

252-
if (args.Length() > 2 && args[2]->IsBoolean()) {
253-
wait_for_connect = args[2]->BooleanValue(args.GetIsolate());
254-
}
255251
agent->StartIoThread();
256-
if (wait_for_connect)
252+
}
253+
254+
void WaitForDebugger(const FunctionCallbackInfo<Value>& args) {
255+
Environment* env = Environment::GetCurrent(args);
256+
Agent* agent = env->inspector_agent();
257+
if (agent->IsActive())
257258
agent->WaitForConnect();
259+
args.GetReturnValue().Set(agent->IsActive());
258260
}
259261

260262
void Url(const FunctionCallbackInfo<Value>& args) {
@@ -285,6 +287,7 @@ void Initialize(Local<Object> target, Local<Value> unused,
285287
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
286288
env->SetMethod(target, "open", Open);
287289
env->SetMethodNoSideEffect(target, "url", Url);
290+
env->SetMethod(target, "waitForDebugger", WaitForDebugger);
288291

289292
env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper);
290293
env->SetMethod(target, "asyncTaskCanceled",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Flags: --expose-internals
2+
3+
'use strict';
4+
const common = require('../common');
5+
6+
common.skipIfInspectorDisabled();
7+
8+
const assert = require('assert');
9+
const { NodeInstance } = require('../common/inspector-helper.js');
10+
11+
async function runTests() {
12+
const child = new NodeInstance(['-e', `(${main.toString()})()`], '', '');
13+
const session = await child.connectInspectorSession();
14+
await session.send({ method: 'Runtime.enable' });
15+
// Check that there is only one console message received.
16+
await session.waitForConsoleOutput('log', 'before wait for debugger');
17+
assert.ok(!session.unprocessedNotifications()
18+
.some((n) => n.method === 'Runtime.consoleAPICalled'));
19+
// Check that inspector.url() is available between inspector.open() and
20+
// inspector.waitForDebugger()
21+
const { result: { value } } = await session.send({
22+
method: 'Runtime.evaluate',
23+
params: {
24+
expression: 'process._ws',
25+
includeCommandLineAPI: true
26+
}
27+
});
28+
assert.ok(value.startsWith('ws://'));
29+
session.send({ method: 'Runtime.runIfWaitingForDebugger' });
30+
// Check that messages after first and before second waitForDebugger are
31+
// received
32+
await session.waitForConsoleOutput('log', 'after wait for debugger');
33+
await session.waitForConsoleOutput('log', 'before second wait for debugger');
34+
assert.ok(!session.unprocessedNotifications()
35+
.some((n) => n.method === 'Runtime.consoleAPICalled'));
36+
const secondSession = await child.connectInspectorSession();
37+
// Check that inspector.waitForDebugger can be resumed from another session
38+
secondSession.send({ method: 'Runtime.runIfWaitingForDebugger' });
39+
await session.waitForConsoleOutput('log', 'after second wait for debugger');
40+
assert.ok(!session.unprocessedNotifications()
41+
.some((n) => n.method === 'Runtime.consoleAPICalled'));
42+
secondSession.disconnect();
43+
session.disconnect();
44+
45+
function main(prefix) {
46+
const inspector = require('inspector');
47+
inspector.open(0, undefined, false);
48+
process._ws = inspector.url();
49+
console.log('before wait for debugger');
50+
inspector.waitForDebugger();
51+
console.log('after wait for debugger');
52+
console.log('before second wait for debugger');
53+
inspector.waitForDebugger();
54+
console.log('after second wait for debugger');
55+
}
56+
57+
// Check that inspector.waitForDebugger throws if there is no active
58+
// inspector
59+
const re = /^Error \[ERR_INSPECTOR_NOT_ACTIVE\]: Inspector is not active$/;
60+
assert.throws(() => require('inspector').waitForDebugger(), re);
61+
}
62+
63+
runTests();

0 commit comments

Comments
 (0)