-
Type: Bug
-
Resolution: Fixed
-
Priority: Minor - P4
-
Affects Version/s: None
-
Component/s: Replication
-
Fully Compatible
-
ALL
-
Repl 2019-04-08
Currently, if prepareTransaction fails midway and enters the abort guard, abortActiveTransaction is called. At this point, it is possible that the transaction has already transitioned into the kPrepared state, but the transactions metrics observer has not yet been called to increment the current and total number of prepared transactions. At this point, abortActiveTransaction will decrement the currentPrepared while incrementing the TotalPreparedThenAborted metrics. Therefore, it is possible to leave the metrics in a bad state if preparing a transactions fails.
Example test that will pass (incorrectly):
/** * Tests that failing while preparing a transaction leaves the server transaction metrics in an illegal state. * */ (function() { "use strict"; load("jstests/core/txns/libs/prepare_helpers.js"); const dbName = "test"; const collName = "fail_prepare_transaction"; const testDB = db.getSiblingDB(dbName); const testColl = testDB.getCollection(collName); testColl.drop({writeConcern: {w: "majority"}}); assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}})); const session = testDB.getMongo().startSession({causalConsistency: false}); const sessionDB = session.getDatabase(dbName); const sessionColl = sessionDB.getCollection(collName); const doc1 = {_id: 1, x: 1}; session.startTransaction({readConcern: {level: 'snapshot'}}); assert.commandWorked(sessionColl.insert(doc1)); // Assuming prepareTransaction was to fail and enter the abortGuard. assert.commandFailed(session.getDatabase('admin').adminCommand({prepareTransaction: 1})); let newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); // This breaks the invariant that metrics cannot be negative. assert.eq(newStatus.transactions["currentPrepared"], -1); assert.eq(newStatus.transactions["totalPreparedThenAborted"], 1); assert.eq(newStatus.transactions["totalPreparedThenCommitted"], 0); assert.eq(newStatus.transactions["totalPrepared"], 0); session.endSession(); }());
We should either delay transitioning to kPrepared until the abortGuard is dismissed or increment the current and total prepared metrics earlier.