summaryrefslogtreecommitdiff
path: root/jstests/replsets/recover_committed_aborted_prepared_transactions.js
blob: c4499ce87b4e8b0a48bcaff56db19aeb5918e87b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * 1. Test that rollback can successfully recover committed prepared transactions that were prepared
 * before the stable timestamp and committed between the stable timestamp and the common point.
 * 2. Test that rollback can successfully recover aborted prepared transactions that were prepared
 * and aborted between the stable timestamp and the common point.
 *
 * @tags: [
 *   uses_prepare_transaction,
 *   uses_transactions,
 * ]
 */
(function() {
"use strict";

load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/replsets/libs/rollback_test.js");

const dbName = "test";
const collName = "recover_committed_aborted_prepared_transactions";

const rollbackTest = new RollbackTest(dbName);
let primary = rollbackTest.getPrimary();

// Create collection we're using beforehand.
let testDB = primary.getDB(dbName);
let testColl = testDB.getCollection(collName);

assert.commandWorked(testDB.runCommand({create: collName}));

// Start two different sessions on the primary.
let session1 = primary.startSession({causalConsistency: false});
const sessionID1 = session1.getSessionId();
const session2 = primary.startSession({causalConsistency: false});

let sessionDB1 = session1.getDatabase(dbName);
let sessionColl1 = sessionDB1.getCollection(collName);

const sessionDB2 = session2.getDatabase(dbName);
const sessionColl2 = sessionDB2.getCollection(collName);

assert.commandWorked(sessionColl1.insert({_id: 1}));

rollbackTest.awaitLastOpCommitted();

// Prepare a transaction on the first session which will be committed eventually.
session1.startTransaction();
assert.commandWorked(sessionColl1.insert({_id: 2}));
const prepareTimestamp = PrepareHelpers.prepareTransaction(session1);

// Prevent the stable timestamp from moving beyond the following prepared transactions so
// that when we replay the oplog from the stable timestamp, we correctly recover them.
assert.commandWorked(
    primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'alwaysOn'}));

// The following transactions will be prepared before the common point, so they must be in
// prepare after rollback recovery.

// Prepare another transaction on the second session which will be aborted.
session2.startTransaction();
assert.commandWorked(sessionColl2.insert({_id: 3}));
const prepareTimestamp2 = PrepareHelpers.prepareTransaction(session2, {w: 1});

// Commit the first transaction.
assert.commandWorked(PrepareHelpers.commitTransaction(session1, prepareTimestamp));

// Abort the second transaction.
assert.commandWorked(session2.abortTransaction_forTesting());

// Check that we have two transactions in the transactions table.
assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);

// The following write will be rolled back.
rollbackTest.transitionToRollbackOperations();
assert.commandWorked(testColl.insert({_id: 4}));

rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
rollbackTest.transitionToSyncSourceOperationsDuringRollback();

try {
    rollbackTest.transitionToSteadyStateOperations();
} finally {
    assert.commandWorked(
        primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'off'}));
}

// Make sure there are two transactions in the transactions table.
assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);

// Make sure we can see the first two writes and the insert from the first prepared transaction.
// Make sure we cannot see the insert from the second prepared transaction or the writes after
// transitionToRollbackOperations.
assert.sameMembers(testColl.find().toArray(), [{_id: 1}, {_id: 2}]);
assert.sameMembers(sessionColl1.find().toArray(), [{_id: 1}, {_id: 2}]);

assert.eq(testColl.count(), 2);
assert.eq(sessionColl1.count(), 2);

// Get the correct members after the topology changes.
primary = rollbackTest.getPrimary();
testDB = primary.getDB(dbName);
testColl = testDB.getCollection(collName);
const rst = rollbackTest.getTestFixture();
const secondaries = rst.getSecondaries();

// Make sure we can successfully run a prepared transaction on the same first session after
// going through rollback. This ensures that the session state has properly been restored.
session1 = PrepareHelpers.createSessionWithGivenId(primary, sessionID1, {causalConsistency: false});
sessionDB1 = session1.getDatabase(dbName);
sessionColl1 = sessionDB1.getCollection(collName);
// The next transaction on this session should have a txnNumber of 1. We explicitly set this
// since createSessionWithGivenId does not restore the current txnNumber in the shell.
session1.setTxnNumber_forTesting(1);

session1.startTransaction();
assert.commandWorked(sessionColl1.insert({_id: 5}));
const prepareTimestamp3 = PrepareHelpers.prepareTransaction(session1);
// Make sure we can successfully retry the commitTransaction command after rollback.
assert.commandWorked(PrepareHelpers.commitTransaction(session1, prepareTimestamp3));

session1.startTransaction();
assert.commandWorked(sessionColl1.insert({_id: 6}));
PrepareHelpers.prepareTransaction(session1);
assert.commandWorked(session1.abortTransaction_forTesting());
// Retrying the abortTransaction command should fail with a NoSuchTransaction error.
assert.commandFailedWithCode(sessionDB1.adminCommand({
    abortTransaction: 1,
    txnNumber: NumberLong(session1.getTxnNumber_forTesting()),
    autocommit: false,
}),
                             ErrorCodes.NoSuchTransaction);

// Make sure we can see the insert after committing the prepared transaction.
assert.sameMembers(testColl.find().toArray(), [{_id: 1}, {_id: 2}, {_id: 5}]);
assert.eq(testColl.count(), 3);

rollbackTest.stop();
}());