diff options
author | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2019-05-13 17:33:07 -0400 |
---|---|---|
committer | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2019-05-20 18:38:44 -0400 |
commit | cb7eff01f675d854d7484bd68ce298a8817d2af2 (patch) | |
tree | 27e2ccaf24b526867adf7a254f5342898af1ba53 | |
parent | c8e0cc640ba6045ab3f6bd5f192d7c533f45e0f1 (diff) | |
download | mongo-cb7eff01f675d854d7484bd68ce298a8817d2af2.tar.gz |
SERVER-41100 Update lastWriteOpTime for prepareTransaction on secondaries
-rw-r--r-- | jstests/replsets/prepare_failover_rollback_commit.js | 68 | ||||
-rw-r--r-- | src/mongo/db/transaction_participant.cpp | 11 |
2 files changed, 79 insertions, 0 deletions
diff --git a/jstests/replsets/prepare_failover_rollback_commit.js b/jstests/replsets/prepare_failover_rollback_commit.js new file mode 100644 index 00000000000..658f20c1d32 --- /dev/null +++ b/jstests/replsets/prepare_failover_rollback_commit.js @@ -0,0 +1,68 @@ +/** + * This tests that preparing a transaction successfully will update the lastWriteOpTime on + * secondaries so that the corresponding commitTransaction oplog entry has a correct prevOpTime. + * The test exercises a failover right after a prepare, so that we have to commit the transaction + * while talking to a node that was in secondary state when it was prepared. We commit that + * transaction, then roll back that commit entry, the success of which depends on the prevOpTime + * being set properly. + * + * @tags: [uses_transactions, uses_prepare_transaction] + */ +(function() { + "use strict"; + load("jstests/replsets/libs/rollback_test.js"); + load("jstests/core/txns/libs/prepare_helpers.js"); + + const dbName = "test"; + const collName = "prepare_failover_rollback_commit"; + + const rollbackTest = + new RollbackTest(collName, undefined, true /* expectPreparedTxnsDuringRollback */); + + let primary = rollbackTest.getPrimary(); + const testDB = primary.getDB(dbName); + const testColl = testDB.getCollection(collName); + + // First create the collection for all. + assert.commandWorked(testColl.insert({"a": "baseDoc"})); + + const session = primary.startSession(); + const sessionDB = session.getDatabase(dbName); + const sessionColl = sessionDB.getCollection(collName); + + session.startTransaction(); + assert.commandWorked(sessionColl.insert({"b": "transactionDoc"})); + + // Prepare a transaction. This will be replicated to the secondary. + const prepareTimestamp = PrepareHelpers.prepareTransaction(session); + + // Do a failover first, without rolling back any of the data from this test. We want the + // current secondary to become primary and inherit the prepared transaction. + rollbackTest.transitionToRollbackOperations(); + rollbackTest.transitionToSyncSourceOperationsBeforeRollback(); + rollbackTest.transitionToSyncSourceOperationsDuringRollback(); + rollbackTest.transitionToSteadyStateOperations(); + + // Now set up a rollback scenario for that new primary. + rollbackTest.transitionToRollbackOperations(); + + // Create a proxy session to reuse the session state of the old primary. + primary = rollbackTest.getPrimary(); + const newSession1 = new _DelegatingDriverSession(primary, session); + + // Commit the transaction on this primary. We expect the commit to roll back. + assert.commandWorked(PrepareHelpers.commitTransaction(newSession1, prepareTimestamp)); + + rollbackTest.transitionToSyncSourceOperationsBeforeRollback(); + rollbackTest.transitionToSyncSourceOperationsDuringRollback(); + rollbackTest.transitionToSteadyStateOperations(); + + // Create a proxy session to reuse the session state of the old primary. + primary = rollbackTest.getPrimary(); + const newSession2 = new _DelegatingDriverSession(primary, session); + + // Commit the transaction for all to conclude the test. + assert.commandWorked(PrepareHelpers.commitTransaction(newSession2, prepareTimestamp)); + + rollbackTest.stop(); +})(); diff --git a/src/mongo/db/transaction_participant.cpp b/src/mongo/db/transaction_participant.cpp index 0260a0509f5..8a19b66dfad 100644 --- a/src/mongo/db/transaction_participant.cpp +++ b/src/mongo/db/transaction_participant.cpp @@ -1103,6 +1103,13 @@ Timestamp TransactionParticipant::Participant::prepareTransaction( const auto ticks = opCtx->getServiceContext()->getTickSource()->getTicks(); stdx::lock_guard<Client> lk(*opCtx->getClient()); o(lk).transactionMetricsObserver.onPrepare(ServerTransactionsMetrics::get(opCtx), ticks); + + // Ensure the lastWriteOpTime is set. This is needed so that we can correctly assign the + // prevOpTime for commit and abort oplog entries if a failover happens after the prepare. + // This value is updated in _registerCacheUpdateOnCommit, but only on primaries. We + // update the lastWriteOpTime here so that it is also available to secondaries. We can + // count on it to persist since we never invalidate prepared transactions. + o(lk).lastWriteOpTime = prepareOplogSlot; } if (MONGO_FAIL_POINT(hangAfterSettingPrepareStartTime)) { @@ -1297,6 +1304,10 @@ void TransactionParticipant::Participant::commitPreparedTransaction( invariant(commitOplogEntryOpTime); } + // We must have a lastWriteOpTime set, as that will be used for the prevOpTime on the oplog + // entry. + invariant(!o().lastWriteOpTime.isNull()); + // If commitOplogEntryOpTime is a nullopt, then we grab the OpTime from the commitOplogSlot // which will only be set if we are primary. Otherwise, the commitOplogEntryOpTime must have // been passed in during secondary oplog application. |