[SERVER-46238] Race between commitTransaction and transaction expiration leads to invariant Created: 18/Feb/20  Updated: 29/Oct/23  Resolved: 25/Feb/20

Status: Closed
Project: Core Server
Component/s: Replication
Affects Version/s: 4.0.16
Fix Version/s: 4.0.17

Type: Bug Priority: Major - P3
Reporter: Tess Avitabile (Inactive) Assignee: Jason Chan
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
is related to SERVER-51598 Add new test suites that test transac... Closed
Backwards Compatibility: Fully Compatible
Operating System: ALL
Steps To Reproduce:

Add a sleep to the commitTransaction command:

diff --git a/src/mongo/db/commands/txn_cmds.cpp b/src/mongo/db/commands/txn_cmds.cpp
index 62fabeb627..9b1ed18870 100644
--- a/src/mongo/db/commands/txn_cmds.cpp
+++ b/src/mongo/db/commands/txn_cmds.cpp
@@ -80,6 +80,8 @@ public:
         uassert(
             ErrorCodes::CommandFailed, "commitTransaction must be run within a session", session);
 
+        opCtx->sleepFor(Milliseconds(2000));
+
         // commitTransaction is retryable.
         if (session->transactionIsCommitted()) {
             // We set the client last op to the last optime observed by the system to ensure that

Then run the following test:

(function() {
"use strict";
 
var replTest = new ReplSetTest({nodes: 1});
replTest.startSet();
replTest.initiate();
 
var primary = replTest.getPrimary();
assert.commandWorked(primary.adminCommand({setParameter: 1, transactionLifetimeLimitSeconds: 1}));
 
const session = primary.getDB("test").getMongo().startSession();
const sessionDb = session.getDatabase("test");
assert.commandWorked(sessionDb.createCollection("c"));
 
session.startTransaction();
assert.commandWorked(sessionDb.c.insert({a: 1}));
assert.commandWorked(sessionDb.adminCommand({commitTransaction: 1, writeConcern: {w: "majority"}}));
session.commitTransaction();
 
replTest.stopSet();
})();

Sprint: Repl 2020-03-09
Participants:
Case:

 Description   

The following race leads to invariant failure:

The startPeriodicThreadToAbortExpiredTransactions thread kills the commitTransaction operation here. It then marks the transaction state as aborted here.

The commitTransaction operation gets interrupted and triggers an abort-guard here. However, since the transaction state was already marked as aborted, we skip the destruction of the WriteUnitOfWork here. This means that the commitTransaction operation is still in a WriteUnitOfWork when it waits for writeConcern, triggering this invariant.

This only occurs on 4.0, due to a refactor on 4.2, where transaction state can only be modified by the thread that checked out the session.



 Comments   
Comment by Githook User [ 25/Feb/20 ]

Author:

{'username': 'jasonjhchan', 'name': 'Jason Chan', 'email': 'jason.chan@mongodb.com'}

Message: SERVER-46238 Transaction will abort properly when expiring in the middle of commitTransaction
Branch: v4.0
https://github.com/mongodb/mongo/commit/ad4fc77306b08a76cf3ec19e249e811b566dd464

Generated at Thu Feb 08 05:10:54 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.