Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 534db1a

Browse files
committedMay 11, 2021
src: write named pipe info in diagnostic report
Writes pipe handles with `uv_pipe_getsockname()` and `uv_pipe_getpeername()`.
1 parent 4ebb88f commit 534db1a

4 files changed

+168
-44
lines changed
 

‎src/node_report_utils.cc

+38
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace report {
77

88
using node::JSONWriter;
99
using node::MallocedBuffer;
10+
using node::MaybeStackBuffer;
1011

1112
static constexpr auto null = JSONWriter::Null{};
1213

@@ -82,6 +83,40 @@ static void ReportEndpoints(uv_handle_t* h, JSONWriter* writer) {
8283
ReportEndpoint(h, rc == 0 ? addr : nullptr, "remoteEndpoint", writer);
8384
}
8485

86+
// Utility function to format libuv pipe information.
87+
static void ReportPipeEndpoints(uv_handle_t* h, JSONWriter* writer) {
88+
uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(h);
89+
MaybeStackBuffer<char> buffer;
90+
size_t buffer_size = buffer.capacity();
91+
int rc = -1;
92+
93+
rc = uv_pipe_getsockname(&handle->pipe, buffer.out(), &buffer_size);
94+
if (rc == UV_ENOBUFS) {
95+
// Buffer is not large enough, reallocate to the updated buffer_size
96+
// and fetch the value again.
97+
buffer.AllocateSufficientStorage(buffer_size);
98+
rc = uv_pipe_getsockname(&handle->pipe, buffer.out(), &buffer_size);
99+
}
100+
if (rc == 0 && buffer_size != 0) {
101+
writer->json_keyvalue("localEndpointName", buffer.out());
102+
} else {
103+
writer->json_keyvalue("localEndpointName", null);
104+
}
105+
106+
rc = uv_pipe_getpeername(&handle->pipe, buffer.out(), &buffer_size);
107+
if (rc == UV_ENOBUFS) {
108+
// Buffer is not large enough, reallocate to the updated buffer_size
109+
// and fetch the value again.
110+
buffer.AllocateSufficientStorage(buffer_size);
111+
rc = uv_pipe_getpeername(&handle->pipe, buffer.out(), &buffer_size);
112+
}
113+
if (rc == 0 && buffer_size != 0) {
114+
writer->json_keyvalue("remoteEndpointName", buffer.out());
115+
} else {
116+
writer->json_keyvalue("remoteEndpointName", null);
117+
}
118+
}
119+
85120
// Utility function to format libuv path information.
86121
static void ReportPath(uv_handle_t* h, JSONWriter* writer) {
87122
MallocedBuffer<char> buffer(0);
@@ -147,6 +182,9 @@ void WalkHandle(uv_handle_t* h, void* arg) {
147182
case UV_UDP:
148183
ReportEndpoints(h, writer);
149184
break;
185+
case UV_NAMED_PIPE:
186+
ReportPipeEndpoints(h, writer);
187+
break;
150188
case UV_TIMER: {
151189
uint64_t due = handle->timer.timeout;
152190
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

+128-42
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
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);
12-
10+
function createFsHandle(childData) {
1311
const fs = require('fs');
14-
const http = require('http');
15-
const spawn = require('child_process').spawn;
16-
1712
// Watching files should result in fs_event/fs_poll uv handles.
1813
let watcher;
1914
try {
@@ -22,54 +17,127 @@ if (process.argv[2] === 'child') {
2217
// fs.watch() unavailable
2318
}
2419
fs.watchFile(__filename, () => {});
20+
childData.skip_fs_watch = watcher === undefined;
21+
22+
return () => {
23+
if (watcher) watcher.close();
24+
fs.unwatchFile(__filename);
25+
};
26+
}
2527

28+
function createChildProcessHandle(childData) {
29+
const spawn = require('child_process').spawn;
2630
// 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()));']);
31+
const cp = spawn(process.execPath,
32+
['-e', "process.stdin.on('data', (x) => " +
33+
'console.log(x.toString()));']);
34+
childData.pid = cp.pid;
35+
36+
return () => {
37+
cp.kill();
38+
};
39+
}
3040

41+
function createTimerHandle() {
3142
const timeout = setInterval(() => {}, 1000);
3243
// Make sure the timer doesn't keep the test alive and let
3344
// us check we detect unref'd handles correctly.
3445
timeout.unref();
46+
return () => {
47+
clearInterval(timeout);
48+
};
49+
}
50+
51+
function createTcpHandle(childData) {
52+
const http = require('http');
53+
54+
return new Promise((resolve) => {
55+
// Simple server/connection to create tcp uv handles.
56+
const server = http.createServer((req, res) => {
57+
req.on('end', () => {
58+
resolve(() => {
59+
res.writeHead(200, { 'Content-Type': 'text/plain' });
60+
res.end();
61+
server.close();
62+
});
63+
});
64+
req.resume();
65+
});
66+
server.listen(() => {
67+
childData.tcp_address = server.address();
68+
http.get({ port: server.address().port });
69+
});
70+
});
71+
}
3572

73+
function createUdpHandle(childData) {
3674
// Datagram socket for udp uv handles.
3775
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-
}));
76+
const udpSocket = dgram.createSocket('udp4');
77+
const connectedUdpSocket = dgram.createSocket('udp4');
78+
79+
return new Promise((resolve) => {
80+
udpSocket.bind({}, common.mustCall(() => {
81+
connectedUdpSocket.connect(udpSocket.address().port);
4382

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);
83+
childData.udp_address = udpSocket.address();
84+
resolve(() => {
85+
connectedUdpSocket.close();
86+
udpSocket.close();
87+
});
88+
}));
89+
});
90+
}
91+
92+
function createNamedPipeHandle(childData) {
93+
const net = require('net');
94+
const fs = require('fs');
95+
fs.mkdirSync(tmpdir.path, { recursive: true });
96+
const sockPath = path.join(tmpdir.path, 'test-report-uv-handles.sock');
97+
return new Promise((resolve) => {
98+
const server = net.createServer((socket) => {
99+
childData.pipe_sock_path = server.address();
100+
resolve(() => {
101+
socket.end();
102+
server.close();
61103
});
62104
});
63-
req.resume();
105+
server.listen(
106+
sockPath,
107+
() => {
108+
net.connect(sockPath, (socket) => {});
109+
});
64110
});
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 });
111+
}
112+
113+
async function child() {
114+
// Exit on loss of parent process
115+
const exit = () => process.exit(2);
116+
process.on('disconnect', exit);
117+
118+
const childData = {};
119+
const disposes = await Promise.all([
120+
createFsHandle(childData),
121+
createChildProcessHandle(childData),
122+
createTimerHandle(childData),
123+
createTcpHandle(childData),
124+
createUdpHandle(childData),
125+
createNamedPipeHandle(childData),
126+
]);
127+
process.send(childData);
128+
129+
// Generate the report while the connection is active.
130+
console.log(JSON.stringify(process.report.getReport(), null, 2));
131+
132+
// Tidy up to allow process to exit cleanly.
133+
disposes.forEach((it) => {
134+
it();
72135
});
136+
process.removeListener('disconnect', exit);
137+
}
138+
139+
if (process.argv[2] === 'child') {
140+
child();
73141
} else {
74142
const helper = require('../common/report.js');
75143
const fork = require('child_process').fork;
@@ -116,6 +184,7 @@ if (process.argv[2] === 'child') {
116184
const expected_filename = `${prefix}${__filename}`;
117185
const found_tcp = [];
118186
const found_udp = [];
187+
const found_named_pipe = [];
119188
// Functions are named to aid debugging when they are not called.
120189
const validators = {
121190
fs_event: common.mustCall(function fs_event_validator(handle) {
@@ -133,6 +202,20 @@ if (process.argv[2] === 'child') {
133202
}),
134203
pipe: common.mustCallAtLeast(function pipe_validator(handle) {
135204
assert(handle.is_referenced);
205+
// Pipe handles. The report should contain three pipes:
206+
// 1. The server's listening pipe.
207+
// 2. The inbound pipe making the request.
208+
// 3. The outbound pipe sending the response.
209+
const sockPath = child_data.pipe_sock_path;
210+
if (handle.localEndpointName === sockPath) {
211+
if (handle.writable === false) {
212+
found_named_pipe.push('listening');
213+
} else {
214+
found_named_pipe.push('inbound');
215+
}
216+
} else if (handle.remoteEndpointName === sockPath) {
217+
found_named_pipe.push('outbound');
218+
}
136219
}),
137220
process: common.mustCall(function process_validator(handle) {
138221
assert.strictEqual(handle.pid, child_data.pid);
@@ -172,7 +255,7 @@ if (process.argv[2] === 'child') {
172255
assert(handle.is_referenced);
173256
}, 2),
174257
};
175-
console.log(report.libuv);
258+
176259
for (const entry of report.libuv) {
177260
if (validators[entry.type]) validators[entry.type](entry);
178261
}
@@ -182,6 +265,9 @@ if (process.argv[2] === 'child') {
182265
for (const socket of ['connected', 'unconnected']) {
183266
assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
184267
}
268+
for (const socket of ['listening', 'inbound', 'outbound']) {
269+
assert(found_named_pipe.includes(socket), `${socket} Named pipe socket was not found`);
270+
}
185271

186272
// Common report tests.
187273
helper.validateContent(stdout);

0 commit comments

Comments
 (0)
Please sign in to comment.