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
|
/**
* Tests prepared transactions can survive failover and commit on a new primary.
*
* @tags: [uses_transactions, uses_prepare_transaction]
*/
(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/replsets/rslib.js"); // For reconnect()
const replTest = new ReplSetTest({nodes: 2});
replTest.startSet();
replTest.initiate();
const dbName = jsTest.name();
const collName = "coll";
const otherDbName = dbName + "_other";
function testTransactionsWithFailover(doWork, stepDown, postCommit) {
const primary = replTest.getPrimary();
const newPrimary = replTest.getSecondary();
const testDB = primary.getDB(dbName);
testDB.dropDatabase();
testDB.getSiblingDB(otherDbName).dropDatabase();
assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
jsTestLog("Starting transaction");
const session = primary.startSession({causalConsistency: false});
session.startTransaction({writeConcern: {w: "majority"}});
doWork(primary, session);
jsTestLog("Putting transaction into prepare");
const prepareTimestamp = PrepareHelpers.prepareTransaction(session);
replTest.awaitReplication();
stepDown();
reconnect(primary);
jsTestLog("Waiting for the other node to run for election and become primary");
assert.eq(replTest.getPrimary(), newPrimary);
jsTestLog("Creating an unrelated collection");
// Application of an unrelated DDL command needs a strong lock on secondary. Make sure
// the prepared transactions have yielded their locks on secondary.
assert.commandWorked(newPrimary.getDB(otherDbName).runCommand({create: collName}));
replTest.awaitReplication();
jsTestLog("Dropping the collection in use cannot acquire the lock");
assert.commandFailedWithCode(
newPrimary.getDB(testDB).runCommand({drop: collName, maxTimeMS: 1000}),
ErrorCodes.MaxTimeMSExpired);
jsTestLog("Committing transaction on the new primary");
// Create a proxy session to reuse the session state of the old primary.
const newSession = new _DelegatingDriverSession(newPrimary, session);
assert.commandWorked(PrepareHelpers.commitTransaction(newSession, prepareTimestamp));
replTest.awaitReplication();
postCommit(primary, newPrimary);
jsTestLog("Running another transaction on the new primary");
const secondSession = newPrimary.startSession({causalConsistency: false});
secondSession.startTransaction({writeConcern: {w: "majority"}});
assert.commandWorked(
secondSession.getDatabase(dbName).getCollection(collName).insert({_id: "second-doc"}));
assert.commandWorked(secondSession.commitTransaction_forTesting());
// Unfreeze the original primary so that it can stand for election again for the next test.
assert.commandWorked(primary.adminCommand({replSetFreeze: 0}));
}
function doInsert(primary, session) {
const doc = {_id: "txn on primary " + primary};
jsTestLog("Inserting a document in a transaction.");
assert.commandWorked(session.getDatabase(dbName).getCollection(collName).insert(doc));
}
function postInsert(primary, newPrimary) {
const doc = {_id: "txn on primary " + primary};
assert.docEq(doc, primary.getDB(dbName).getCollection(collName).findOne());
assert.docEq(doc, newPrimary.getDB(dbName).getCollection(collName).findOne());
}
function doInsertTextSearch(primary, session) {
// Create an index outside of the transaction.
assert.commandWorked(primary.getDB(dbName).getCollection(collName).createIndex({text: "text"}));
// Do the followings in a transaction.
jsTestLog("Inserting a document in a transaction.");
assert.commandWorked(
session.getDatabase(dbName).getCollection(collName).insert({text: "text"}));
// Text search will recursively acquire the global lock. This tests that yielding
// recursively held locks works on step down.
jsTestLog("Doing a text search in a transaction.");
assert.eq(1,
session.getDatabase(dbName)
.getCollection(collName)
.find({$text: {$search: "text"}})
.itcount());
}
function postInsertTextSearch(primary, newPrimary) {
assert.eq(
1,
primary.getDB(dbName).getCollection(collName).find({$text: {$search: "text"}}).itcount());
assert.eq(1,
newPrimary.getDB(dbName)
.getCollection(collName)
.find({$text: {$search: "text"}})
.itcount());
}
function stepDownViaHeartbeat() {
jsTestLog("Stepping down primary via heartbeat");
replTest.stepUp(replTest.getSecondary());
}
function stepDownViaCommand() {
jsTestLog("Stepping down primary via command");
assert.commandWorked(replTest.getPrimary().adminCommand({replSetStepDown: 10}));
}
testTransactionsWithFailover(doInsert, stepDownViaHeartbeat, postInsert);
testTransactionsWithFailover(doInsert, stepDownViaCommand, postInsert);
testTransactionsWithFailover(doInsertTextSearch, stepDownViaHeartbeat, postInsertTextSearch);
testTransactionsWithFailover(doInsertTextSearch, stepDownViaCommand, postInsertTextSearch);
replTest.stopSet();
})();
|