Skip to content

Commit 61c95f0

Browse files
legendecasdanielleadams
authored andcommitted
src: write named pipe info in diagnostic report
Writes pipe handles with `uv_pipe_getsockname()` and `uv_pipe_getpeername()`. PR-URL: #38637 Fixes: #38625 Reviewed-By: Richard Lau <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
1 parent ba96f14 commit 61c95f0

4 files changed

+177
-45
lines changed

src/node_report_utils.cc

+39
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,42 @@ static void ReportEndpoints(uv_handle_t* h, JSONWriter* writer) {
8282
ReportEndpoint(h, rc == 0 ? addr : nullptr, "remoteEndpoint", writer);
8383
}
8484

85+
// Utility function to format libuv pipe information.
86+
static void ReportPipeEndpoints(uv_handle_t* h, JSONWriter* writer) {
87+
uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(h);
88+
MallocedBuffer<char> buffer(0);
89+
size_t buffer_size = 0;
90+
int rc = -1;
91+
92+
// First call to get required buffer size.
93+
rc = uv_pipe_getsockname(&handle->pipe, buffer.data, &buffer_size);
94+
if (rc == UV_ENOBUFS) {
95+
buffer = MallocedBuffer<char>(buffer_size);
96+
if (buffer.data != nullptr) {
97+
rc = uv_pipe_getsockname(&handle->pipe, buffer.data, &buffer_size);
98+
}
99+
}
100+
if (rc == 0 && buffer_size != 0 && buffer.data != nullptr) {
101+
writer->json_keyvalue("localEndpoint", buffer.data);
102+
} else {
103+
writer->json_keyvalue("localEndpoint", null);
104+
}
105+
106+
// First call to get required buffer size.
107+
rc = uv_pipe_getpeername(&handle->pipe, buffer.data, &buffer_size);
108+
if (rc == UV_ENOBUFS) {
109+
buffer = MallocedBuffer<char>(buffer_size);
110+
if (buffer.data != nullptr) {
111+
rc = uv_pipe_getpeername(&handle->pipe, buffer.data, &buffer_size);
112+
}
113+
}
114+
if (rc == 0 && buffer_size != 0 && buffer.data != nullptr) {
115+
writer->json_keyvalue("remoteEndpoint", buffer.data);
116+
} else {
117+
writer->json_keyvalue("remoteEndpoint", null);
118+
}
119+
}
120+
85121
// Utility function to format libuv path information.
86122
static void ReportPath(uv_handle_t* h, JSONWriter* writer) {
87123
MallocedBuffer<char> buffer(0);
@@ -147,6 +183,9 @@ void WalkHandle(uv_handle_t* h, void* arg) {
147183
case UV_UDP:
148184
ReportEndpoints(h, writer);
149185
break;
186+
case UV_NAMED_PIPE:
187+
ReportPipeEndpoints(h, writer);
188+
break;
150189
case UV_TIMER: {
151190
uint64_t due = handle->timer.timeout;
152191
uint64_t now = uv_now(handle->timer.loop);

test/report/test-report-uncaught-exception-primitives.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ process.on('uncaughtException', common.mustCall((err) => {
1515
assert.strictEqual(err, exception);
1616
const reports = helper.findReports(process.pid, tmpdir.path);
1717
assert.strictEqual(reports.length, 1);
18-
console.log(reports[0]);
18+
1919
helper.validate(reports[0], [
2020
['header.event', 'Exception'],
2121
['javascriptStack.message', `${exception}`],

test/report/test-report-uncaught-exception-symbols.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ process.on('uncaughtException', common.mustCall((err) => {
1515
assert.strictEqual(err, exception);
1616
const reports = helper.findReports(process.pid, tmpdir.path);
1717
assert.strictEqual(reports.length, 1);
18-
console.log(reports[0]);
18+
1919
helper.validate(reports[0], [
2020
['header.event', 'Exception'],
2121
['javascriptStack.message', 'Symbol(foobar)'],

test/report/test-report-uv-handles.js

+136-43
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22

33
// Testcase to check reporting of uv handles.
44
const common = require('../common');
5+
const tmpdir = require('../common/tmpdir');
6+
const path = require('path');
57
if (common.isIBMi)
68
common.skip('IBMi does not support fs.watch()');
79

8-
if (process.argv[2] === 'child') {
9-
// Exit on loss of parent process
10-
const exit = () => process.exit(2);
11-
process.on('disconnect', exit);
10+
// This is quite similar to common.PIPE except that it uses an extended prefix
11+
// of "\\?\pipe" on windows.
12+
const PIPE = (() => {
13+
const localRelative = path.relative(process.cwd(), `${tmpdir.path}/`);
14+
const pipePrefix = common.isWindows ? '\\\\?\\pipe\\' : localRelative;
15+
const pipeName = `node-test.${process.pid}.sock`;
16+
return path.join(pipePrefix, pipeName);
17+
})();
1218

19+
function createFsHandle(childData) {
1320
const fs = require('fs');
14-
const http = require('http');
15-
const spawn = require('child_process').spawn;
16-
1721
// Watching files should result in fs_event/fs_poll uv handles.
1822
let watcher;
1923
try {
@@ -22,59 +26,129 @@ if (process.argv[2] === 'child') {
2226
// fs.watch() unavailable
2327
}
2428
fs.watchFile(__filename, () => {});
29+
childData.skip_fs_watch = watcher === undefined;
30+
31+
return () => {
32+
if (watcher) watcher.close();
33+
fs.unwatchFile(__filename);
34+
};
35+
}
2536

37+
function createChildProcessHandle(childData) {
38+
const spawn = require('child_process').spawn;
2639
// Child should exist when this returns as child_process.pid must be set.
27-
const child_process = spawn(process.execPath,
28-
['-e', "process.stdin.on('data', (x) => " +
29-
'console.log(x.toString()));']);
40+
const cp = spawn(process.execPath,
41+
['-e', "process.stdin.on('data', (x) => " +
42+
'console.log(x.toString()));']);
43+
childData.pid = cp.pid;
44+
45+
return () => {
46+
cp.kill();
47+
};
48+
}
3049

50+
function createTimerHandle() {
3151
const timeout = setInterval(() => {}, 1000);
3252
// Make sure the timer doesn't keep the test alive and let
3353
// us check we detect unref'd handles correctly.
3454
timeout.unref();
55+
return () => {
56+
clearInterval(timeout);
57+
};
58+
}
59+
60+
function createTcpHandle(childData) {
61+
const http = require('http');
3562

63+
return new Promise((resolve) => {
64+
// Simple server/connection to create tcp uv handles.
65+
const server = http.createServer((req, res) => {
66+
req.on('end', () => {
67+
resolve(() => {
68+
res.writeHead(200, { 'Content-Type': 'text/plain' });
69+
res.end();
70+
server.close();
71+
});
72+
});
73+
req.resume();
74+
});
75+
server.listen(() => {
76+
childData.tcp_address = server.address();
77+
http.get({ port: server.address().port });
78+
});
79+
});
80+
}
81+
82+
function createUdpHandle(childData) {
3683
// Datagram socket for udp uv handles.
3784
const dgram = require('dgram');
38-
const udp_socket = dgram.createSocket('udp4');
39-
const connected_udp_socket = dgram.createSocket('udp4');
40-
udp_socket.bind({}, common.mustCall(() => {
41-
connected_udp_socket.connect(udp_socket.address().port);
42-
}));
85+
const udpSocket = dgram.createSocket('udp4');
86+
const connectedUdpSocket = dgram.createSocket('udp4');
87+
88+
return new Promise((resolve) => {
89+
udpSocket.bind({}, common.mustCall(() => {
90+
connectedUdpSocket.connect(udpSocket.address().port);
4391

44-
// Simple server/connection to create tcp uv handles.
45-
const server = http.createServer((req, res) => {
46-
req.on('end', () => {
47-
// Generate the report while the connection is active.
48-
console.log(JSON.stringify(process.report.getReport(), null, 2));
49-
child_process.kill();
50-
51-
res.writeHead(200, { 'Content-Type': 'text/plain' });
52-
res.end();
53-
54-
// Tidy up to allow process to exit cleanly.
55-
server.close(() => {
56-
if (watcher) watcher.close();
57-
fs.unwatchFile(__filename);
58-
connected_udp_socket.close();
59-
udp_socket.close();
60-
process.removeListener('disconnect', exit);
92+
childData.udp_address = udpSocket.address();
93+
resolve(() => {
94+
connectedUdpSocket.close();
95+
udpSocket.close();
96+
});
97+
}));
98+
});
99+
}
100+
101+
function createNamedPipeHandle(childData) {
102+
const net = require('net');
103+
const sockPath = PIPE;
104+
return new Promise((resolve) => {
105+
const server = net.createServer((socket) => {
106+
childData.pipe_sock_path = server.address();
107+
resolve(() => {
108+
socket.end();
109+
server.close();
61110
});
62111
});
63-
req.resume();
112+
server.listen(
113+
sockPath,
114+
() => {
115+
net.connect(sockPath, (socket) => {});
116+
});
64117
});
65-
server.listen(() => {
66-
const data = { pid: child_process.pid,
67-
tcp_address: server.address(),
68-
udp_address: udp_socket.address(),
69-
skip_fs_watch: (watcher === undefined) };
70-
process.send(data);
71-
http.get({ port: server.address().port });
118+
}
119+
120+
async function child() {
121+
// Exit on loss of parent process
122+
const exit = () => process.exit(2);
123+
process.on('disconnect', exit);
124+
125+
const childData = {};
126+
const disposes = await Promise.all([
127+
createFsHandle(childData),
128+
createChildProcessHandle(childData),
129+
createTimerHandle(childData),
130+
createTcpHandle(childData),
131+
createUdpHandle(childData),
132+
createNamedPipeHandle(childData),
133+
]);
134+
process.send(childData);
135+
136+
// Generate the report while the connection is active.
137+
console.log(JSON.stringify(process.report.getReport(), null, 2));
138+
139+
// Tidy up to allow process to exit cleanly.
140+
disposes.forEach((it) => {
141+
it();
72142
});
143+
process.removeListener('disconnect', exit);
144+
}
145+
146+
if (process.argv[2] === 'child') {
147+
child();
73148
} else {
74149
const helper = require('../common/report.js');
75150
const fork = require('child_process').fork;
76151
const assert = require('assert');
77-
const tmpdir = require('../common/tmpdir');
78152
tmpdir.refresh();
79153
const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
80154
const child = fork(__filename, ['child'], options);
@@ -86,11 +160,11 @@ if (process.argv[2] === 'child') {
86160
const report_msg = 'Report files were written: unexpectedly';
87161
child.stdout.on('data', (chunk) => { stdout += chunk; });
88162
child.on('exit', common.mustCall((code, signal) => {
163+
assert.strictEqual(stderr.trim(), '');
89164
assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code: ' +
90165
`${code}`);
91166
assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
92167
` but did not: ${signal}`);
93-
assert.strictEqual(stderr.trim(), '');
94168

95169
const reports = helper.findReports(child.pid, tmpdir.path);
96170
assert.deepStrictEqual(reports, [], report_msg, reports);
@@ -116,6 +190,7 @@ if (process.argv[2] === 'child') {
116190
const expected_filename = `${prefix}${__filename}`;
117191
const found_tcp = [];
118192
const found_udp = [];
193+
const found_named_pipe = [];
119194
// Functions are named to aid debugging when they are not called.
120195
const validators = {
121196
fs_event: common.mustCall(function fs_event_validator(handle) {
@@ -133,6 +208,21 @@ if (process.argv[2] === 'child') {
133208
}),
134209
pipe: common.mustCallAtLeast(function pipe_validator(handle) {
135210
assert(handle.is_referenced);
211+
// Pipe handles. The report should contain three pipes:
212+
// 1. The server's listening pipe.
213+
// 2. The inbound pipe making the request.
214+
// 3. The outbound pipe sending the response.
215+
//
216+
// There is no way to distinguish inbound and outbound in a cross
217+
// platform manner, so we just check inbound here.
218+
const sockPath = child_data.pipe_sock_path;
219+
if (handle.localEndpoint === sockPath) {
220+
if (handle.writable === false) {
221+
found_named_pipe.push('listening');
222+
}
223+
} else if (handle.remoteEndpoint === sockPath) {
224+
found_named_pipe.push('inbound');
225+
}
136226
}),
137227
process: common.mustCall(function process_validator(handle) {
138228
assert.strictEqual(handle.pid, child_data.pid);
@@ -172,7 +262,7 @@ if (process.argv[2] === 'child') {
172262
assert(handle.is_referenced);
173263
}, 2),
174264
};
175-
console.log(report.libuv);
265+
176266
for (const entry of report.libuv) {
177267
if (validators[entry.type]) validators[entry.type](entry);
178268
}
@@ -182,6 +272,9 @@ if (process.argv[2] === 'child') {
182272
for (const socket of ['connected', 'unconnected']) {
183273
assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
184274
}
275+
for (const socket of ['listening', 'inbound']) {
276+
assert(found_named_pipe.includes(socket), `${socket} named pipe socket was not found`);
277+
}
185278

186279
// Common report tests.
187280
helper.validateContent(stdout);

0 commit comments

Comments
 (0)