From 028aec79f5d3ac5dc5361f643be88d65c7a23a3d Mon Sep 17 00:00:00 2001 From: Matt Broadstone Date: Fri, 1 Dec 2017 15:31:30 -0500 Subject: [PATCH] feat(retryable-writes): retry on "not master" stepdown errors NODE-1105 --- lib/topologies/mongos.js | 2 +- lib/topologies/replset.js | 2 +- .../unit/mongos/retryable_writes_tests.js | 54 +++++++++++++++++++ .../unit/replset/retryable_writes_tests.js | 54 +++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/lib/topologies/mongos.js b/lib/topologies/mongos.js index 836b69f4c..5f881517e 100644 --- a/lib/topologies/mongos.js +++ b/lib/topologies/mongos.js @@ -900,7 +900,7 @@ var executeWriteOperation = function(self, op, ns, ops, options, callback) { server[op](ns, ops, options, (err, result) => { if (!err) return callback(null, result); - if (!(err instanceof errors.MongoNetworkError)) { + if (!(err instanceof errors.MongoNetworkError) && !err.message.match(/not master/)) { return callback(err); } diff --git a/lib/topologies/replset.js b/lib/topologies/replset.js index 3fec27d4a..f6472cda3 100644 --- a/lib/topologies/replset.js +++ b/lib/topologies/replset.js @@ -1187,7 +1187,7 @@ var executeWriteOperation = function(self, op, ns, ops, options, callback) { self.s.replicaSetState.primary[op](ns, ops, options, (err, result) => { if (!err) return callback(null, result); - if (!(err instanceof errors.MongoNetworkError)) { + if (!(err instanceof errors.MongoNetworkError) && !err.message.match(/not master/)) { return callback(err); } diff --git a/test/tests/unit/mongos/retryable_writes_tests.js b/test/tests/unit/mongos/retryable_writes_tests.js index 0020be26f..08e7ad326 100644 --- a/test/tests/unit/mongos/retryable_writes_tests.js +++ b/test/tests/unit/mongos/retryable_writes_tests.js @@ -113,4 +113,58 @@ describe('Retryable Writes (Mongos)', function() { mongos.connect(); } }); + + it('should retry write commands where `retryWrites` is true, and there is a "not master" error', { + metadata: { requires: { topology: ['single'] } }, + test: function(done) { + const mongos = new Mongos(test.servers.map(server => server.address()), { + connectionTimeout: 3000, + socketTimeout: 0, + haInterval: 10000, + localThresholdMS: 500, + size: 1 + }); + + const sessionPool = new ServerSessionPool(mongos); + const session = new ClientSession(mongos, sessionPool); + + let command = null, + insertCount = 0; + + const messageHandler = () => { + return request => { + const doc = request.document; + if (doc.ismaster) { + request.reply(test.defaultFields); + } else if (doc.insert) { + insertCount++; + if (insertCount === 1) { + request.reply({ ok: 0, errmsg: 'not master' }); // simulate a stepdown + } else { + command = doc; + request.reply({ ok: 1 }); + } + } + }; + }; + + test.servers[0].setMessageHandler(messageHandler('MONGOS1')); + test.servers[1].setMessageHandler(messageHandler('MONGOS2')); + mongos.once('fullsetup', function() { + mongos.insert('test.test', [{ a: 1 }], { retryWrites: true, session: session }, function( + err + ) { + expect(err).to.not.exist; + expect(command).to.have.property('txnNumber'); + expect(command.txnNumber).to.eql(1); + + mongos.destroy(); + done(); + }); + }); + + mongos.on('error', done); + mongos.connect(); + } + }); }); diff --git a/test/tests/unit/replset/retryable_writes_tests.js b/test/tests/unit/replset/retryable_writes_tests.js index 8d74c69fc..95872046f 100644 --- a/test/tests/unit/replset/retryable_writes_tests.js +++ b/test/tests/unit/replset/retryable_writes_tests.js @@ -111,4 +111,58 @@ describe('Retryable Writes (ReplSet)', function() { replset.connect(); } }); + + it('should retry write commands where `retryWrites` is true, and there is a "not master" error', { + metadata: { requires: { topology: ['single'] } }, + test: function(done) { + var replset = new ReplSet( + [test.primaryServer.address(), test.firstSecondaryServer.address()], + { + setName: 'rs', + connectionTimeout: 100, + socketTimeout: 0, + haInterval: 100, + size: 5, + minSize: 1 + } + ); + + const sessionPool = new ServerSessionPool(replset); + const session = new ClientSession(replset, sessionPool); + + let command = null, + insertCount = 0; + + test.primaryServer.setMessageHandler(request => { + const doc = request.document; + if (doc.ismaster) { + request.reply(test.primaryStates[0]); + } else if (doc.insert) { + insertCount++; + if (insertCount === 1) { + request.reply({ ok: 0, errmsg: 'not master' }); // simulate a stepdown + } else { + command = doc; + request.reply({ ok: 1 }); + } + } + }); + + replset.on('all', () => { + replset.insert('test.test', [{ a: 1 }], { retryWrites: true, session: session }, function( + err + ) { + expect(err).to.not.exist; + expect(command).to.have.property('txnNumber'); + expect(command.txnNumber).to.eql(1); + + replset.destroy(); + done(); + }); + }); + + replset.on('error', done); + replset.connect(); + } + }); });