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
|
/**
* Tests that prepared transactions are correctly rolled-back.
*
* @tags: [
* uses_prepare_transaction,
* uses_transactions,
* ]
*/
(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/replsets/libs/rollback_test.js");
load("jstests/replsets/libs/rollback_files.js");
load("jstests/libs/uuid_util.js");
const rollbackTest = new RollbackTest();
const rollbackNode = rollbackTest.getPrimary();
const testDB = rollbackNode.getDB("test");
const collName = "rollback_prepare_transaction";
const testColl = testDB.getCollection(collName);
// We perform some operations on the collection aside from starting and preparing a transaction
// in order to cause the count diff computed by replication to be non-zero.
assert.commandWorked(testColl.insert({_id: "a"}));
// Start two separate sessions for running transactions. On 'session1', we will run a prepared
// transaction whose commit operation gets rolled back, and on 'session2', we will run a
// prepared transaction whose prepare operation gets rolled back.
const session1 = rollbackNode.startSession();
const session1DB = session1.getDatabase(testDB.getName());
const session1Coll = session1DB.getCollection(collName);
const session2 = rollbackNode.startSession();
const session2DB = session2.getDatabase(testDB.getName());
const session2Coll = session2DB.getCollection(collName);
// Prepare a transaction whose commit operation will be rolled back.
session1.startTransaction();
assert.commandWorked(session1Coll.insert({_id: "t2_a"}));
assert.commandWorked(session1Coll.insert({_id: "t2_b"}));
assert.commandWorked(session1Coll.insert({_id: "t2_c"}));
let prepareTs = PrepareHelpers.prepareTransaction(session1);
rollbackTest.transitionToRollbackOperations();
// The following operations will be rolled-back.
assert.commandWorked(testColl.insert({_id: "b"}));
session2.startTransaction();
assert.commandWorked(session2Coll.insert({_id: "t1"}));
// Use w: 1 to simulate a prepare that will not become majority-committed.
PrepareHelpers.prepareTransaction(session2, {w: 1});
// Commit the transaction that was prepared before the common point.
PrepareHelpers.commitTransaction(session1, prepareTs);
// This is not exactly correct, but characterizes the current behavior of fastcount, which
// includes the prepared but uncommitted transaction in the collection count.
assert.eq(6, testColl.count());
// Check the visible documents.
assert.sameMembers([{_id: "a"}, {_id: "b"}, {_id: "t2_a"}, {_id: "t2_b"}, {_id: "t2_c"}],
testColl.find().toArray());
rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
// Skip consistency checks so they don't conflict with the prepared transaction.
rollbackTest.transitionToSteadyStateOperations({skipDataConsistencyChecks: true});
// Both the regular insert and prepared insert should be rolled-back.
assert.sameMembers([{_id: "a"}], testColl.find().toArray());
// Confirm that the rollback wrote deleted documents to a file.
const replTest = rollbackTest.getTestFixture();
const expectedDocs = [{_id: "b"}, {_id: "t2_a"}, {_id: "t2_b"}, {_id: "t2_c"}];
const uuid = getUUIDFromListCollections(testDB, collName);
checkRollbackFiles(replTest.getDbPath(rollbackNode), testColl.getFullName(), uuid, expectedDocs);
let adminDB = rollbackTest.getPrimary().getDB("admin");
// Since we rolled back the prepared transaction on session2, retrying the prepareTransaction
// command on this session should fail with a NoSuchTransaction error.
assert.commandFailedWithCode(adminDB.adminCommand({
prepareTransaction: 1,
lsid: session2.getSessionId(),
txnNumber: session2.getTxnNumber_forTesting(),
autocommit: false
}),
ErrorCodes.NoSuchTransaction);
// Allow the test to complete by aborting the left over prepared transaction.
jsTestLog("Aborting the prepared transaction on session " + tojson(session1.getSessionId()));
assert.commandWorked(adminDB.adminCommand({
abortTransaction: 1,
lsid: session1.getSessionId(),
txnNumber: session1.getTxnNumber_forTesting(),
autocommit: false
}));
rollbackTest.stop();
})();
|