Skip to content

Commit 2b6a359

Browse files
committed
fix(topology): don't early abort server selection on network errors
Network errors encountered during server selection are meant to be stored on the topology description, in order to eventually convey the reason for failed selection to the user. We were reporting this error as soon as it was encountered erroneously. Now, we wait until selection has failed to report the newly added `reason` property of the `MongoTimeoutError` returned.
1 parent dc70c2d commit 2b6a359

10 files changed

+60
-23
lines changed

lib/core/error.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,17 @@ class MongoParseError extends MongoError {
8383
* An error signifying a timeout event
8484
*
8585
* @param {Error|string|object} message The error message
86+
* @param {string|object} [reason] The reason the timeout occured
8687
* @property {string} message The error message
88+
* @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
8789
*/
8890
class MongoTimeoutError extends MongoError {
89-
constructor(message) {
91+
constructor(message, reason) {
9092
super(message);
9193
this.name = 'MongoTimeoutError';
94+
if (reason != null) {
95+
this.reason = reason;
96+
}
9297
}
9398
}
9499

lib/core/sdam/topology.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -836,18 +836,18 @@ function selectServers(topology, selector, timeout, start, callback) {
836836
clearTimeout(iterationTimer);
837837
topology.s.iterationTimers.splice(timerIndex, 1);
838838

839-
if (topology.description.error) {
840-
callback(topology.description.error, null);
841-
return;
842-
}
843-
844839
// topology description has changed due to monitoring, reattempt server selection
845840
selectServers(topology, selector, timeout, start, callback);
846841
};
847842

848843
const iterationTimer = setTimeout(() => {
849844
topology.removeListener('topologyDescriptionChanged', descriptionChangedHandler);
850-
callback(new MongoTimeoutError(`Server selection timed out after ${timeout} ms`));
845+
callback(
846+
new MongoTimeoutError(
847+
`Server selection timed out after ${timeout} ms`,
848+
topology.description.error
849+
)
850+
);
851851
}, timeout - duration);
852852

853853
// track this timer in case we need to clean it up outside this loop

test/core/functional/server_tests.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -993,12 +993,17 @@ describe('Server tests', function() {
993993
});
994994

995995
const config = this.configuration;
996-
var client = config.newTopology(server.address().host, server.address().port);
996+
var client = config.newTopology(server.address().host, server.address().port, {
997+
serverSelectionTimeoutMS: 10
998+
});
999+
9971000
client.on('error', error => {
9981001
let err;
9991002
try {
10001003
expect(error).to.be.an.instanceOf(Error);
1001-
expect(error.message).to.match(/but this version of the Node.js Driver requires/);
1004+
1005+
const errorMessage = error.reason ? error.reason.message : error.message;
1006+
expect(errorMessage).to.match(/but this version of the Node.js Driver requires/);
10021007
} catch (e) {
10031008
err = e;
10041009
}

test/functional/connection_tests.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,9 @@ describe('Connection', function() {
423423
// The actual test we wish to run
424424
test: function(done) {
425425
var configuration = this.configuration;
426-
const client = configuration.newClient(configuration.url('slithy', 'toves'));
426+
const client = configuration.newClient(configuration.url('slithy', 'toves'), {
427+
serverSelectionTimeoutMS: 10
428+
});
427429

428430
client.connect(function(err, client) {
429431
expect(err).to.exist;

test/functional/db_tests.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@ describe('Db', function() {
243243
test: function(done) {
244244
var configuration = this.configuration;
245245
var fs_client = configuration.newClient('mongodb://127.0.0.1:25117/test', {
246-
auto_reconnect: false
246+
auto_reconnect: false,
247+
serverSelectionTimeoutMS: 10
247248
});
248249

249250
fs_client.connect(function(err) {
@@ -435,7 +436,8 @@ describe('Db', function() {
435436
var configuration = this.configuration;
436437
var client = configuration.newClient(`mongodb://127.0.0.1:27088/test`, {
437438
auto_reconnect: false,
438-
poolSize: 4
439+
poolSize: 4,
440+
serverSelectionTimeoutMS: 10
439441
});
440442

441443
// Establish connection to db

test/functional/mongo_client_tests.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,10 @@ describe('MongoClient', function() {
474474
// The actual test we wish to run
475475
test: function(done) {
476476
var configuration = this.configuration;
477-
const client = configuration.newClient('mongodb://localhost:27088/test');
477+
const client = configuration.newClient('mongodb://localhost:27088/test', {
478+
serverSelectionTimeoutMS: 10
479+
});
480+
478481
client.connect(function(err) {
479482
test.ok(err != null);
480483

@@ -508,7 +511,10 @@ describe('MongoClient', function() {
508511
// The actual test we wish to run
509512
test: function(done) {
510513
var configuration = this.configuration;
511-
const client = configuration.newClient('mongodb://test.does.not.exist.com:80/test');
514+
const client = configuration.newClient('mongodb://test.does.not.exist.com:80/test', {
515+
serverSelectionTimeoutMS: 10
516+
});
517+
512518
client.connect(function(err) {
513519
test.ok(err != null);
514520

@@ -593,7 +599,10 @@ describe('MongoClient', function() {
593599
// The actual test we wish to run
594600
test: function(done) {
595601
var configuration = this.configuration;
596-
const client = configuration.newClient('mongodb://unknownhost:36363/ddddd');
602+
const client = configuration.newClient('mongodb://unknownhost:36363/ddddd', {
603+
serverSelectionTimeoutMS: 10
604+
});
605+
597606
client.connect(function(err) {
598607
test.ok(err != null);
599608
done();

test/functional/operation_example_tests.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4027,7 +4027,8 @@ describe('Operation Examples', function() {
40274027

40284028
const oldClient = secondClient;
40294029
const thirdClient = configuration.newClient(
4030-
'mongodb://user:name@localhost:27017/integration_tests'
4030+
'mongodb://user:name@localhost:27017/integration_tests',
4031+
{ serverSelectionTimeoutMS: 10 }
40314032
);
40324033

40334034
// Authenticate

test/functional/operation_generators_example_tests.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -2966,7 +2966,10 @@ describe('Operation (Generators)', function() {
29662966

29672967
try {
29682968
// Authenticate
2969-
const client = configuration.newClient('mongodb://user:name@localhost:27017/admin');
2969+
const client = configuration.newClient('mongodb://user:name@localhost:27017/admin', {
2970+
serverSelectionTimeoutMS: 10
2971+
});
2972+
29702973
yield client.connect();
29712974
test.ok(false);
29722975
} catch (err) {} // eslint-disable-line

test/functional/operation_promises_example_tests.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3174,7 +3174,8 @@ describe('Operation (Promises)', function() {
31743174

31753175
// Should error out due to user no longer existing
31763176
const thirdClient = configuration.newClient(
3177-
'mongodb://user3:name@localhost:27017/integration_tests'
3177+
'mongodb://user3:name@localhost:27017/integration_tests',
3178+
{ serverSelectionTimeoutMS: 10 }
31783179
);
31793180

31803181
return thirdClient.connect();

test/functional/scram_sha_256_tests.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,17 @@ describe('SCRAM-SHA-256 auth', function() {
179179
password: userMap.sha256.password
180180
},
181181
authSource: this.configuration.db,
182-
authMechanism: 'SCRAM-SHA-1'
182+
authMechanism: 'SCRAM-SHA-1',
183+
serverSelectionTimeoutMS: 10
183184
};
184185

185186
return withClient(
186187
this.configuration.newClient({}, options),
187188
() => Promise.reject(new Error('This request should have failed to authenticate')),
188-
err => expect(err).to.match(/Authentication failed/)
189+
err => {
190+
const errMessage = err.reason ? err.reason.message : err;
191+
expect(errMessage).to.match(/Authentication failed/);
192+
}
189193
);
190194
}
191195
});
@@ -202,22 +206,27 @@ describe('SCRAM-SHA-256 auth', function() {
202206
user: 'roth',
203207
password: 'pencil'
204208
},
205-
authSource: 'admin'
209+
authSource: 'admin',
210+
serverSelectionTimeoutMS: 1000
206211
};
207212

208213
const badPasswordOptions = {
209214
auth: {
210215
user: 'both',
211216
password: 'pencil'
212217
},
213-
authSource: 'admin'
218+
authSource: 'admin',
219+
serverSelectionTimeoutMS: 1000
214220
};
215221

216222
const getErrorMsg = options =>
217223
withClient(
218224
this.configuration.newClient({}, options),
219225
() => Promise.reject(new Error('This request should have failed to authenticate')),
220-
err => expect(err).to.match(/Authentication failed/)
226+
err => {
227+
const errMessage = err.reason ? err.reason.message : err;
228+
expect(errMessage).to.match(/Authentication failed/);
229+
}
221230
);
222231

223232
return Promise.all([getErrorMsg(noUsernameOptions), getErrorMsg(badPasswordOptions)]);

0 commit comments

Comments
 (0)