diff --git a/src/sessions.ts b/src/sessions.ts index bad966ed71c..4029744dcac 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -124,6 +124,10 @@ export class ClientSession owner?: symbol | AbstractCursor; defaultTransactionOptions: TransactionOptions; transaction: Transaction; + /** @internal + * Keeps track of whether or not the current transaction has attempted to be committed. Is + * initially undefined. Gets set to false when startTransaction is called. When commitTransaction is sent to server, if the commitTransaction succeeds, it is then set to undefined, otherwise, set to true */ + commitAttempted?: boolean; /** @internal */ [kServerSession]: ServerSession | null; /** @internal */ @@ -417,6 +421,7 @@ export class ClientSession ); } + this.commitAttempted = false; // increment txnNumber this.incrementTransactionNumber(); // create transaction state @@ -474,7 +479,7 @@ export class ClientSession WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc }); } - if (this.transaction.state === TxnState.TRANSACTION_COMMITTED) { + if (this.transaction.state === TxnState.TRANSACTION_COMMITTED || this.commitAttempted) { WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' }); } @@ -494,8 +499,10 @@ export class ClientSession try { await executeOperation(this.client, operation); + this.commitAttempted = undefined; return; } catch (firstCommitError) { + this.commitAttempted = true; if (firstCommitError instanceof MongoError && isRetryableWriteError(firstCommitError)) { // SPEC-1185: apply majority write concern when retrying commitTransaction WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' }); @@ -503,7 +510,14 @@ export class ClientSession this.unpin({ force: true }); try { - await executeOperation(this.client, operation); + await executeOperation( + this.client, + new RunAdminCommandOperation(command, { + session: this, + readPreference: ReadPreference.primary, + bypassPinningCheck: true + }) + ); return; } catch (retryCommitError) { // If the retry failed, we process that error instead of the original diff --git a/test/integration/transactions/transactions.test.ts b/test/integration/transactions/transactions.test.ts index 7e22d65d209..b0ecadecb88 100644 --- a/test/integration/transactions/transactions.test.ts +++ b/test/integration/transactions/transactions.test.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { ClientSession, type Collection, + type CommandStartedEvent, type MongoClient, MongoInvalidArgumentError, MongoNetworkError, @@ -231,8 +232,13 @@ describe('Transactions', function () { context('when completing a transaction', () => { let client: MongoClient; + let commandsStarted: CommandStartedEvent[]; beforeEach(async function () { - client = this.configuration.newClient(); + client = this.configuration.newClient(undefined, { monitorCommands: true }); + commandsStarted = []; + client.on('commandStarted', ev => { + commandsStarted.push(ev); + }); }); afterEach(async function () { @@ -260,6 +266,30 @@ describe('Transactions', function () { }) ) ); + + it( + 'commitTransaction does not override write concern on initial attempt', + { requires: { mongodb: '>=4.2.0', topology: '!single' } }, + async function () { + await client + .db('test') + .dropCollection('test') + .catch(() => null); + const collection = await client.db('test').createCollection('test'); + const session = client.startSession({ + defaultTransactionOptions: { writeConcern: { w: 1 } } + }); + session.startTransaction(); + await collection.insertOne({ x: 1 }, { session }); + await session.commitTransaction(); + + const commitTransactions = commandsStarted.filter( + x => x.commandName === 'commitTransaction' + ); + expect(commitTransactions).to.have.lengthOf(1); + expect(commitTransactions[0].command).to.have.nested.property('writeConcern.w', 1); + } + ); }); describe('TransientTransactionError', function () {