summaryrefslogtreecommitdiff
path: root/jstests/replsets/kill_reads_with_prepare_conflicts_during_step_down.js
blob: 237fc1880fc3de2f9f677a21b12cb5e6f32768fa (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
/*
 * Tests that read operations that encounter prepare conflicts are killed during
 * stepdown to prevent deadlocks between prepare conflicts and state transitions.
 *
 * @tags: [uses_transactions, uses_prepare_transaction]
 */

(function() {
    "use strict";

    load("jstests/core/txns/libs/prepare_helpers.js");
    load("jstests/libs/check_log.js");

    // Start one of the nodes with priority: 0 to avoid elections.
    var rst = new ReplSetTest({nodes: [{}, {rsConfig: {priority: 0}}]});
    rst.startSet();
    rst.initiate();

    let primary = rst.getPrimary();

    const dbName = "test";
    const collName = "kill_reads_with_prepare_conflicts_during_step_down";

    const primaryDB = primary.getDB(dbName);
    // Used to make sure that the correct amount of operations were killed on this node
    // during stepdown.
    const primaryAdmin = primary.getDB("admin");
    const primaryColl = primaryDB[collName];

    let session = primary.startSession();
    const sessionID = session.getSessionId();
    let sessionDB = session.getDatabase(dbName);
    const sessionColl = sessionDB.getCollection(collName);

    assert.commandWorked(primaryAdmin.adminCommand(
        {configureFailPoint: "WTPrintPrepareConflictLog", mode: "alwaysOn"}));

    // Insert a document that we will later modify in a transaction.
    assert.commandWorked(primaryColl.insert({_id: 1}));

    jsTestLog("Start a transaction and prepare it");
    session.startTransaction();
    assert.commandWorked(sessionColl.update({_id: 1}, {_id: 1, a: 1}));
    const prepareTimestamp = PrepareHelpers.prepareTransaction(session);

    TestData.dbName = dbName;
    TestData.collName = collName;

    const readBlockedOnPrepareConflictThread = startParallelShell(() => {
        const parallelTestDB = db.getSiblingDB(TestData.dbName);
        const parallelTestCollName = TestData.collName;

        // Advance the clusterTime with another insert.
        let res = assert.commandWorked(parallelTestDB.runCommand(
            {insert: parallelTestCollName, documents: [{advanceClusterTime: 1}]}));
        assert(res.hasOwnProperty("$clusterTime"), res);
        assert(res.$clusterTime.hasOwnProperty("clusterTime"), res);
        const clusterTime = res.$clusterTime.clusterTime;
        jsTestLog("Using afterClusterTime: " + clusterTime);

        // The following read should block on the prepared transaction since it will be
        // reading a conflicting document using an afterClusterTime later than the
        // prepareTimestamp.
        assert.commandFailedWithCode(parallelTestDB.runCommand({
            find: parallelTestCollName,
            filter: {_id: 1},
            readConcern: {afterClusterTime: clusterTime}
        }),
                                     ErrorCodes.InterruptedDueToReplStateChange);
    }, primary.port);

    jsTestLog("Waiting for failpoint");
    checkLog.contains(primary, "WTPrintPrepareConflictLog fail point enabled");

    // Once we have confirmed that the find command has hit a prepare conflict, we can perform
    // a step down.
    jsTestLog("Stepping down primary");
    assert.commandWorked(
        primaryAdmin.adminCommand({replSetStepDown: 60 * 10 /* 10 minutes */, force: true}));

    readBlockedOnPrepareConflictThread();

    rst.waitForState(primary, ReplSetTest.State.SECONDARY);

    // Validate that the read operation got killed during step down.
    let replMetrics =
        assert.commandWorked(primaryAdmin.adminCommand({serverStatus: 1})).metrics.repl;
    assert.eq(replMetrics.stepDown.userOperationsKilled, 1);

    // Allow the primary to be re-elected, and wait for it.
    assert.commandWorked(primaryAdmin.adminCommand({replSetFreeze: 0}));
    primary = rst.getPrimary();

    // Make sure we can successfully commit the prepared transaction.
    jsTestLog("Restoring shell session state");
    session = PrepareHelpers.createSessionWithGivenId(primary, sessionID);
    sessionDB = session.getDatabase(dbName);
    // The transaction on this session should have a txnNumber of 0. We explicitly set this
    // since createSessionWithGivenId does not restore the current txnNumber in the shell.
    session.setTxnNumber_forTesting(0);
    const txnNumber = session.getTxnNumber_forTesting();

    jsTestLog("Committing transaction");
    // Commit the transaction.
    assert.commandWorked(sessionDB.adminCommand({
        commitTransaction: 1,
        commitTimestamp: prepareTimestamp,
        txnNumber: NumberLong(txnNumber),
        autocommit: false,
    }));

    rst.stopSet();
})();