Skip to content

Commit bcc1e4f

Browse files
committed
net,tls: add abort signal support to connect
Add documentation for net.connect AbortSignal, and add the support to tls.connect as well
1 parent bfa6e37 commit bcc1e4f

6 files changed

+384
-0
lines changed

doc/api/net.md

+6
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ it to interact with the client.
496496
### `new net.Socket([options])`
497497
<!-- YAML
498498
added: v0.3.4
499+
changes:
500+
- version: REPLACEME
501+
pr-url: https://github.com/nodejs/node/pull/37735
502+
description: AbortSignal support was added.
499503
-->
500504

501505
* `options` {Object} Available options are:
@@ -508,6 +512,8 @@ added: v0.3.4
508512
otherwise ignored. **Default:** `false`.
509513
* `writable` {boolean} Allow writes on the socket when an `fd` is passed,
510514
otherwise ignored. **Default:** `false`.
515+
* `signal` {AbortSignal} An Abort signal that may be used to destroy the
516+
socket.
511517
* Returns: {net.Socket}
512518

513519
Creates a new socket object.

lib/_tls_wrap.js

+2
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ function TLSSocket(socket, opts) {
515515
manualStart: true,
516516
highWaterMark: tlsOptions.highWaterMark,
517517
onread: !socket ? tlsOptions.onread : null,
518+
signal: tlsOptions.signal,
518519
}]);
519520

520521
// Proxy for API compatibility
@@ -1627,6 +1628,7 @@ exports.connect = function connect(...args) {
16271628
pskCallback: options.pskCallback,
16281629
highWaterMark: options.highWaterMark,
16291630
onread: options.onread,
1631+
signal: options.signal,
16301632
});
16311633

16321634
tlssock[kConnectOptions] = options;

test/parallel/test-http2-client-destroy.js

+46
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const h2 = require('http2');
1010
const { kSocket } = require('internal/http2/util');
1111
const { kEvents } = require('internal/event_target');
1212
const Countdown = require('../common/countdown');
13+
const { getEventListeners } = require('events');
1314

