Skip to content

Commit

Permalink
inspector: added --inspect-publish-uid-ipc-path argument
Browse files Browse the repository at this point in the history
The argument takes the IPC path as an argument when it is passed,
the inspector web socket server will send its full web socket url
to IPC server with the given path at the start.

This flag is required to solve inspector web socket url discovery
the problem for child processes.

Only one discovery option is available right now; it is searching
for ws:// URLs in stderr of node process. This approach does not
work when the parent process ignores the stderr of the child process,
e.g. update-notifier uses this technique.

Discussion about using files instead can be found here:
nodejs/diagnostics#298
  • Loading branch information
alexkozy authored and alexeykozy committed Jun 14, 2019
1 parent 282e2f6 commit a1c4fe7
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 0 deletions.
6 changes: 6 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,11 @@ 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-publish-uid-ipc-path`

Inspector will send a space-separated list of inspector web socket URLs to IPC
`net.Socket` with given path at the start.

### `--loader=file`
<!-- YAML
added: v9.0.0
Expand Down Expand Up @@ -989,6 +994,7 @@ Node.js options that are allowed are:
- `--inspect-brk`
- `--inspect-port`
- `--inspect-publish-uid`
- `--inspect-publish-uid-ipc-path`
- `--loader`
- `--max-http-header-size`
- `--napi-modules`
Expand Down
74 changes: 74 additions & 0 deletions src/inspector_socket_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,75 @@ void PrintDebuggerReadyMessage(
fflush(out);
}

void SendSocketToSocket(
uv_loop_t* loop,
const std::string& ipc_path,
const std::string& host,
const std::vector<InspectorSocketServer::ServerSocketPtr>& server_sockets,
const std::vector<std::string>& ids) {
if (ipc_path.empty())
return;

uv_pipe_t* pipe = new uv_pipe_t();
int r = uv_pipe_init(loop, pipe, true);
if (r < 0) {
delete pipe;
return;
}

pipe->data = new std::string();
std::string& data = *static_cast<std::string*>(pipe->data);
for (const auto& server_socket : server_sockets) {
for (const std::string& id : ids) {
data += (data.length() ? " " : "") +
FormatWsAddress(host, server_socket->port(), id, true);
}
}

uv_connect_t* connect_req = new uv_connect_t();
uv_pipe_connect(connect_req,
pipe,
ipc_path.c_str(),
[](uv_connect_t* req, int status) {
uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(req->handle);
delete req;

std::string* data = reinterpret_cast<std::string*>(stream->data);
uv_buf_t buf = uv_buf_init(&(*data)[0], data->length());
uv_write_t* write_req = new uv_write_t();
int err = uv_write(write_req,
stream,
&buf,
1,
[](uv_write_t* req, int status) {
uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(req->handle);
delete req;
delete reinterpret_cast<std::string*>(stream->data);
stream->data = nullptr;
});
if (err < 0) {
delete write_req;
delete reinterpret_cast<std::string*>(stream->data);
stream->data = nullptr;
}

uv_shutdown_t* shutdown_req = new uv_shutdown_t();
err = uv_shutdown(shutdown_req, stream, [](uv_shutdown_t* req, int status) {
uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(req->handle);
delete req;
uv_close(reinterpret_cast<uv_handle_t*>(stream), [](uv_handle_t* handle){
delete reinterpret_cast<uv_pipe_t*>(handle);
});
});
if (err < 0) {
delete shutdown_req;
uv_close(reinterpret_cast<uv_handle_t*>(stream), [](uv_handle_t* handle){
delete reinterpret_cast<uv_pipe_t*>(handle);
});
}
});
}

InspectorSocketServer::InspectorSocketServer(
std::unique_ptr<SocketServerDelegate> delegate, uv_loop_t* loop,
const std::string& host, int port,
Expand Down Expand Up @@ -426,6 +495,11 @@ bool InspectorSocketServer::Start() {
delegate_->GetTargetIds(),
inspect_publish_uid_.console,
out_);
SendSocketToSocket(loop_,
inspect_publish_uid_.ipc_path,
host_,
server_sockets_,
delegate_->GetTargetIds());
return true;
}

Expand Down
6 changes: 6 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void DebugOptions::CheckOptions(std::vector<std::string>* errors) {
SplitString(inspect_publish_uid_string, ',');
inspect_publish_uid.console = false;
inspect_publish_uid.http = false;
inspect_publish_uid.ipc_path = inspect_publish_uid_ipc_path;
for (const std::string& destination : destinations) {
if (destination == "stderr") {
inspect_publish_uid.console = true;
Expand Down Expand Up @@ -297,6 +298,11 @@ DebugOptionsParser::DebugOptionsParser() {
"(default: stderr,http)",
&DebugOptions::inspect_publish_uid_string,
kAllowedInEnvironment);

AddOption("--inspect-publish-uid-ipc-path",
"path for IPC connection, inspector will send websocket URL to it",
&DebugOptions::inspect_publish_uid_ipc_path,
kAllowedInEnvironment);
}

EnvironmentOptionsParser::EnvironmentOptionsParser() {
Expand Down
2 changes: 2 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Options {
struct InspectPublishUid {
bool console;
bool http;
std::string ipc_path;
};

// These options are currently essentially per-Environment, but it can be nice
Expand All @@ -77,6 +78,7 @@ class DebugOptions : public Options {
bool break_node_first_line = false;
// --inspect-publish-uid
std::string inspect_publish_uid_string = "stderr,http";
std::string inspect_publish_uid_ipc_path;

InspectPublishUid inspect_publish_uid;

Expand Down
60 changes: 60 additions & 0 deletions test/parallel/test-inspect-publish-ipc-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';
const common = require('../common');

common.skipIfInspectorDisabled();

const assert = require('assert');
const { spawn } = require('child_process');
const net = require('net');

(async function test() {
await testWithServer();
await testWithoutServer();
})();


async function testWithServer() {
const pipe = common.PIPE;
const CHILD_COUNT = 5;
const sockets = [];
const server = net.createServer((socket) => {
let buf = '';
socket.on('data', (d) => buf += d.toString());
socket.on('end', () => sockets.push(...buf.split(' ')));
}).listen(pipe);
await runNodeWithChildren(CHILD_COUNT, pipe);
server.close();
assert.strictEqual(CHILD_COUNT + 1, sockets.length);
sockets.forEach((socket) => {
const m = socket.match(/ws:\/\/[^/]+\/[-a-z0-9]+/);
assert.strictEqual(m[0], socket);
});
}

async function testWithoutServer() {
await runNodeWithChildren(5, common.PIPE);
}

function runNodeWithChildren(childCount, pipe) {
const nodeProcess = spawn(process.execPath, [
'--inspect=0',
`--inspect-publish-uid-ipc-path=${pipe}`,
'-e', `(${main.toString()})()`
], {
env: {
COUNT: childCount
}
});
return new Promise((resolve) => nodeProcess.on('close', resolve));

function main() {
const { spawn } = require('child_process');
if (process.env.COUNT * 1 === 0)
return;
spawn(process.execPath, process.execArgv, {
env: {
COUNT: process.env.COUNT - 1
}
});
}
}

0 comments on commit a1c4fe7

Please sign in to comment.