Skip to content

Commit a7d1d43

Browse files
authored
fix(NODE-6436): only force majority write concern on commitTransaction retry (#4284)
1 parent 3d5bd51 commit a7d1d43

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

Diff for: src/sessions.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ export class ClientSession
124124
owner?: symbol | AbstractCursor;
125125
defaultTransactionOptions: TransactionOptions;
126126
transaction: Transaction;
127+
/** @internal
128+
* Keeps track of whether or not the current transaction has attempted to be committed. Is
129+
* 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 */
130+
commitAttempted?: boolean;
127131
/** @internal */
128132
[kServerSession]: ServerSession | null;
129133
/** @internal */
@@ -417,6 +421,7 @@ export class ClientSession
417421
);
418422
}
419423

424+
this.commitAttempted = false;
420425
// increment txnNumber
421426
this.incrementTransactionNumber();
422427
// create transaction state
@@ -474,7 +479,7 @@ export class ClientSession
474479
WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
475480
}
476481

477-
if (this.transaction.state === TxnState.TRANSACTION_COMMITTED) {
482+
if (this.transaction.state === TxnState.TRANSACTION_COMMITTED || this.commitAttempted) {
478483
WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
479484
}
480485

@@ -494,16 +499,25 @@ export class ClientSession
494499

495500
try {
496501
await executeOperation(this.client, operation);
502+
this.commitAttempted = undefined;
497503
return;
498504
} catch (firstCommitError) {
505+
this.commitAttempted = true;
499506
if (firstCommitError instanceof MongoError && isRetryableWriteError(firstCommitError)) {
500507
// SPEC-1185: apply majority write concern when retrying commitTransaction
501508
WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
502509
// per txns spec, must unpin session in this case
503510
this.unpin({ force: true });
504511

505512
try {
506-
await executeOperation(this.client, operation);
513+
await executeOperation(
514+
this.client,
515+
new RunAdminCommandOperation(command, {
516+
session: this,
517+
readPreference: ReadPreference.primary,
518+
bypassPinningCheck: true
519+
})
520+
);
507521
return;
508522
} catch (retryCommitError) {
509523
// If the retry failed, we process that error instead of the original

Diff for: test/integration/transactions/transactions.test.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { expect } from 'chai';
33
import {
44
ClientSession,
55
type Collection,
6+
type CommandStartedEvent,
67
type MongoClient,
78
MongoInvalidArgumentError,
89
MongoNetworkError,
@@ -231,8 +232,13 @@ describe('Transactions', function () {
231232

232233
context('when completing a transaction', () => {
233234
let client: MongoClient;
235+
let commandsStarted: CommandStartedEvent[];
234236
beforeEach(async function () {
235-
client = this.configuration.newClient();
237+
client = this.configuration.newClient(undefined, { monitorCommands: true });
238+
commandsStarted = [];
239+
client.on('commandStarted', ev => {
240+
commandsStarted.push(ev);
241+
});
236242
});
237243

238244
afterEach(async function () {
@@ -260,6 +266,30 @@ describe('Transactions', function () {
260266
})
261267
)
262268
);
269+
270+
it(
271+
'commitTransaction does not override write concern on initial attempt',
272+
{ requires: { mongodb: '>=4.2.0', topology: '!single' } },
273+
async function () {
274+
await client
275+
.db('test')
276+
.dropCollection('test')
277+
.catch(() => null);
278+
const collection = await client.db('test').createCollection('test');
279+
const session = client.startSession({
280+
defaultTransactionOptions: { writeConcern: { w: 1 } }
281+
});
282+
session.startTransaction();
283+
await collection.insertOne({ x: 1 }, { session });
284+
await session.commitTransaction();
285+
286+
const commitTransactions = commandsStarted.filter(
287+
x => x.commandName === 'commitTransaction'
288+
);
289+
expect(commitTransactions).to.have.lengthOf(1);
290+
expect(commitTransactions[0].command).to.have.nested.property('writeConcern.w', 1);
291+
}
292+
);
263293
});
264294

265295
describe('TransientTransactionError', function () {

0 commit comments

Comments
 (0)