summaryrefslogtreecommitdiff
path: root/jstests/replsets/transactions_during_step_down.js
blob: bcc2a87a8a2a2cebaa04fe0a20159d964419b59d (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
/**
 * Test the following behavior for a transaction on step down.
 *  1) Active transactional operations (like read and write) are killed and the transaction is
 * aborted, but the connection not closed.
 *  2) Inactive transaction is aborted.
 * @tags: [uses_transactions]
 */
(function() {
"use strict";

load("jstests/libs/curop_helpers.js");  // for waitForCurOpByFailPoint().

const testName = "txnsDuringStepDown";
const dbName = testName;
const collName = "testcoll";

const rst = new ReplSetTest({nodes: [{}, {rsConfig: {priority: 0}}]});
rst.startSet();
rst.initiate();

var primary = rst.getPrimary();
var db = primary.getDB(dbName);
var primaryAdmin = primary.getDB("admin");
var primaryColl = db[collName];
var collNss = primaryColl.getFullName();

jsTestLog("Writing data to collection.");
assert.commandWorked(primaryColl.insert({_id: 'readOp'}, {"writeConcern": {"w": 2}}));

TestData.dbName = dbName;
TestData.collName = collName;
TestData.skipRetryOnNetworkError = true;

function txnFunc() {
    jsTestLog("Starting a new transaction.");
    const session = db.getMongo().startSession();
    const sessionDb = session.getDatabase(TestData.dbName);
    const sessionColl = sessionDb[TestData.collName];
    session.startTransaction({writeConcern: {w: "majority"}});
    print(TestData.cmd);
    eval(TestData.cmd);

    // Validate that the connection is not closed on step down.
    assert.commandWorked(db.adminCommand({ping: 1}));
}

function runStepDown() {
    jsTestLog("Making primary step down.");
    assert.commandWorked(primaryAdmin.runCommand({"replSetStepDown": 30 * 60, "force": true}));

    // Wait until the primary transitioned to SECONDARY state.
    rst.waitForState(primary, ReplSetTest.State.SECONDARY);

    jsTestLog("Validating data.");
    assert.docEq([{_id: 'readOp'}], primaryColl.find().toArray());

    jsTestLog("Making old primary eligible to be re-elected.");
    assert.commandWorked(primaryAdmin.runCommand({replSetFreeze: 0}));
    rst.getPrimary();
}

function testTxnFailsWithCode({
    op,
    failPoint: failPoint = 'hangAfterPreallocateSnapshot',
    nss: nss = dbName + '.$cmd',
    preOp: preOp = ''
}) {
    jsTestLog("Enabling failPoint '" + failPoint + "' on primary.");
    assert.commandWorked(primary.adminCommand({
        configureFailPoint: failPoint,
        data: {shouldContinueOnInterrupt: true},
        mode: "alwaysOn"
    }));

    // Start transaction.
    TestData.cmd =
        preOp + `assert.commandFailedWithCode(${op}, ErrorCodes.InterruptedDueToReplStateChange);`;
    const waitForTxnShell = startParallelShell(txnFunc, primary.port);

    jsTestLog("Waiting for primary to reach failPoint '" + failPoint + "'.");
    waitForCurOpByFailPoint(primaryAdmin, nss, failPoint);

    // Call step down & validate data.
    runStepDown();

    // Wait for transaction shell to join.
    waitForTxnShell();

    // Disable fail point.
    assert.commandWorked(primaryAdmin.runCommand({configureFailPoint: failPoint, mode: 'off'}));
}

function testAbortOrCommitTxnFailsWithCode(params) {
    params["preOp"] = `sessionColl.insert({_id: 'abortOrCommitTxnOp'});`;
    params["nss"] = "admin.$cmd";
    testTxnFailsWithCode(params);
}

jsTestLog("Testing stepdown during read transaction.");
testTxnFailsWithCode({op: "sessionDb.runCommand({find: '" + collName + "', batchSize: 1})"});

jsTestLog("Testing stepdown during write transaction.");
testTxnFailsWithCode({op: "sessionColl.insert({_id: 'writeOp'})"});

jsTestLog("Testing stepdown during read-write transaction.");
testTxnFailsWithCode({
    op: "sessionDb.runCommand({findAndModify: '" + collName +
        "', query: {_id: 'readOp'}, remove: true})"
});

jsTestLog("Testing stepdown during commit transaction.");
testAbortOrCommitTxnFailsWithCode(
    {failPoint: "hangBeforeCommitingTxn", op: "session.commitTransaction_forTesting()"});

jsTestLog("Testing stepdown during running transaction in inactive state.");
// Do not start the transaction in parallel shell because when the parallel
// shell work is done, implicit call to "endSessions" and "abortTransaction"
// cmds are made. So, during step down we might not have any running
jsTestLog("Starting a new transaction.");
const session = db.getMongo().startSession();
const sessionDb = session.getDatabase(TestData.dbName);
const sessionColl = sessionDb[TestData.collName];
session.startTransaction({writeConcern: {w: "majority"}});
assert.commandWorked(sessionColl.insert({_id: 'inactiveTxnOp'}));

// Call step down & validate data.
runStepDown();

// Even though the transaction was aborted by the stepdown, we must still update the shell's
// transaction state to aborted.
assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction);

rst.stopSet();
})();