From b365c5061ded832e1682167edac58e8a04b05fc4 Mon Sep 17 00:00:00 2001 From: Eric Adum Date: Tue, 1 Dec 2020 12:11:58 -0500 Subject: [PATCH] fix: awaitable isMaster timeout must respect connectTimeoutMS (#2627) NODE-2874 --- lib/core/sdam/monitor.js | 23 ++++---- test/functional/mongo_client_options.test.js | 58 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/lib/core/sdam/monitor.js b/lib/core/sdam/monitor.js index 79bc6d0ac1..e86cba3d08 100644 --- a/lib/core/sdam/monitor.js +++ b/lib/core/sdam/monitor.js @@ -200,16 +200,19 @@ function checkServer(monitor, callback) { const topologyVersion = monitor[kServer].description.topologyVersion; const isAwaitable = topologyVersion != null; - const cmd = isAwaitable - ? { ismaster: true, maxAwaitTimeMS, topologyVersion: makeTopologyVersion(topologyVersion) } - : { ismaster: true }; - - const options = isAwaitable - ? { socketTimeout: connectTimeoutMS + maxAwaitTimeMS, exhaustAllowed: true } - : { socketTimeout: connectTimeoutMS }; - - if (isAwaitable && monitor[kRTTPinger] == null) { - monitor[kRTTPinger] = new RTTPinger(monitor[kCancellationToken], monitor.connectOptions); + const cmd = { ismaster: true }; + const options = { socketTimeout: connectTimeoutMS }; + + if (isAwaitable) { + cmd.maxAwaitTimeMS = maxAwaitTimeMS; + cmd.topologyVersion = makeTopologyVersion(topologyVersion); + if (connectTimeoutMS) { + options.socketTimeout = connectTimeoutMS + maxAwaitTimeMS; + } + options.exhaustAllowed = true; + if (monitor[kRTTPinger] == null) { + monitor[kRTTPinger] = new RTTPinger(monitor[kCancellationToken], monitor.connectOptions); + } } monitor[kConnection].command('admin.$cmd', cmd, options, (err, result) => { diff --git a/test/functional/mongo_client_options.test.js b/test/functional/mongo_client_options.test.js index 9eb5ff59a6..50cc791abd 100644 --- a/test/functional/mongo_client_options.test.js +++ b/test/functional/mongo_client_options.test.js @@ -2,6 +2,8 @@ const test = require('./shared').assert; const setupDatabase = require('./shared').setupDatabase; const expect = require('chai').expect; +const sinon = require('sinon'); +const Connection = require('../../lib/cmap/connection').Connection; describe('MongoClient Options', function() { before(function() { @@ -110,6 +112,62 @@ describe('MongoClient Options', function() { }); }); + it('must respect an infinite connectTimeoutMS for the streaming protocol', { + metadata: { requires: { topology: 'replicaset', mongodb: '>= 4.4' } }, + test: function(done) { + if (!this.configuration.usingUnifiedTopology()) return done(); + const client = this.configuration.newClient({ + connectTimeoutMS: 0, + heartbeatFrequencyMS: 500 + }); + client.connect(err => { + expect(err).to.not.exist; + const stub = sinon.stub(Connection.prototype, 'command').callsFake(function() { + const args = Array.prototype.slice.call(arguments); + const ns = args[0]; + const command = args[1]; + const options = args[2]; + if (ns === 'admin.$cmd' && command.ismaster && options.exhaustAllowed) { + stub.restore(); + expect(options) + .property('socketTimeout') + .to.equal(0); + client.close(done); + } + stub.wrappedMethod.apply(this, args); + }); + }); + } + }); + + it('must respect a finite connectTimeoutMS for the streaming protocol', { + metadata: { requires: { topology: 'replicaset', mongodb: '>= 4.4' } }, + test: function(done) { + if (!this.configuration.usingUnifiedTopology()) return done(); + const client = this.configuration.newClient({ + connectTimeoutMS: 10, + heartbeatFrequencyMS: 500 + }); + client.connect(err => { + expect(err).to.not.exist; + const stub = sinon.stub(Connection.prototype, 'command').callsFake(function() { + const args = Array.prototype.slice.call(arguments); + const ns = args[0]; + const command = args[1]; + const options = args[2]; + if (ns === 'admin.$cmd' && command.ismaster && options.exhaustAllowed) { + stub.restore(); + expect(options) + .property('socketTimeout') + .to.equal(510); + client.close(done); + } + stub.wrappedMethod.apply(this, args); + }); + }); + } + }); + /** * @ignore */