diff --git a/lib/core/error.js b/lib/core/error.js index d35fbbeffa..f4c62c5e0a 100644 --- a/lib/core/error.js +++ b/lib/core/error.js @@ -60,9 +60,6 @@ class MongoNetworkError extends MongoError { constructor(message) { super(message); this.name = 'MongoNetworkError'; - - // This is added as part of the transactions specification - this.errorLabels = ['TransientTransactionError']; } } diff --git a/lib/core/wireprotocol/command.js b/lib/core/wireprotocol/command.js index 47107c625b..09ea788311 100644 --- a/lib/core/wireprotocol/command.js +++ b/lib/core/wireprotocol/command.js @@ -8,6 +8,7 @@ const isSharded = require('./shared').isSharded; const databaseNamespace = require('./shared').databaseNamespace; const isTransactionCommand = require('../transactions').isTransactionCommand; const applySession = require('../sessions').applySession; +const MongoNetworkError = require('../error').MongoNetworkError; function isClientEncryptionEnabled(server) { return server.autoEncrypter; @@ -90,6 +91,18 @@ function _command(server, ns, cmd, options, callback) { const inTransaction = session && (session.inTransaction() || isTransactionCommand(finalCmd)); const commandResponseHandler = inTransaction ? function(err) { + // We need to add a TransientTransactionError errorLabel, as stated in the transaction spec. + if ( + err && + err instanceof MongoNetworkError && + !err.hasErrorLabel('TransientTransactionError') + ) { + if (err.errorLabels == null) { + err.errorLabels = []; + } + err.errorLabels.push('TransientTransactionError'); + } + if ( !cmd.commitTransaction && err && diff --git a/test/functional/transactions_tests.js b/test/functional/transactions_tests.js index 74fc46e0cc..f799296594 100644 --- a/test/functional/transactions_tests.js +++ b/test/functional/transactions_tests.js @@ -7,6 +7,7 @@ const sessions = core.Sessions; const TestRunnerContext = require('./spec-runner').TestRunnerContext; const gatherTestSuites = require('./spec-runner').gatherTestSuites; const generateTopologyTests = require('./spec-runner').generateTopologyTests; +const MongoNetworkError = require('../../lib/core').MongoNetworkError; describe('Transactions', function() { const testContext = new TestRunnerContext(); @@ -123,4 +124,76 @@ describe('Transactions', function() { } }); }); + + describe('TransientTransactionError', function() { + it('should have a TransientTransactionError label inside of a transaction', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.0.0' } }, + test: function(done) { + const configuration = this.configuration; + const client = configuration.newClient({ w: 1 }, { useUnifiedTopology: true }); + + client.connect((err, client) => { + const session = client.startSession(); + const db = client.db(configuration.db); + db.createCollection('transaction_error_test', (err, coll) => { + expect(err).to.not.exist; + session.startTransaction(); + coll.insertOne({ a: 1 }, { session }, err => { + expect(err).to.not.exist; + expect(session.inTransaction()).to.be.true; + + db.executeDbAdminCommand( + { + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { failCommands: ['insert'], closeConnection: true } + }, + () => { + expect(session.inTransaction()).to.be.true; + coll.insertOne({ b: 2 }, { session }, err => { + expect(err) + .to.exist.and.to.be.an.instanceof(MongoNetworkError) + .and.to.have.a.property('errorLabels') + .that.includes('TransientTransactionError'); + session.endSession(() => client.close(done)); + }); + } + ); + }); + }); + }); + } + }); + + it('should not have a TransientTransactionError label outside of a transaction', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.0.0' } }, + test: function(done) { + const configuration = this.configuration; + const client = configuration.newClient({ w: 1 }, { useUnifiedTopology: true }); + + client.connect((err, client) => { + const db = client.db(configuration.db); + const coll = db.collection('transaction_error_test1'); + + db.executeDbAdminCommand( + { + configureFailPoint: 'failCommand', + mode: 'alwaysOn', + data: { failCommands: ['insert'], closeConnection: true } + }, + () => { + coll.insertOne({ a: 1 }, err => { + expect(err) + .to.exist.and.to.be.an.instanceOf(MongoNetworkError) + .and.to.not.have.a.property('errorLabels'); + db.executeDbAdminCommand({ configureFailPoint: 'failCommand', mode: 'off' }, () => { + client.close(done); + }); + }); + } + ); + }); + } + }); + }); });