summaryrefslogtreecommitdiff
path: root/jstests/replsets/abort_in_progress_transactions_on_step_up.js
blob: 7c4f37c476958925f09a26a2f3b7306a93b3d652 (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
138
139
/**
 * Tests primary aborts in-progress transactions on stepup.
 *
 * @tags: [uses_transactions, exclude_from_large_txns]
 */
(function() {
"use strict";
load("jstests/replsets/rslib.js");  // For reconnect()
load("jstests/libs/fail_point_util.js");

function getTxnTableEntry(db) {
    let txnTableEntries = db.getSiblingDB("config")["transactions"].find().toArray();
    assert.eq(txnTableEntries.length, 1);
    return txnTableEntries[0];
}

const replTest = new ReplSetTest({
    nodes: 3,
    nodeOptions: {
        setParameter:
            {maxNumberOfTransactionOperationsInSingleOplogEntry: 1, bgSyncOplogFetcherBatchSize: 1}
    },
});

replTest.startSet();
let config = replTest.getReplSetConfig();
config.members[2].priority = 0;
// Disable primary catchup and chaining.
config.settings = {
    catchUpTimeoutMillis: 0,
    chainingAllowed: false
};
replTest.initiate(config);

setLogVerbosity(replTest.nodes, {"replication": {"verbosity": 3}});

const dbName = jsTest.name();
const collName = "coll";

const primary = replTest.nodes[0];
const testDB = primary.getDB(dbName);
const newPrimary = replTest.nodes[1];
const newTestDB = newPrimary.getDB(dbName);

testDB.dropDatabase();
assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));

// Prevent the priority: 0 node from fetching new ops so that it can vote for the new primary.
const stopReplProducerFailPoint = configureFailPoint(replTest.nodes[2], 'stopReplProducer');

jsTest.log("Stop secondary oplog replication before the last operation in the transaction.");
// The stopReplProducerOnDocument failpoint ensures that secondary stops replicating before
// applying the last operation in the transaction. This depends on the oplog fetcher batch size
// being 1.
const stopReplProducerOnDocumentFailPoint = configureFailPoint(
    newPrimary, "stopReplProducerOnDocument", {document: {"applyOps.o._id": "last in txn"}});

jsTestLog("Starting transaction");
const session = primary.startSession({causalConsistency: false});
const sessionDB = session.getDatabase(dbName);
session.startTransaction({writeConcern: {w: "majority", wtimeout: 500}});

const doc = {
    _id: "first in txn on primary " + primary
};
assert.commandWorked(sessionDB.getCollection(collName).insert(doc));
assert.commandWorked(sessionDB.getCollection(collName).insert({_id: "last in txn"}));

jsTestLog("Committing transaction but fail on replication");
let res = session.commitTransaction_forTesting();
assert.commandFailedWithCode(res, ErrorCodes.WriteConcernFailed);

// Remember the commit OpTime on primary.
let txnTableEntry = getTxnTableEntry(testDB);
assert.eq(txnTableEntry.state, "committed");
const commitOpTime = getTxnTableEntry(testDB).lastWriteOpTime;

jsTestLog("Wait for the new primary to block on fail point.");
stopReplProducerOnDocumentFailPoint.wait();

// Now the transaction should be in-progress on newPrimary.
txnTableEntry = getTxnTableEntry(newTestDB);
assert.eq(txnTableEntry.state, "inProgress");
// The startOpTime should be less than the commit optime.
assert.eq(rs.compareOpTimes(txnTableEntry.startOpTime, commitOpTime), -1);

jsTestLog("Stepping down primary via heartbeat.");
assert.commandWorked(newPrimary.adminCommand({replSetStepUp: 1}));
replTest.awaitNodesAgreeOnPrimary();
reconnect(primary);

// Make sure we won't apply the whole transaction by any chance.
jsTestLog("Wait for the new primary to stop replication after primary catch-up.");
checkLog.contains(newPrimary, "Stopping replication producer");

jsTestLog("Enable replication on the new primary so that it can finish state transition");
stopReplProducerOnDocumentFailPoint.off();

assert.eq(replTest.getPrimary(), newPrimary);
stopReplProducerFailPoint.off();
replTest.awaitReplication();

jsTestLog("The transaction has been aborted on the new primary.");
// Create a proxy session to reuse the session state of the old primary.
const newSession = new _DelegatingDriverSession(newPrimary, session);
const newSessionDB = newSession.getDatabase(dbName);
// The transaction has been aborted.
assert.commandFailedWithCode(newSessionDB.adminCommand({
    commitTransaction: 1,
    txnNumber: NumberLong(newSession.getTxnNumber_forTesting()),
    autocommit: false,
    writeConcern: {w: "majority"}
}),
                             ErrorCodes.NoSuchTransaction);

// The old primary rolls back the local committed transaction.
assert.eq(testDB.getCollection(collName).find().itcount(), 0);
assert.eq(newTestDB.getCollection(collName).find().itcount(), 0);

// The transaction table should be the same on both old and new primaries.
txnTableEntry = getTxnTableEntry(newTestDB);
assert.eq(txnTableEntry.state, "aborted");
assert(!txnTableEntry.hasOwnProperty("startOpTime"));
txnTableEntry = getTxnTableEntry(testDB);
assert.eq(txnTableEntry.state, "aborted");
assert(!txnTableEntry.hasOwnProperty("startOpTime"));

jsTestLog("Running another transaction on the new primary");
newSession.startTransaction({writeConcern: {w: 3}});
const secondDoc = {
    _id: "second-doc"
};
assert.commandWorked(newSession.getDatabase(dbName).getCollection(collName).insert(secondDoc));
assert.commandWorked(newSession.commitTransaction_forTesting());
assert.docEq(testDB.getCollection(collName).find().toArray(), [secondDoc]);
assert.docEq(newTestDB.getCollection(collName).find().toArray(), [secondDoc]);

replTest.stopSet();
})();