Skip to content

Commit fefc165

Browse files
committed
feat: use error labels for retryable writes in legacy topologies
NODE-2379
1 parent 79f4c65 commit fefc165

File tree

3 files changed

+59
-24
lines changed

3 files changed

+59
-24
lines changed

lib/core/topologies/mongos.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const cloneOptions = require('./shared').cloneOptions;
1313
const SessionMixins = require('./shared').SessionMixins;
1414
const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported;
1515
const relayEvents = require('../utils').relayEvents;
16-
const isRetryableError = require('../error').isRetryableError;
1716
const BSON = retrieveBSON();
1817
const getMMAPError = require('./shared').getMMAPError;
1918
const makeClientMetadata = require('../utils').makeClientMetadata;
19+
const legacyIsRetryableWriteError = require('./shared').legacyIsRetryableWriteError;
2020

2121
/**
2222
* @fileOverview The **Mongos** class is a class that represents a Mongos Proxy topology and is
@@ -113,6 +113,18 @@ var Mongos = function(seedlist, options) {
113113
// Get replSet Id
114114
this.id = id++;
115115

116+
// deduplicate seedlist
117+
if (Array.isArray(seedlist)) {
118+
seedlist = seedlist.reduce((seeds, seed) => {
119+
if (seeds.find(s => s.host === seed.host && s.port === seed.port)) {
120+
return seeds;
121+
}
122+
123+
seeds.push(seed);
124+
return seeds;
125+
}, []);
126+
}
127+
116128
// Internal state
117129
this.s = {
118130
options: Object.assign({ metadata: makeClientMetadata(options) }, options),
@@ -911,7 +923,7 @@ function executeWriteOperation(args, options, callback) {
911923

912924
const handler = (err, result) => {
913925
if (!err) return callback(null, result);
914-
if (!isRetryableError(err) || !willRetryWrite) {
926+
if (!legacyIsRetryableWriteError(err, self) || !willRetryWrite) {
915927
err = getMMAPError(err);
916928
return callback(err);
917929
}
@@ -1107,7 +1119,7 @@ Mongos.prototype.command = function(ns, cmd, options, callback) {
11071119

11081120
const cb = (err, result) => {
11091121
if (!err) return callback(null, result);
1110-
if (!isRetryableError(err)) {
1122+
if (!legacyIsRetryableWriteError(err, self)) {
11111123
return callback(err);
11121124
}
11131125

@@ -1121,8 +1133,8 @@ Mongos.prototype.command = function(ns, cmd, options, callback) {
11211133

11221134
// increment and assign txnNumber
11231135
if (willRetryWrite) {
1124-
options.session.incrementTransactionNumber();
1125-
options.willRetryWrite = willRetryWrite;
1136+
clonedOptions.session.incrementTransactionNumber();
1137+
clonedOptions.willRetryWrite = willRetryWrite;
11261138
}
11271139

11281140
// Execute the command

lib/core/topologies/replset.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ const Interval = require('./shared').Interval;
1515
const SessionMixins = require('./shared').SessionMixins;
1616
const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported;
1717
const relayEvents = require('../utils').relayEvents;
18-
const isRetryableError = require('../error').isRetryableError;
1918
const BSON = retrieveBSON();
2019
const calculateDurationInMs = require('../utils').calculateDurationInMs;
2120
const getMMAPError = require('./shared').getMMAPError;
2221
const makeClientMetadata = require('../utils').makeClientMetadata;
22+
const legacyIsRetryableWriteError = require('./shared').legacyIsRetryableWriteError;
2323

2424
//
2525
// States
@@ -1202,7 +1202,7 @@ function executeWriteOperation(args, options, callback) {
12021202

12031203
const handler = (err, result) => {
12041204
if (!err) return callback(null, result);
1205-
if (!isRetryableError(err)) {
1205+
if (!legacyIsRetryableWriteError(err, self)) {
12061206
err = getMMAPError(err);
12071207
return callback(err);
12081208
}
@@ -1365,7 +1365,7 @@ ReplSet.prototype.command = function(ns, cmd, options, callback) {
13651365

13661366
const cb = (err, result) => {
13671367
if (!err) return callback(null, result);
1368-
if (!isRetryableError(err)) {
1368+
if (!legacyIsRetryableWriteError(err, self)) {
13691369
return callback(err);
13701370
}
13711371

lib/core/topologies/shared.js

+39-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
const ReadPreference = require('./read_preference');
33
const TopologyType = require('../sdam/common').TopologyType;
44
const MongoError = require('../error').MongoError;
5-
5+
const isRetryableWriteError = require('../error').isRetryableWriteError;
6+
const maxWireVersion = require('../utils').maxWireVersion;
7+
const MongoNetworkError = require('../error').MongoNetworkError;
68
const MMAPv1_RETRY_WRITES_ERROR_CODE = 20;
79

810
/**
@@ -409,18 +411,39 @@ function getMMAPError(err) {
409411
return newErr;
410412
}
411413

412-
module.exports.SessionMixins = SessionMixins;
413-
module.exports.resolveClusterTime = resolveClusterTime;
414-
module.exports.inquireServerState = inquireServerState;
415-
module.exports.getTopologyType = getTopologyType;
416-
module.exports.emitServerDescriptionChanged = emitServerDescriptionChanged;
417-
module.exports.emitTopologyDescriptionChanged = emitTopologyDescriptionChanged;
418-
module.exports.cloneOptions = cloneOptions;
419-
module.exports.createCompressionInfo = createCompressionInfo;
420-
module.exports.clone = clone;
421-
module.exports.diff = diff;
422-
module.exports.Interval = Interval;
423-
module.exports.Timeout = Timeout;
424-
module.exports.isRetryableWritesSupported = isRetryableWritesSupported;
425-
module.exports.getMMAPError = getMMAPError;
426-
module.exports.topologyType = topologyType;
414+
// NOTE: only used for legacy topology types
415+
function legacyIsRetryableWriteError(err, topology) {
416+
if (!(err instanceof MongoError)) {
417+
return false;
418+
}
419+
420+
// if pre-4.4 server, then add error label if its a retryable write error
421+
if (
422+
isRetryableWritesSupported(topology) &&
423+
(err instanceof MongoNetworkError ||
424+
(maxWireVersion(topology) < 9 && isRetryableWriteError(err)))
425+
) {
426+
err.addErrorLabel('RetryableWriteError');
427+
}
428+
429+
return err.hasErrorLabel('RetryableWriteError');
430+
}
431+
432+
module.exports = {
433+
SessionMixins,
434+
resolveClusterTime,
435+
inquireServerState,
436+
getTopologyType,
437+
emitServerDescriptionChanged,
438+
emitTopologyDescriptionChanged,
439+
cloneOptions,
440+
createCompressionInfo,
441+
clone,
442+
diff,
443+
Interval,
444+
Timeout,
445+
isRetryableWritesSupported,
446+
getMMAPError,
447+
topologyType,
448+
legacyIsRetryableWriteError
449+
};

0 commit comments

Comments
 (0)