Skip to content

Commit 4725298

Browse files
committed
net: defer self.destroy calls to nextTick
Wrote tests for the suggested fix in #48771 (comment) Fixes: #48771
1 parent 9404d3a commit 4725298

2 files changed

Lines changed: 81 additions & 22 deletions

File tree

‎lib/net.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,7 @@ function internalConnect(
10531053
err = checkBindError(err, localPort, self._handle);
10541054
if (err) {
10551055
const ex = new ExceptionWithHostPort(err, 'bind', localAddress, localPort);
1056-
self.destroy(ex);
1056+
process.nextTick(() => self.destroy(ex));
10571057
return;
10581058
}
10591059
}
@@ -1091,7 +1091,7 @@ function internalConnect(
10911091
}
10921092

10931093
const ex = new ExceptionWithHostPort(err, 'connect', address, port, details);
1094-
self.destroy(ex);
1094+
process.nextTick(() => self.destroy(ex));
10951095
} else if ((addressType === 6 || addressType === 4) && hasObserver('net')) {
10961096
startPerf(self, kPerfHooksNetConnectContext, { type: 'net', name: 'connect', detail: { host: address, port } });
10971097
}
@@ -1110,11 +1110,11 @@ function internalConnectMultiple(context, canceled) {
11101110
// All connections have been tried without success, destroy with error
11111111
if (canceled || context.current === context.addresses.length) {
11121112
if (context.errors.length === 0) {
1113-
self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT());
1113+
process.nextTick(() => self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT()));
11141114
return;
11151115
}
11161116

1117-
self.destroy(new NodeAggregateError(context.errors));
1117+
process.nextTick(() => self.destroy(new NodeAggregateError(context.errors)));
11181118
return;
11191119
}
11201120

‎test/parallel/test-http-client-immediate-error.js‎

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,95 @@ const assert = require('assert');
99
const net = require('net');
1010
const http = require('http');
1111
const { internalBinding } = require('internal/test/binding');
12-
const { UV_ENETUNREACH } = internalBinding('uv');
12+
const { UV_ENETUNREACH, UV_EADDRINUSE } = internalBinding('uv');
1313
const {
1414
newAsyncId,
1515
symbols: { async_id_symbol }
1616
} = require('internal/async_hooks');
1717

18-
const agent = new http.Agent();
19-
agent.createConnection = common.mustCall((cfg) => {
20-
const sock = new net.Socket();
18+
const config = {
19+
host: 'http://example.com',
20+
// We need to specify 'family' in both items of the array so 'internalConnectMultiple' is invoked
21+
connectMultiple: [{ address: '192.4.4.4', family: 4 }, { address: '200::1', family: 6 }],
22+
connectSolo: { address: '192.4.4.4', family: 4 },
23+
};
2124

22-
// Fake the handle so we can enforce returning an immediate error
23-
sock._handle = {
24-
connect: common.mustCall((req, addr, port) => {
25-
return UV_ENETUNREACH;
26-
}),
27-
readStart() {},
28-
close() {}
29-
};
25+
function agentFactory(handle, count) {
26+
const agent = new http.Agent();
27+
agent.createConnection = common.mustCall((cfg) => {
28+
const sock = new net.Socket();
3029

31-
// Simulate just enough socket handle initialization
32-
sock[async_id_symbol] = newAsyncId();
30+
// Fake the handle so we can enforce returning an immediate error
31+
sock._handle = handle;
3332

34-
sock.connect(cfg);
35-
return sock;
36-
});
33+
// Simulate just enough socket handle initialization
34+
sock[async_id_symbol] = newAsyncId();
35+
36+
sock.connect(cfg);
37+
return sock;
38+
}, count);
39+
40+
return agent;
41+
};
42+
43+
const handleWithoutBind = {
44+
connect: common.mustCall((req, addr, port) => {
45+
return UV_ENETUNREACH;
46+
}, 3), // Because two tests will use this
47+
readStart() { },
48+
close() { },
49+
};
50+
51+
const handleWithBind = {
52+
readStart() { },
53+
close() { },
54+
bind: common.mustCall(() => UV_EADDRINUSE, 2), // Because two tests will use this handle
55+
};
56+
57+
const agentWithoutBind = agentFactory(handleWithoutBind, 3);
58+
const agentWithBind = agentFactory(handleWithBind, 2);
3759

3860
http.get({
3961
host: '127.0.0.1',
4062
port: 1,
41-
agent
63+
agent: agentWithoutBind,
4264
}).on('error', common.mustCall((err) => {
4365
assert.strictEqual(err.code, 'ENETUNREACH');
4466
}));
67+
68+
http.request(config.host, {
69+
agent: agentWithoutBind,
70+
lookup(_0, _1, cb) {
71+
cb(null, config.connectMultiple);
72+
},
73+
}).on('error', common.mustCall((err) => {
74+
assert.strictEqual(err.code, 'ENETUNREACH');
75+
}));
76+
77+
http.request(config.host, {
78+
agent: agentWithoutBind,
79+
lookup(_0, _1, cb) {
80+
cb(null, config.connectSolo.address, config.connectSolo.family);
81+
},
82+
family: 4,
83+
}).on('error', common.mustCall((err) => {
84+
assert.strictEqual(err.code, 'ENETUNREACH');
85+
}));
86+
87+
http.request(config.host, {
88+
agent: agentWithBind,
89+
family: 4, // We specify 'family' so 'internalConnect' is invoked
90+
localPort: 2222 // Required to trigger _handle.bind()
91+
}).on('error', common.mustCall((err) => {
92+
assert.strictEqual(err.code, 'EADDRINUSE');
93+
}));
94+
95+
http.request(config.host, {
96+
agent: agentWithBind,
97+
lookup(_0, _1, cb) {
98+
cb(null, config.connectMultiple);
99+
},
100+
localPort: 2222, // Required to trigger _handle.bind()
101+
}).on('error', common.mustCall((err) => {
102+
assert.strictEqual(err.code, 'EADDRINUSE');
103+
}));

0 commit comments

Comments
 (0)