Skip to content

Commit d6c0eb3

Browse files
authored
feat(NODE-6245): add keepAliveInitialDelay config (#4510)
1 parent 2d86095 commit d6c0eb3

File tree

5 files changed

+239
-35
lines changed

5 files changed

+239
-35
lines changed

src/cmap/connect.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ export const LEGAL_TLS_SOCKET_OPTIONS = [
289289
export const LEGAL_TCP_SOCKET_OPTIONS = [
290290
'autoSelectFamily',
291291
'autoSelectFamilyAttemptTimeout',
292+
'keepAliveInitialDelay',
292293
'family',
293294
'hints',
294295
'localAddress',
@@ -306,6 +307,9 @@ function parseConnectOptions(options: ConnectionOptions): SocketConnectOpts {
306307
(result as Document)[name] = options[name];
307308
}
308309
}
310+
result.keepAliveInitialDelay ??= 120000;
311+
result.keepAlive = true;
312+
result.noDelay = options.noDelay ?? true;
309313

310314
if (typeof hostAddress.socketPath === 'string') {
311315
result.path = hostAddress.socketPath;
@@ -347,7 +351,6 @@ function parseSslOptions(options: MakeConnectionOptions): TLSConnectionOpts {
347351

348352
export async function makeSocket(options: MakeConnectionOptions): Promise<Stream> {
349353
const useTLS = options.tls ?? false;
350-
const noDelay = options.noDelay ?? true;
351354
const connectTimeoutMS = options.connectTimeoutMS ?? 30000;
352355
const existingSocket = options.existingSocket;
353356

@@ -376,9 +379,7 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
376379
socket = net.createConnection(parseConnectOptions(options));
377380
}
378381

379-
socket.setKeepAlive(true, 300000);
380382
socket.setTimeout(connectTimeoutMS);
381-
socket.setNoDelay(noDelay);
382383

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

src/connection_string.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,7 @@ export const OPTIONS = {
12731273
requestCert: { type: 'any' },
12741274
rejectUnauthorized: { type: 'any' },
12751275
checkServerIdentity: { type: 'any' },
1276+
keepAliveInitialDelay: { type: 'any' },
12761277
ALPNProtocols: { type: 'any' },
12771278
SNICallback: { type: 'any' },
12781279
session: { type: 'any' },

src/mongo_client.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,12 @@ export type SupportedTLSSocketOptions = Pick<
115115

116116
/** @public */
117117
export type SupportedSocketOptions = Pick<
118-
TcpNetConnectOpts & { autoSelectFamily?: boolean; autoSelectFamilyAttemptTimeout?: number },
118+
TcpNetConnectOpts & {
119+
autoSelectFamily?: boolean;
120+
autoSelectFamilyAttemptTimeout?: number;
121+
/** Node.JS socket option to set the time the first keepalive probe is sent on an idle socket. Defaults to 120000ms */
122+
keepAliveInitialDelay?: number;
123+
},
119124
(typeof LEGAL_TCP_SOCKET_OPTIONS)[number]
120125
>;
121126

test/integration/node-specific/mongo_client.test.ts

+211-11
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,206 @@ describe('class MongoClient', function () {
135135
expect(error).to.be.instanceOf(MongoServerSelectionError);
136136
});
137137

138+
describe('#connect', function () {
139+
context('when keepAliveInitialDelay is provided', function () {
140+
context('when the value is 0', function () {
141+
const options = { keepAliveInitialDelay: 0 };
142+
let client;
143+
let spy;
144+
145+
beforeEach(async function () {
146+
spy = sinon.spy(net, 'createConnection');
147+
const uri = this.configuration.url();
148+
client = new MongoClient(uri, options);
149+
await client.connect();
150+
});
151+
152+
afterEach(async function () {
153+
await client?.close();
154+
spy.restore();
155+
});
156+
157+
it('passes through the option', {
158+
metadata: { requires: { apiVersion: false } },
159+
test: function () {
160+
expect(spy).to.have.been.calledWith(
161+
sinon.match({
162+
keepAlive: true,
163+
keepAliveInitialDelay: 0
164+
})
165+
);
166+
}
167+
});
168+
});
169+
170+
context('when the value is positive', function () {
171+
const options = { keepAliveInitialDelay: 100 };
172+
let client;
173+
let spy;
174+
175+
beforeEach(async function () {
176+
spy = sinon.spy(net, 'createConnection');
177+
const uri = this.configuration.url();
178+
client = new MongoClient(uri, options);
179+
await client.connect();
180+
});
181+
182+
afterEach(async function () {
183+
await client?.close();
184+
spy.restore();
185+
});
186+
187+
it('passes through the option', {
188+
metadata: { requires: { apiVersion: false } },
189+
test: function () {
190+
expect(spy).to.have.been.calledWith(
191+
sinon.match({
192+
keepAlive: true,
193+
keepAliveInitialDelay: 100
194+
})
195+
);
196+
}
197+
});
198+
});
199+
200+
context('when the value is negative', function () {
201+
const options = { keepAliveInitialDelay: -100 };
202+
let client;
203+
let spy;
204+
205+
beforeEach(async function () {
206+
spy = sinon.spy(net, 'createConnection');
207+
const uri = this.configuration.url();
208+
client = new MongoClient(uri, options);
209+
await client.connect();
210+
});
211+
212+
afterEach(async function () {
213+
await client?.close();
214+
spy.restore();
215+
});
216+
217+
it('the Node.js runtime sets the option to 0', {
218+
metadata: { requires: { apiVersion: false } },
219+
test: function () {
220+
expect(spy).to.have.been.calledWith(
221+
sinon.match({
222+
keepAlive: true,
223+
keepAliveInitialDelay: 0
224+
})
225+
);
226+
}
227+
});
228+
});
229+
230+
context('when the value is mistyped', function () {
231+
// Set server selection timeout to get the error quicker.
232+
const options = { keepAliveInitialDelay: 'test', serverSelectionTimeoutMS: 1000 };
233+
let client;
234+
let spy;
235+
236+
beforeEach(async function () {
237+
spy = sinon.spy(net, 'createConnection');
238+
const uri = this.configuration.url();
239+
client = new MongoClient(uri, options);
240+
});
241+
242+
afterEach(async function () {
243+
await client?.close();
244+
spy.restore();
245+
});
246+
247+
it('throws an error', {
248+
metadata: { requires: { apiVersion: false } },
249+
test: async function () {
250+
const error = await client.connect().catch(error => error);
251+
expect(error.message).to.include(
252+
'property must be of type number. Received type string'
253+
);
254+
}
255+
});
256+
});
257+
});
258+
259+
context('when keepAliveInitialDelay is not provided', function () {
260+
let client;
261+
let spy;
262+
263+
beforeEach(async function () {
264+
spy = sinon.spy(net, 'createConnection');
265+
client = this.configuration.newClient();
266+
await client.connect();
267+
});
268+
269+
afterEach(async function () {
270+
await client?.close();
271+
spy.restore();
272+
});
273+
274+
it('sets keepalive to 120000', function () {
275+
expect(spy).to.have.been.calledWith(
276+
sinon.match({
277+
keepAlive: true,
278+
keepAliveInitialDelay: 120000
279+
})
280+
);
281+
});
282+
});
283+
284+
context('when noDelay is not provided', function () {
285+
let client;
286+
let spy;
287+
288+
beforeEach(async function () {
289+
spy = sinon.spy(net, 'createConnection');
290+
client = this.configuration.newClient();
291+
await client.connect();
292+
});
293+
294+
afterEach(async function () {
295+
await client?.close();
296+
spy.restore();
297+
});
298+
299+
it('sets noDelay to true', function () {
300+
expect(spy).to.have.been.calledWith(
301+
sinon.match({
302+
noDelay: true
303+
})
304+
);
305+
});
306+
});
307+
308+
context('when noDelay is provided', function () {
309+
let client;
310+
let spy;
311+
312+
beforeEach(async function () {
313+
const options = { noDelay: false };
314+
spy = sinon.spy(net, 'createConnection');
315+
const uri = this.configuration.url();
316+
client = new MongoClient(uri, options);
317+
await client.connect();
318+
});
319+
320+
afterEach(async function () {
321+
await client?.close();
322+
spy.restore();
323+
});
324+
325+
it('sets noDelay', {
326+
metadata: { requires: { apiVersion: false } },
327+
test: function () {
328+
expect(spy).to.have.been.calledWith(
329+
sinon.match({
330+
noDelay: false
331+
})
332+
);
333+
}
334+
});
335+
});
336+
});
337+
138338
it('Should correctly pass through appname', {
139339
metadata: {
140340
requires: {
@@ -889,12 +1089,12 @@ describe('class MongoClient', function () {
8891089
metadata: { requires: { topology: ['single'] } },
8901090
test: async function () {
8911091
await client.connect();
892-
expect(netSpy).to.have.been.calledWith({
893-
autoSelectFamily: false,
894-
autoSelectFamilyAttemptTimeout: 100,
895-
host: 'localhost',
896-
port: 27017
897-
});
1092+
expect(netSpy).to.have.been.calledWith(
1093+
sinon.match({
1094+
autoSelectFamily: false,
1095+
autoSelectFamilyAttemptTimeout: 100
1096+
})
1097+
);
8981098
}
8991099
});
9001100
});
@@ -908,11 +1108,11 @@ describe('class MongoClient', function () {
9081108
metadata: { requires: { topology: ['single'] } },
9091109
test: async function () {
9101110
await client.connect();
911-
expect(netSpy).to.have.been.calledWith({
912-
autoSelectFamily: true,
913-
host: 'localhost',
914-
port: 27017
915-
});
1111+
expect(netSpy).to.have.been.calledWith(
1112+
sinon.match({
1113+
autoSelectFamily: true
1114+
})
1115+
);
9161116
}
9171117
});
9181118
});

test/manual/tls_support.test.ts

+17-20
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,17 @@ describe('TLS Support', function () {
7777

7878
it('sets the default options', async function () {
7979
await client.connect();
80-
expect(tlsSpy).to.have.been.calledWith({
81-
autoSelectFamily: true,
82-
host: 'localhost',
83-
port: 27017,
84-
servername: 'localhost',
85-
ca: sinon.match.defined,
86-
cert: sinon.match.defined,
87-
key: sinon.match.defined
88-
});
80+
expect(tlsSpy).to.have.been.calledWith(
81+
sinon.match({
82+
ca: sinon.match.defined,
83+
cert: sinon.match.defined,
84+
key: sinon.match.defined
85+
})
86+
);
8987
});
9088
});
9189

92-
context('when auto family options are set', function () {
90+
context('when auto select family options are set', function () {
9391
let tlsSpy;
9492

9593
afterEach(function () {
@@ -107,16 +105,15 @@ describe('TLS Support', function () {
107105

108106
it('sets the provided options', async function () {
109107
await client.connect();
110-
expect(tlsSpy).to.have.been.calledWith({
111-
autoSelectFamily: false,
112-
autoSelectFamilyAttemptTimeout: 100,
113-
host: 'localhost',
114-
port: 27017,
115-
servername: 'localhost',
116-
ca: sinon.match.defined,
117-
cert: sinon.match.defined,
118-
key: sinon.match.defined
119-
});
108+
expect(tlsSpy).to.have.been.calledWith(
109+
sinon.match({
110+
autoSelectFamily: false,
111+
autoSelectFamilyAttemptTimeout: 100,
112+
ca: sinon.match.defined,
113+
cert: sinon.match.defined,
114+
key: sinon.match.defined
115+
})
116+
);
120117
});
121118
});
122119

0 commit comments

Comments
 (0)