1415
{
1516
const server = h2.createServer();
@@ -241,3 +242,48 @@ const Countdown = require('../common/countdown');
241242
req.on('close', common.mustCall(() => server.close()));
242243
}));
243244
}
245+
246+
247+
// Destroy ClientHttpSession with AbortSignal
248+
{
249+
function testH2ConnectAbort(secure) {
250+
const server = secure ? h2.createSecureServer() : h2.createServer();
251+
const controller = new AbortController();
252+
253+
server.on('stream', common.mustNotCall());
254+
server.listen(0, common.mustCall(() => {
255+
const { signal } = controller;
256+
const protocol = secure ? 'https' : 'http';
257+
const client = h2.connect(`${protocol}://localhost:${server.address().port}`, {
258+
signal,
259+
});
260+
client.on('close', common.mustCall());
261+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
262+
263+
client.on('error', common.mustCall(common.mustCall((err) => {
264+
assert.strictEqual(err.code, 'ABORT_ERR');
265+
assert.strictEqual(err.name, 'AbortError');
266+
})));
267+
268+
const req = client.request({}, {});
269+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
270+
271+
req.on('error', common.mustCall((err) => {
272+
assert.strictEqual(err.code, 'ERR_HTTP2_STREAM_CANCEL');
273+
assert.strictEqual(err.name, 'Error');
274+
assert.strictEqual(req.aborted, false);
275+
assert.strictEqual(req.destroyed, true);
276+
}));
277+
req.on('close', common.mustCall(() => server.close()));
278+
279+
assert.strictEqual(req.aborted, false);
280+
assert.strictEqual(req.destroyed, false);
281+
// Signal listener attached
282+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
283+
284+
controller.abort();
285+
}));
286+
}
287+
testH2ConnectAbort(false);
288+
testH2ConnectAbort(true);
289+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto)
4+
common.skip('missing crypto');
5+
6+
const assert = require('assert');
7+
const https = require('https');
8+
const Agent = https.Agent;
9+
const fixtures = require('../common/fixtures');
10+
11+
const { getEventListeners } = require('events');
12+
const agent = new Agent();
13+
14+
const options = {
15+
key: fixtures.readKey('agent1-key.pem'),
16+
cert: fixtures.readKey('agent1-cert.pem')
17+
};
18+
19+
const server = https.createServer(options);
20+
21+
server.listen(0, common.mustCall(async () => {
22+
const port = server.address().port;
23+
const host = 'localhost';
24+
const options = {
25+
port: port,
26+
host: host,
27+
rejectUnauthorized: false,
28+
_agentKey: agent.getName({ port, host })
29+
};
30+
31+
function postCreateConnection() {
32+
return new Promise((res) => {
33+
const ac = new AbortController();
34+
const { signal } = ac;
35+
const connection = agent.createConnection({ ...options, signal });
36+
connection.on('error', common.mustCall((err) => {
37+
assert(err);
38+
assert.strictEqual(err.name, 'AbortError');
39+
res();
40+
}));
41+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
42+
ac.abort();
43+
});
44+
}
45+
46+
function preCreateConnection() {
47+
return new Promise((res) => {
48+
const ac = new AbortController();
49+
const { signal } = ac;
50+
ac.abort();
51+
const connection = agent.createConnection({ ...options, signal });
52+
connection.on('error', common.mustCall((err) => {
53+
assert(err);
54+
assert.strictEqual(err.name, 'AbortError');
55+
res();
56+
}));
57+
});
58+
}
59+
60+
// Blocked on https://github.com/nodejs/node/pull/37730
61+
// function agentAsParam() {
62+
// return new Promise((res) => {
63+
// const ac = new AbortController();
64+
// const { signal } = ac;
65+
// const request = https.get({
66+
// port: server.address().port,
67+
// path: '/hello',
68+
// agent: agent,
69+
// signal,
70+
// });
71+
// request.on('error', common.mustCall((err) => {
72+
// assert(err);
73+
// assert.strictEqual(err.name, 'AbortError');
74+
// res();
75+
// }));
76+
// assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
77+
// ac.abort();
78+
// });
79+
// }
80+
81+
// Blocked on https://github.com/nodejs/node/pull/37730
82+
// function agentAsParamPreAbort() {
83+
// return new Promise((res) => {
84+
// const ac = new AbortController();
85+
// const { signal } = ac;
86+
// ac.abort();
87+
// const request = https.get({
88+
// port: server.address().port,
89+
// path: '/hello',
90+
// agent: agent,
91+
// signal,
92+
// });
93+
// request.on('error', common.mustCall((err) => {
94+
// assert(err);
95+
// assert.strictEqual(err.name, 'AbortError');
96+
// res();
97+
// }));
98+
// assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
99+
// });
100+
// }
101+
102+
await postCreateConnection();
103+
await preCreateConnection();
104+
// Blocked on https://github.com/nodejs/node/pull/37730
105+
// await agentAsParam();
106+
// Blocked on https://github.com/nodejs/node/pull/37730
107+
// await agentAsParamPreAbort();
108+
server.close();
109+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
'use strict';
2+
const common = require('../common');
3+
const net = require('net');
4+
const assert = require('assert');
5+
const server = net.createServer();
6+
const { getEventListeners } = require('events');
7+
8+
const liveConnections = new Set();
9+
10+
server.listen(0, common.mustCall(async () => {
11+
const port = server.address().port;
12+
const host = 'localhost';
13+
const socketOptions = (signal) => ({ port, host, signal });
14+
server.on('connection', (connection) => {
15+
liveConnections.add(connection);
16+
connection.on('close', () => {
17+
liveConnections.delete(connection);
18+
});
19+
});
20+
21+
const attachHandlers = (socket, res) => {
22+
socket.on('error', common.mustCall((err) => {
23+
assert.strictEqual(err.name, 'AbortError');
24+
}));
25+
socket.on('close', () => {
26+
res();
27+
});
28+
};
29+
30+
function postAbort() {
31+
return new Promise((res) => {
32+
const ac = new AbortController();
33+
const { signal } = ac;
34+
const socket = net.connect(socketOptions(signal));
35+
attachHandlers(socket, res);
36+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
37+
ac.abort();
38+
});
39+
}
40+
41+
function preAbort() {
42+
return new Promise((res) => {
43+
const ac = new AbortController();
44+
const { signal } = ac;
45+
ac.abort();
46+
const socket = net.connect(socketOptions(signal));
47+
attachHandlers(socket, res);
48+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
49+
});
50+
}
51+
52+
function tickAbort() {
53+
return new Promise((res) => {
54+
const ac = new AbortController();
55+
const { signal } = ac;
56+
setImmediate(() => ac.abort());
57+
const socket = net.connect(socketOptions(signal));
58+
attachHandlers(socket, res);
59+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
60+
});
61+
}
62+
63+
function testConstructor() {
64+
return new Promise((res) => {
65+
const ac = new AbortController();
66+
const { signal } = ac;
67+
ac.abort();
68+
const socket = new net.Socket(socketOptions(signal));
69+
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
70+
attachHandlers(socket, res);
71+
});
72+
}
73+
74+
function testConstructorPost() {
75+
return new Promise((res) => {
76+
const ac = new AbortController();
77+
const { signal } = ac;
78+
const socket = new net.Socket(socketOptions(signal));
79+
attachHandlers(socket, res);
80+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
81+
ac.abort();
82+
});
83+
}
84+
85+
function testConstructorPostTick() {
86+
return new Promise((res) => {
87+
const ac = new AbortController();
88+
const { signal } = ac;
89+
const socket = new net.Socket(socketOptions(signal));
90+
attachHandlers(socket, res);
91+
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
92+
setImmediate(() => {
93+
ac.abort();
94+
});
95+
});
96+
}
97+
98+
await postAbort();
99+
await preAbort();
100+
await tickAbort();
101+
await testConstructor();
102+
await testConstructorPost();
103+
await testConstructorPostTick();
104+
105+
// Killing the net.socket without connecting hangs the server.
106+
for (const connection of liveConnections) {
107+
connection.destroy();
108+
}
109+
server.close(common.mustCall());
110+
}));

0 commit comments

Comments
 (0)