Skip to content

Commit 6614fe6

Browse files
committed
net: report BoundSocket connect errors asynchronously
Keep the synchronous connect(2) issuance for adopted BoundSockets (so the concrete source address is observable via localAddress once connect() returns), but deliver connection failures through the normal deferred 'error' event on all platforms instead of throwing synchronously. This gives a single, consistent error channel across POSIX and Windows, where connect(2) is asynchronous (ConnectEx).
1 parent 3bc518c commit 6614fe6

3 files changed

Lines changed: 15 additions & 21 deletions

File tree

doc/api/net.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,8 +1658,9 @@ throw [`ERR_SOCKET_HANDLE_ADOPTED`][]. A handle that is never adopted must be
16581658
closed to avoid leaking the socket.
16591659

16601660
When an adopted `BoundSocket` connects to a numeric IP literal, `connect(2)` is
1661-
issued synchronously: [`socket.localAddress`][] is resolved once
1662-
[`socket.connect()`][] returns, and on POSIX failures throw synchronously.
1661+
issued synchronously, so [`socket.localAddress`][] is resolved once
1662+
[`socket.connect()`][] returns. Connection failures are still reported via a
1663+
deferred `'error'` event.
16631664

16641665
```mjs
16651666
import net from 'node:net';

lib/net.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,7 +1249,7 @@ function checkBindError(err, port, handle) {
12491249

12501250

12511251
function internalConnect(
1252-
self, address, port, addressType, localAddress, localPort, flags, boundSocket) {
1252+
self, address, port, addressType, localAddress, localPort, flags) {
12531253
// TODO return promise from Socket.prototype.connect which
12541254
// wraps _connectReq.
12551255

@@ -1313,12 +1313,6 @@ function internalConnect(
13131313
}
13141314

13151315
const ex = new ExceptionWithHostPort(err, 'connect', address, port, details);
1316-
// Synchronous connect(2): throw in-tick instead of a deferred 'error'.
1317-
if (boundSocket) {
1318-
self.connecting = false;
1319-
self.destroy();
1320-
throw ex;
1321-
}
13221316
self.destroy(ex);
13231317
} else if ((addressType === 6 || addressType === 4) && hasObserver('net')) {
13241318
startPerf(self, kPerfHooksNetConnectContext, { type: 'net', name: 'connect', detail: { host: address, port } });
@@ -1574,12 +1568,13 @@ function lookupAndConnect(self, options) {
15741568
const addressType = isIP(host);
15751569
if (addressType) {
15761570
// An adopted BoundSocket needs no lookup: issue connect(2) in-tick so the
1577-
// concrete source address resolves and failures throw synchronously.
1571+
// kernel-resolved concrete source address is observable synchronously.
1572+
// Connection failures are still reported via a deferred 'error' event.
15781573
if (self[kBoundSource]) {
15791574
defaultTriggerAsyncIdScope(
15801575
self[async_id_symbol],
15811576
internalConnect,
1582-
self, host, port, addressType, localAddress, localPort, 0, true,
1577+
self, host, port, addressType, localAddress, localPort,
15831578
);
15841579
return;
15851580
}

test/parallel/test-net-boundsocket.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,16 @@ if (!common.isWindows && process.getuid() !== 0) {
202202
}));
203203
}
204204

205-
// A synchronous connect(2) failure throws in-tick, without an 'error' event.
206-
// An IPv4-bound handle connecting to an IPv6 literal fails on family mismatch.
207-
// POSIX-only: on Windows connect(2) is asynchronous (ConnectEx), so the error
208-
// is deferred rather than thrown.
209-
if (!common.isWindows) {
205+
// A connect failure is reported via a deferred 'error' event, never thrown
206+
// synchronously. An IPv4-bound handle connecting to an IPv6 literal fails on
207+
// address family mismatch.
208+
{
210209
const bound = new net.BoundSocket({ host: '127.0.0.1', port: 0 });
211210
const client = new net.Socket({ handle: bound });
212-
client.on('error', common.mustNotCall());
213-
assert.throws(() => {
214-
client.connect({ host: '::1', port: 1 });
215-
}, { syscall: 'connect' });
216-
assert.strictEqual(client.destroyed, true);
211+
client.connect({ host: '::1', port: 1 });
212+
client.on('error', common.mustCall((err) => {
213+
assert.strictEqual(err.syscall, 'connect');
214+
}));
217215
}
218216

219217
// reusePort: SO_REUSEPORT permits multiple listeners on the same port. Support

0 commit comments

Comments
 (0)