Skip to content

Commit 07034b4

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

File tree

2 files changed

+81
-22
lines changed

2 files changed

+81
-22
lines changed

lib/net.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ function internalConnect(
10661066
err = checkBindError(err, localPort, self._handle);
10671067
if (err) {
10681068
const ex = new ExceptionWithHostPort(err, 'bind', localAddress, localPort);
1069-
self.destroy(ex);
1069+
process.nextTick(() => self.destroy(ex));
10701070
return;
10711071
}
10721072
}
@@ -1108,7 +1108,7 @@ function internalConnect(
11081108
}
11091109

11101110
const ex = new ExceptionWithHostPort(err, 'connect', address, port, details);
1111-
self.destroy(ex);
1111+
process.nextTick(() => self.destroy(ex));
11121112
} else if ((addressType === 6 || addressType === 4) && hasObserver('net')) {
11131113
startPerf(self, kPerfHooksNetConnectContext, { type: 'net', name: 'connect', detail: { host: address, port } });
11141114
}
@@ -1127,11 +1127,11 @@ function internalConnectMultiple(context, canceled) {
11271127
// All connections have been tried without success, destroy with error
11281128
if (canceled || context.current === context.addresses.length) {
11291129
if (context.errors.length === 0) {
1130-
self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT());
1130+
process.nextTick(() => self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT()));
11311131
return;
11321132
}
11331133

1134-
self.destroy(new NodeAggregateError(context.errors));
1134+
process.nextTick(() => self.destroy(new NodeAggregateError(context.errors)));
11351135
return;
11361136
}
11371137

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)