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
|
"use strict";
/*
* Library used to test that we can recover prepared transactions using the
* 'recoverFromOplogAsStandalone' flag. This can be used to test with both prepared transactions
* that have been committed and ones that are still in the prepared state.
*/
var testPrepareRecoverFromOplogAsStandalone = function(name, commitBeforeRecovery) {
load("jstests/replsets/rslib.js");
load("jstests/libs/write_concern_util.js");
load("jstests/core/txns/libs/prepare_helpers.js");
const dbName = "test";
const txnCollName = "txn_coll";
const nonTxnCollName = "non_txn_coll";
const logLevel = tojson({storage: {recovery: 3}});
const rst = new ReplSetTest({
name: name,
nodes: 2,
});
function getColl(conn, collName = nonTxnCollName) {
return conn.getDB(dbName)[collName];
}
function assertDocsInColl(conn, nums, collName) {
const results = getColl(conn, collName).find().sort({_id: 1}).toArray();
const expected = nums.map((i) => ({_id: i}));
if (!friendlyEqual(results, expected)) {
rst.dumpOplog(node, {}, 100);
}
assert.eq(results, expected, "actual (left) != expected (right)");
}
jsTestLog("Initiating as a replica set.");
// Start up the nodes as a replica set so we can add operations to the oplog.
const nodes = rst.startSet({setParameter: {logComponentVerbosity: logLevel}});
let node = nodes[0];
const secondary = nodes[1];
rst.initiate({
_id: name,
members: [{_id: 0, host: node.host}, {_id: 2, host: secondary.host, priority: 0}]
});
assert.eq(rst.getPrimary(), node);
// Create both collections with {w: majority}.
assert.commandWorked(node.getDB(dbName).runCommand({
create: nonTxnCollName,
writeConcern: {w: "majority", wtimeout: ReplSetTest.kDefaultTimeoutMS}
}));
assert.commandWorked(node.getDB(dbName).runCommand({
create: txnCollName,
writeConcern: {w: "majority", wtimeout: ReplSetTest.kDefaultTimeoutMS}
}));
jsTestLog("Beginning a transaction.");
const session = node.startSession();
const sessionDb = session.getDatabase(dbName);
const sessionColl = sessionDb.getCollection(txnCollName);
// Start the transaction and insert some documents.
session.startTransaction();
assert.commandWorked(sessionColl.insert({_id: 1234}));
assert.commandWorked(sessionColl.insert({_id: 5678}));
// Prepare the transaction.
let prepareTimestamp = PrepareHelpers.prepareTransaction(session);
// Keep node 0 the primary, but prevent it from committing any writes.
stopServerReplication(secondary);
// Issue a few non-transactional writes that will not be majority-committed.
assert.commandWorked(getColl(node).insert({_id: 3}));
assert.commandWorked(getColl(node).insert({_id: 4}));
assert.commandWorked(getColl(node).insert({_id: 5}));
assertDocsInColl(node, [3, 4, 5]);
if (commitBeforeRecovery) {
// Also commit the prepared transaction, but only on the primary.
jsTestLog("Committing the transaction (before recovery).");
assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
}
jsTestLog("Testing that on restart with the flag set we play recovery.");
node = rst.restart(node, {
noReplSet: true,
setParameter: {recoverFromOplogAsStandalone: true, logComponentVerbosity: logLevel}
});
reconnect(node);
assertDocsInColl(node, [3, 4, 5]);
// Verify that we can only see the contents of the transaction if it was committed.
const expectedDocs = commitBeforeRecovery ? [1234, 5678] : [];
assertDocsInColl(node, expectedDocs, txnCollName);
rst.stop(node);
rst.stop(secondary);
};
|