From 77bdbc5f0d1bd7caada99d0097bca2a9bb1f4cee Mon Sep 17 00:00:00 2001 From: Aleksei Koziatinskii Date: Tue, 25 Jun 2019 15:28:56 -0700 Subject: 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: https://github.com/nodejs/node/pull/28453 Reviewed-By: Eugene Ostroukhov Reviewed-By: Ben Noordhuis Reviewed-By: Anna Henningsen Reviewed-By: Rich Trott --- doc/api/errors.md | 5 ++ doc/api/inspector.md | 10 ++++ lib/inspector.js | 22 +++++++- lib/internal/errors.js | 1 + src/inspector_js_api.cc | 13 +++-- .../parallel/test-inspector-wait-for-connection.js | 63 ++++++++++++++++++++++ 6 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 test/parallel/test-inspector-wait-for-connection.js diff --git a/doc/api/errors.md b/doc/api/errors.md index fe81168e40..6147f768c8 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1186,6 +1186,11 @@ after the session had already closed. An error occurred while issuing a command via the `inspector` module. + +### ERR_INSPECTOR_NOT_ACTIVE + +The `inspector` is not active when `inspector.waitForDebugger()` is called. + ### ERR_INSPECTOR_NOT_AVAILABLE diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 63d2a70272..e2980a278d 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -52,6 +52,16 @@ parameter usage. Return the URL of the active inspector, or `undefined` if there is none. +## inspector.waitForDebugger() + + +Blocks until a client (existing or connected later) has sent +`Runtime.runIfWaitingForDebugger` command. + +An exception will be thrown if there is no active inspector. + ## Class: inspector.Session The `inspector.Session` is used for dispatching messages to the V8 inspector diff --git a/lib/inspector.js b/lib/inspector.js index 4bec628b7d..198d87ae44 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -8,6 +8,7 @@ const { ERR_INSPECTOR_COMMAND, ERR_INSPECTOR_NOT_AVAILABLE, ERR_INSPECTOR_NOT_CONNECTED, + ERR_INSPECTOR_NOT_ACTIVE, ERR_INVALID_ARG_TYPE, ERR_INVALID_CALLBACK } = require('internal/errors').codes; @@ -19,7 +20,12 @@ if (!hasInspector) const EventEmitter = require('events'); const { validateString } = require('internal/validators'); const util = require('util'); -const { Connection, open, url } = internalBinding('inspector'); +const { + Connection, + open, + url, + waitForDebugger +} = internalBinding('inspector'); const connectionSymbol = Symbol('connectionProperty'); const messageCallbacksSymbol = Symbol('messageCallbacks'); @@ -105,10 +111,22 @@ class Session extends EventEmitter { } } +function inspectorOpen(port, host, wait) { + open(port, host); + if (wait) + waitForDebugger(); +} + +function inspectorWaitForDebugger() { + if (!waitForDebugger()) + throw new ERR_INSPECTOR_NOT_ACTIVE(); +} + module.exports = { - open: (port, host, wait) => open(port, host, !!wait), + open: inspectorOpen, close: process._debugEnd, url: url, + waitForDebugger: inspectorWaitForDebugger, // This is dynamically added during bootstrap, // where the console from the VM is still available console: require('internal/util/inspector').consoleFromVM, diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 8fa56e315e..23dbb79196 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -882,6 +882,7 @@ E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' + E('ERR_INSPECTOR_ALREADY_CONNECTED', '%s is already connected', Error); E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error); E('ERR_INSPECTOR_COMMAND', 'Inspector error %d: %s', Error); +E('ERR_INSPECTOR_NOT_ACTIVE', 'Inspector is not active', Error); E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error); E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error); E('ERR_INTERNAL_ASSERTION', (message) => { diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 3131c6ad37..8b891a2d61 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -237,7 +237,6 @@ void IsEnabled(const FunctionCallbackInfo& args) { void Open(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Agent* agent = env->inspector_agent(); - bool wait_for_connect = false; if (args.Length() > 0 && args[0]->IsUint32()) { uint32_t port = args[0].As()->Value(); @@ -249,12 +248,15 @@ void Open(const FunctionCallbackInfo& args) { agent->host_port()->set_host(*host); } - if (args.Length() > 2 && args[2]->IsBoolean()) { - wait_for_connect = args[2]->BooleanValue(args.GetIsolate()); - } agent->StartIoThread(); - if (wait_for_connect) +} + +void WaitForDebugger(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Agent* agent = env->inspector_agent(); + if (agent->IsActive()) agent->WaitForConnect(); + args.GetReturnValue().Set(agent->IsActive()); } void Url(const FunctionCallbackInfo& args) { @@ -285,6 +287,7 @@ void Initialize(Local target, Local unused, env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); env->SetMethod(target, "open", Open); env->SetMethodNoSideEffect(target, "url", Url); + env->SetMethod(target, "waitForDebugger", WaitForDebugger); env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper); env->SetMethod(target, "asyncTaskCanceled", diff --git a/test/parallel/test-inspector-wait-for-connection.js b/test/parallel/test-inspector-wait-for-connection.js new file mode 100644 index 0000000000..e6cfd81deb --- /dev/null +++ b/test/parallel/test-inspector-wait-for-connection.js @@ -0,0 +1,63 @@ +// Flags: --expose-internals + +'use strict'; +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +const assert = require('assert'); +const { NodeInstance } = require('../common/inspector-helper.js'); + +async function runTests() { + const child = new NodeInstance(['-e', `(${main.toString()})()`], '', ''); + const session = await child.connectInspectorSession(); + await session.send({ method: 'Runtime.enable' }); + // Check that there is only one console message received. + await session.waitForConsoleOutput('log', 'before wait for debugger'); + assert.ok(!session.unprocessedNotifications() + .some((n) => n.method === 'Runtime.consoleAPICalled')); + // Check that inspector.url() is available between inspector.open() and + // inspector.waitForDebugger() + const { result: { value } } = await session.send({ + method: 'Runtime.evaluate', + params: { + expression: 'process._ws', + includeCommandLineAPI: true + } + }); + assert.ok(value.startsWith('ws://')); + session.send({ method: 'Runtime.runIfWaitingForDebugger' }); + // Check that messages after first and before second waitForDebugger are + // received + await session.waitForConsoleOutput('log', 'after wait for debugger'); + await session.waitForConsoleOutput('log', 'before second wait for debugger'); + assert.ok(!session.unprocessedNotifications() + .some((n) => n.method === 'Runtime.consoleAPICalled')); + const secondSession = await child.connectInspectorSession(); + // Check that inspector.waitForDebugger can be resumed from another session + secondSession.send({ method: 'Runtime.runIfWaitingForDebugger' }); + await session.waitForConsoleOutput('log', 'after second wait for debugger'); + assert.ok(!session.unprocessedNotifications() + .some((n) => n.method === 'Runtime.consoleAPICalled')); + secondSession.disconnect(); + session.disconnect(); + + function main(prefix) { + const inspector = require('inspector'); + inspector.open(0, undefined, false); + process._ws = inspector.url(); + console.log('before wait for debugger'); + inspector.waitForDebugger(); + console.log('after wait for debugger'); + console.log('before second wait for debugger'); + inspector.waitForDebugger(); + console.log('after second wait for debugger'); + } + + // Check that inspector.waitForDebugger throws if there is no active + // inspector + const re = /^Error \[ERR_INSPECTOR_NOT_ACTIVE\]: Inspector is not active$/; + assert.throws(() => require('inspector').waitForDebugger(), re); +} + +runTests(); -- cgit v1.2.3