Skip to content

Commit 8e35f8e

Browse files
committed
fix(NODE-7459): explicitly call setKeepAlive and setNoDelay on socket
1 parent 22c6031 commit 8e35f8e

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

src/cmap/connect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
396396
socket = net.createConnection(parseConnectOptions(options));
397397
}
398398

399+
socket.setKeepAlive(true, options.keepAliveInitialDelay ?? 120_000);
400+
socket.setNoDelay(options.noDelay ?? true);
399401
socket.setTimeout(connectTimeoutMS);
400402

401403
let cancellationHandler: ((err: Error) => void) | null = null;

test/unit/cmap/connect.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { expect } from 'chai';
2+
import { execSync } from 'child_process';
3+
import * as crypto from 'crypto';
4+
import * as net from 'net';
25
import * as process from 'process';
6+
import * as sinon from 'sinon';
7+
import * as tls from 'tls';
38

49
import {
510
CancellationToken,
@@ -11,6 +16,7 @@ import {
1116
isHello,
1217
LEGACY_HELLO_COMMAND,
1318
makeClientMetadata,
19+
makeSocket,
1420
MongoClientAuthProviders,
1521
MongoCredentials,
1622
MongoNetworkError,
@@ -448,4 +454,119 @@ describe('Connect Tests', function () {
448454
});
449455
});
450456
});
457+
458+
describe('makeSocket', function () {
459+
// TLS sockets created by tls.connect() do not honor keepAlive/noDelay constructor
460+
// options due to a Node.js bug (options are not forwarded to the net.Socket constructor).
461+
// The driver must call setKeepAlive/setNoDelay explicitly on all sockets.
462+
// See: https://github.com/nodejs/node/issues/...
463+
464+
let tlsServer: tls.Server;
465+
let tlsPort: number;
466+
let setKeepAliveSpy: sinon.SinonSpy;
467+
let setNoDelaySpy: sinon.SinonSpy;
468+
469+
before(function (done) {
470+
const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
471+
const key = privateKey.export({ type: 'pkcs8', format: 'pem' });
472+
const cert = execSync(
473+
'openssl req -new -x509 -key /dev/stdin -out /dev/stdout -days 1 -subj /CN=localhost -batch 2>/dev/null',
474+
{ input: key }
475+
).toString();
476+
477+
tlsServer = tls.createServer({ key, cert }, () => {
478+
/* empty */
479+
});
480+
tlsServer.listen(0, '127.0.0.1', () => {
481+
tlsPort = (tlsServer.address() as net.AddressInfo).port;
482+
done();
483+
});
484+
});
485+
486+
after(function () {
487+
tlsServer?.close();
488+
});
489+
490+
beforeEach(function () {
491+
setKeepAliveSpy = sinon.spy(net.Socket.prototype, 'setKeepAlive');
492+
setNoDelaySpy = sinon.spy(net.Socket.prototype, 'setNoDelay');
493+
});
494+
495+
afterEach(function () {
496+
sinon.restore();
497+
});
498+
499+
context('when tls is enabled', function () {
500+
it('calls setKeepAlive with default keepAliveInitialDelay', async function () {
501+
const socket = await makeSocket({
502+
hostAddress: new HostAddress(`127.0.0.1:${tlsPort}`),
503+
tls: true,
504+
rejectUnauthorized: false
505+
} as ConnectionOptions);
506+
507+
try {
508+
expect(setKeepAliveSpy).to.have.been.calledWith(true, 120000);
509+
} finally {
510+
socket.destroy();
511+
}
512+
});
513+
514+
it('calls setKeepAlive with custom keepAliveInitialDelay', async function () {
515+
const socket = await makeSocket({
516+
hostAddress: new HostAddress(`127.0.0.1:${tlsPort}`),
517+
tls: true,
518+
rejectUnauthorized: false,
519+
keepAliveInitialDelay: 5000
520+
} as ConnectionOptions);
521+
522+
try {
523+
expect(setKeepAliveSpy).to.have.been.calledWith(true, 5000);
524+
} finally {
525+
socket.destroy();
526+
}
527+
});
528+
529+
it('calls setNoDelay with true by default', async function () {
530+
const socket = await makeSocket({
531+
hostAddress: new HostAddress(`127.0.0.1:${tlsPort}`),
532+
tls: true,
533+
rejectUnauthorized: false
534+
} as ConnectionOptions);
535+
536+
try {
537+
expect(setNoDelaySpy).to.have.been.calledWith(true);
538+
} finally {
539+
socket.destroy();
540+
}
541+
});
542+
});
543+
544+
context('when tls is disabled', function () {
545+
it('calls setKeepAlive with default keepAliveInitialDelay', async function () {
546+
const socket = await makeSocket({
547+
hostAddress: new HostAddress(`127.0.0.1:${tlsPort}`),
548+
tls: false
549+
} as ConnectionOptions);
550+
551+
try {
552+
expect(setKeepAliveSpy).to.have.been.calledWith(true, 120000);
553+
} finally {
554+
socket.destroy();
555+
}
556+
});
557+
558+
it('calls setNoDelay with true by default', async function () {
559+
const socket = await makeSocket({
560+
hostAddress: new HostAddress(`127.0.0.1:${tlsPort}`),
561+
tls: false
562+
} as ConnectionOptions);
563+
564+
try {
565+
expect(setNoDelaySpy).to.have.been.calledWith(true);
566+
} finally {
567+
socket.destroy();
568+
}
569+
});
570+
});
571+
});
451572
});

0 commit comments

Comments
 (0)