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
|
/**
* Test that a node crashes if it tries to roll back a 'commit' oplog entry using refetch-based
* rollback. The tests mimics the standard PSA rollback setup by using a PSS replica set where the
* last node effectively acts as an arbiter without formally being one (this is necessary because
* we disallow the 'prepareTransaction' command in sets with arbiters).
*
* @tags: [uses_transactions, uses_prepare_transaction]
*/
TestData.skipCheckDBHashes = true;
(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/replsets/libs/rollback_test.js");
const dbName = "test";
const collName = "rollback_via_refetch_commit_transaction";
// Provide RollbackTest with custom ReplSetTest so we can set forceRollbackViaRefetch.
const rst = new ReplSetTest({
name: collName,
nodes: 3,
useBridge: true,
nodeOptions: {setParameter: "forceRollbackViaRefetch=true"}
});
rst.startSet();
const config = rst.getReplSetConfig();
config.members[2].priority = 0;
config.settings = {
chainingAllowed: false
};
rst.initiate(config);
const primaryNode = rst.getPrimary();
// Create collection that exists on the sync source and rollback node.
assert.commandWorked(
primaryNode.getDB(dbName).runCommand({create: collName, writeConcern: {w: 2}}));
// Issue a 'prepareTransaction' command just to the current primary.
const session = primaryNode.getDB(dbName).getMongo().startSession({causalConsistency: false});
const sessionDB = session.getDatabase(dbName);
const sessionColl = sessionDB.getCollection(collName);
session.startTransaction();
assert.commandWorked(sessionColl.insert({"prepare": "entry"}));
const prepareTimestamp = PrepareHelpers.prepareTransaction(session);
const rollbackTest = new RollbackTest(collName, rst);
// Stop replication from the current primary ("rollbackNode").
const rollbackNode = rollbackTest.transitionToRollbackOperations();
PrepareHelpers.commitTransaction(session, prepareTimestamp);
// Step down current primary and elect a node that lacks the commit.
rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
// Verify the old primary crashes trying to roll back.
clearRawMongoProgramOutput();
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
jsTestLog("Waiting for crash");
assert.soon(function() {
try {
rollbackNode.getDB("local").runCommand({ping: 1});
} catch (e) {
return true;
}
return false;
}, "Node did not fassert", ReplSetTest.kDefaultTimeoutMS);
// Let the ReplSetTest know the old primary is down.
rst.stop(rst.getNodeId(rollbackNode), undefined, {allowedExitCode: MongoRunner.EXIT_ABRUPT});
const msg = RegExp("Can't roll back this command yet: ");
assert.soon(function() {
return rawMongoProgramOutput().match(msg);
}, "Node did not fail to roll back entry.");
// Transaction is still in prepared state and validation will be blocked, so skip it.
rst.stopSet(undefined, undefined, {skipValidation: true});
}());
|