mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 21:58:48 +02:00
src: write named pipe info in diagnostic report
Writes pipe handles with `uv_pipe_getsockname()` and `uv_pipe_getpeername()`. PR-URL: https://github.com/nodejs/node/pull/38637 Fixes: https://github.com/nodejs/node/issues/38625 Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
This commit is contained in:
parent
d7611ec6e6
commit
50f076c4f5
4 changed files with 175 additions and 43 deletions
|
@ -82,6 +82,42 @@ static void ReportEndpoints(uv_handle_t* h, JSONWriter* writer) {
|
||||||
ReportEndpoint(h, rc == 0 ? addr : nullptr, "remoteEndpoint", writer);
|
ReportEndpoint(h, rc == 0 ? addr : nullptr, "remoteEndpoint", writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility function to format libuv pipe information.
|
||||||
|
static void ReportPipeEndpoints(uv_handle_t* h, JSONWriter* writer) {
|
||||||
|
uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(h);
|
||||||
|
MallocedBuffer<char> buffer(0);
|
||||||
|
size_t buffer_size = 0;
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
// First call to get required buffer size.
|
||||||
|
rc = uv_pipe_getsockname(&handle->pipe, buffer.data, &buffer_size);
|
||||||
|
if (rc == UV_ENOBUFS) {
|
||||||
|
buffer = MallocedBuffer<char>(buffer_size);
|
||||||
|
if (buffer.data != nullptr) {
|
||||||
|
rc = uv_pipe_getsockname(&handle->pipe, buffer.data, &buffer_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc == 0 && buffer_size != 0 && buffer.data != nullptr) {
|
||||||
|
writer->json_keyvalue("localEndpoint", buffer.data);
|
||||||
|
} else {
|
||||||
|
writer->json_keyvalue("localEndpoint", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First call to get required buffer size.
|
||||||
|
rc = uv_pipe_getpeername(&handle->pipe, buffer.data, &buffer_size);
|
||||||
|
if (rc == UV_ENOBUFS) {
|
||||||
|
buffer = MallocedBuffer<char>(buffer_size);
|
||||||
|
if (buffer.data != nullptr) {
|
||||||
|
rc = uv_pipe_getpeername(&handle->pipe, buffer.data, &buffer_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc == 0 && buffer_size != 0 && buffer.data != nullptr) {
|
||||||
|
writer->json_keyvalue("remoteEndpoint", buffer.data);
|
||||||
|
} else {
|
||||||
|
writer->json_keyvalue("remoteEndpoint", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utility function to format libuv path information.
|
// Utility function to format libuv path information.
|
||||||
static void ReportPath(uv_handle_t* h, JSONWriter* writer) {
|
static void ReportPath(uv_handle_t* h, JSONWriter* writer) {
|
||||||
MallocedBuffer<char> buffer(0);
|
MallocedBuffer<char> buffer(0);
|
||||||
|
@ -147,6 +183,9 @@ void WalkHandle(uv_handle_t* h, void* arg) {
|
||||||
case UV_UDP:
|
case UV_UDP:
|
||||||
ReportEndpoints(h, writer);
|
ReportEndpoints(h, writer);
|
||||||
break;
|
break;
|
||||||
|
case UV_NAMED_PIPE:
|
||||||
|
ReportPipeEndpoints(h, writer);
|
||||||
|
break;
|
||||||
case UV_TIMER: {
|
case UV_TIMER: {
|
||||||
uint64_t due = handle->timer.timeout;
|
uint64_t due = handle->timer.timeout;
|
||||||
uint64_t now = uv_now(handle->timer.loop);
|
uint64_t now = uv_now(handle->timer.loop);
|
||||||
|
|
|
@ -15,7 +15,7 @@ process.on('uncaughtException', common.mustCall((err) => {
|
||||||
assert.strictEqual(err, exception);
|
assert.strictEqual(err, exception);
|
||||||
const reports = helper.findReports(process.pid, tmpdir.path);
|
const reports = helper.findReports(process.pid, tmpdir.path);
|
||||||
assert.strictEqual(reports.length, 1);
|
assert.strictEqual(reports.length, 1);
|
||||||
console.log(reports[0]);
|
|
||||||
helper.validate(reports[0], [
|
helper.validate(reports[0], [
|
||||||
['header.event', 'Exception'],
|
['header.event', 'Exception'],
|
||||||
['javascriptStack.message', `${exception}`],
|
['javascriptStack.message', `${exception}`],
|
||||||
|
|
|
@ -15,7 +15,7 @@ process.on('uncaughtException', common.mustCall((err) => {
|
||||||
assert.strictEqual(err, exception);
|
assert.strictEqual(err, exception);
|
||||||
const reports = helper.findReports(process.pid, tmpdir.path);
|
const reports = helper.findReports(process.pid, tmpdir.path);
|
||||||
assert.strictEqual(reports.length, 1);
|
assert.strictEqual(reports.length, 1);
|
||||||
console.log(reports[0]);
|
|
||||||
helper.validate(reports[0], [
|
helper.validate(reports[0], [
|
||||||
['header.event', 'Exception'],
|
['header.event', 'Exception'],
|
||||||
['javascriptStack.message', 'Symbol(foobar)'],
|
['javascriptStack.message', 'Symbol(foobar)'],
|
||||||
|
|
|
@ -2,18 +2,22 @@
|
||||||
|
|
||||||
// Testcase to check reporting of uv handles.
|
// Testcase to check reporting of uv handles.
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
|
const tmpdir = require('../common/tmpdir');
|
||||||
|
const path = require('path');
|
||||||
if (common.isIBMi)
|
if (common.isIBMi)
|
||||||
common.skip('IBMi does not support fs.watch()');
|
common.skip('IBMi does not support fs.watch()');
|
||||||
|
|
||||||
if (process.argv[2] === 'child') {
|
// This is quite similar to common.PIPE except that it uses an extended prefix
|
||||||
// Exit on loss of parent process
|
// of "\\?\pipe" on windows.
|
||||||
const exit = () => process.exit(2);
|
const PIPE = (() => {
|
||||||
process.on('disconnect', exit);
|
const localRelative = path.relative(process.cwd(), `${tmpdir.path}/`);
|
||||||
|
const pipePrefix = common.isWindows ? '\\\\?\\pipe\\' : localRelative;
|
||||||
|
const pipeName = `node-test.${process.pid}.sock`;
|
||||||
|
return path.join(pipePrefix, pipeName);
|
||||||
|
})();
|
||||||
|
|
||||||
|
function createFsHandle(childData) {
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const http = require('http');
|
|
||||||
const spawn = require('child_process').spawn;
|
|
||||||
|
|
||||||
// Watching files should result in fs_event/fs_poll uv handles.
|
// Watching files should result in fs_event/fs_poll uv handles.
|
||||||
let watcher;
|
let watcher;
|
||||||
try {
|
try {
|
||||||
|
@ -22,59 +26,129 @@ if (process.argv[2] === 'child') {
|
||||||
// fs.watch() unavailable
|
// fs.watch() unavailable
|
||||||
}
|
}
|
||||||
fs.watchFile(__filename, () => {});
|
fs.watchFile(__filename, () => {});
|
||||||
|
childData.skip_fs_watch = watcher === undefined;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (watcher) watcher.close();
|
||||||
|
fs.unwatchFile(__filename);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChildProcessHandle(childData) {
|
||||||
|
const spawn = require('child_process').spawn;
|
||||||
// Child should exist when this returns as child_process.pid must be set.
|
// Child should exist when this returns as child_process.pid must be set.
|
||||||
const child_process = spawn(process.execPath,
|
const cp = spawn(process.execPath,
|
||||||
['-e', "process.stdin.on('data', (x) => " +
|
['-e', "process.stdin.on('data', (x) => " +
|
||||||
'console.log(x.toString()));']);
|
'console.log(x.toString()));']);
|
||||||
|
childData.pid = cp.pid;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cp.kill();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTimerHandle() {
|
||||||
const timeout = setInterval(() => {}, 1000);
|
const timeout = setInterval(() => {}, 1000);
|
||||||
// Make sure the timer doesn't keep the test alive and let
|
// Make sure the timer doesn't keep the test alive and let
|
||||||
// us check we detect unref'd handles correctly.
|
// us check we detect unref'd handles correctly.
|
||||||
timeout.unref();
|
timeout.unref();
|
||||||
|
return () => {
|
||||||
|
clearInterval(timeout);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Datagram socket for udp uv handles.
|
function createTcpHandle(childData) {
|
||||||
const dgram = require('dgram');
|
const http = require('http');
|
||||||
const udp_socket = dgram.createSocket('udp4');
|
|
||||||
const connected_udp_socket = dgram.createSocket('udp4');
|
|
||||||
udp_socket.bind({}, common.mustCall(() => {
|
|
||||||
connected_udp_socket.connect(udp_socket.address().port);
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
// Simple server/connection to create tcp uv handles.
|
// Simple server/connection to create tcp uv handles.
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
req.on('end', () => {
|
req.on('end', () => {
|
||||||
// Generate the report while the connection is active.
|
resolve(() => {
|
||||||
console.log(JSON.stringify(process.report.getReport(), null, 2));
|
|
||||||
child_process.kill();
|
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
res.end();
|
res.end();
|
||||||
|
server.close();
|
||||||
// Tidy up to allow process to exit cleanly.
|
|
||||||
server.close(() => {
|
|
||||||
if (watcher) watcher.close();
|
|
||||||
fs.unwatchFile(__filename);
|
|
||||||
connected_udp_socket.close();
|
|
||||||
udp_socket.close();
|
|
||||||
process.removeListener('disconnect', exit);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
req.resume();
|
req.resume();
|
||||||
});
|
});
|
||||||
server.listen(() => {
|
server.listen(() => {
|
||||||
const data = { pid: child_process.pid,
|
childData.tcp_address = server.address();
|
||||||
tcp_address: server.address(),
|
|
||||||
udp_address: udp_socket.address(),
|
|
||||||
skip_fs_watch: (watcher === undefined) };
|
|
||||||
process.send(data);
|
|
||||||
http.get({ port: server.address().port });
|
http.get({ port: server.address().port });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUdpHandle(childData) {
|
||||||
|
// Datagram socket for udp uv handles.
|
||||||
|
const dgram = require('dgram');
|
||||||
|
const udpSocket = dgram.createSocket('udp4');
|
||||||
|
const connectedUdpSocket = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
udpSocket.bind({}, common.mustCall(() => {
|
||||||
|
connectedUdpSocket.connect(udpSocket.address().port);
|
||||||
|
|
||||||
|
childData.udp_address = udpSocket.address();
|
||||||
|
resolve(() => {
|
||||||
|
connectedUdpSocket.close();
|
||||||
|
udpSocket.close();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNamedPipeHandle(childData) {
|
||||||
|
const net = require('net');
|
||||||
|
const sockPath = PIPE;
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const server = net.createServer((socket) => {
|
||||||
|
childData.pipe_sock_path = server.address();
|
||||||
|
resolve(() => {
|
||||||
|
socket.end();
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
server.listen(
|
||||||
|
sockPath,
|
||||||
|
() => {
|
||||||
|
net.connect(sockPath, (socket) => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function child() {
|
||||||
|
// Exit on loss of parent process
|
||||||
|
const exit = () => process.exit(2);
|
||||||
|
process.on('disconnect', exit);
|
||||||
|
|
||||||
|
const childData = {};
|
||||||
|
const disposes = await Promise.all([
|
||||||
|
createFsHandle(childData),
|
||||||
|
createChildProcessHandle(childData),
|
||||||
|
createTimerHandle(childData),
|
||||||
|
createTcpHandle(childData),
|
||||||
|
createUdpHandle(childData),
|
||||||
|
createNamedPipeHandle(childData),
|
||||||
|
]);
|
||||||
|
process.send(childData);
|
||||||
|
|
||||||
|
// Generate the report while the connection is active.
|
||||||
|
console.log(JSON.stringify(process.report.getReport(), null, 2));
|
||||||
|
|
||||||
|
// Tidy up to allow process to exit cleanly.
|
||||||
|
disposes.forEach((it) => {
|
||||||
|
it();
|
||||||
|
});
|
||||||
|
process.removeListener('disconnect', exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[2] === 'child') {
|
||||||
|
child();
|
||||||
} else {
|
} else {
|
||||||
const helper = require('../common/report.js');
|
const helper = require('../common/report.js');
|
||||||
const fork = require('child_process').fork;
|
const fork = require('child_process').fork;
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const tmpdir = require('../common/tmpdir');
|
|
||||||
tmpdir.refresh();
|
tmpdir.refresh();
|
||||||
const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
|
const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
|
||||||
const child = fork(__filename, ['child'], options);
|
const child = fork(__filename, ['child'], options);
|
||||||
|
@ -86,11 +160,11 @@ if (process.argv[2] === 'child') {
|
||||||
const report_msg = 'Report files were written: unexpectedly';
|
const report_msg = 'Report files were written: unexpectedly';
|
||||||
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
||||||
child.on('exit', common.mustCall((code, signal) => {
|
child.on('exit', common.mustCall((code, signal) => {
|
||||||
|
assert.strictEqual(stderr.trim(), '');
|
||||||
assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code: ' +
|
assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code: ' +
|
||||||
`${code}`);
|
`${code}`);
|
||||||
assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
|
assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
|
||||||
` but did not: ${signal}`);
|
` but did not: ${signal}`);
|
||||||
assert.strictEqual(stderr.trim(), '');
|
|
||||||
|
|
||||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||||
assert.deepStrictEqual(reports, [], report_msg, reports);
|
assert.deepStrictEqual(reports, [], report_msg, reports);
|
||||||
|
@ -116,6 +190,7 @@ if (process.argv[2] === 'child') {
|
||||||
const expected_filename = `${prefix}${__filename}`;
|
const expected_filename = `${prefix}${__filename}`;
|
||||||
const found_tcp = [];
|
const found_tcp = [];
|
||||||
const found_udp = [];
|
const found_udp = [];
|
||||||
|
const found_named_pipe = [];
|
||||||
// Functions are named to aid debugging when they are not called.
|
// Functions are named to aid debugging when they are not called.
|
||||||
const validators = {
|
const validators = {
|
||||||
fs_event: common.mustCall(function fs_event_validator(handle) {
|
fs_event: common.mustCall(function fs_event_validator(handle) {
|
||||||
|
@ -133,6 +208,21 @@ if (process.argv[2] === 'child') {
|
||||||
}),
|
}),
|
||||||
pipe: common.mustCallAtLeast(function pipe_validator(handle) {
|
pipe: common.mustCallAtLeast(function pipe_validator(handle) {
|
||||||
assert(handle.is_referenced);
|
assert(handle.is_referenced);
|
||||||
|
// Pipe handles. The report should contain three pipes:
|
||||||
|
// 1. The server's listening pipe.
|
||||||
|
// 2. The inbound pipe making the request.
|
||||||
|
// 3. The outbound pipe sending the response.
|
||||||
|
//
|
||||||
|
// There is no way to distinguish inbound and outbound in a cross
|
||||||
|
// platform manner, so we just check inbound here.
|
||||||
|
const sockPath = child_data.pipe_sock_path;
|
||||||
|
if (handle.localEndpoint === sockPath) {
|
||||||
|
if (handle.writable === false) {
|
||||||
|
found_named_pipe.push('listening');
|
||||||
|
}
|
||||||
|
} else if (handle.remoteEndpoint === sockPath) {
|
||||||
|
found_named_pipe.push('inbound');
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
process: common.mustCall(function process_validator(handle) {
|
process: common.mustCall(function process_validator(handle) {
|
||||||
assert.strictEqual(handle.pid, child_data.pid);
|
assert.strictEqual(handle.pid, child_data.pid);
|
||||||
|
@ -172,7 +262,7 @@ if (process.argv[2] === 'child') {
|
||||||
assert(handle.is_referenced);
|
assert(handle.is_referenced);
|
||||||
}, 2),
|
}, 2),
|
||||||
};
|
};
|
||||||
console.log(report.libuv);
|
|
||||||
for (const entry of report.libuv) {
|
for (const entry of report.libuv) {
|
||||||
if (validators[entry.type]) validators[entry.type](entry);
|
if (validators[entry.type]) validators[entry.type](entry);
|
||||||
}
|
}
|
||||||
|
@ -182,6 +272,9 @@ if (process.argv[2] === 'child') {
|
||||||
for (const socket of ['connected', 'unconnected']) {
|
for (const socket of ['connected', 'unconnected']) {
|
||||||
assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
|
assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
|
||||||
}
|
}
|
||||||
|
for (const socket of ['listening', 'inbound']) {
|
||||||
|
assert(found_named_pipe.includes(socket), `${socket} named pipe socket was not found`);
|
||||||
|
}
|
||||||
|
|
||||||
// Common report tests.
|
// Common report tests.
|
||||||
helper.validateContent(stdout);
|
helper.validateContent(stdout);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue