summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/index_builds_ignore_prepare_conflicts.js
blob: f5a0a64eea657dd20110c3f4fad79dcdbb619c76 (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
/**
 * Tests that index builds don't block on operations that conflict because they are in a prepared
 * state.
 *
 * @tags: [
 *   requires_document_locking,
 *   requires_replication,
 *   uses_prepare_transaction,
 *   uses_transactions,
 *   ]
 */
(function() {
    "use strict";

    load("jstests/core/txns/libs/prepare_helpers.js");  // for PrepareHelpers
    load("jstests/noPassthrough/libs/index_build.js");  // for IndexBuildTest

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

    const primary = replSetTest.getPrimary();
    const primaryDB = primary.getDB('test');

    let numDocs = 10;
    let setUp = function(coll) {
        coll.drop();
        let bulk = coll.initializeUnorderedBulkOp();
        for (let i = 0; i < numDocs; i++) {
            bulk.insert({i: i});
        }
        assert.commandWorked(bulk.execute());
    };

    /**
     * Run a background index build, and depending on the provided node, 'conn', ensure that a
     * prepared update does not introduce prepare conflicts on the index builder.
     */
    let runTest = function(conn) {
        const testDB = conn.getDB('test');

        const collName = 'index_builds_ignore_prepare_conflicts';
        const coll = primaryDB.getCollection(collName);
        setUp(coll);

        // Start and pause an index build.
        IndexBuildTest.pauseIndexBuilds(conn);
        const awaitBuild = IndexBuildTest.startIndexBuild(primary, coll.getFullName(), {i: 1});
        const opId = IndexBuildTest.waitForIndexBuildToScanCollection(testDB, collName, 'i_1');

        // This insert will block until the index build pauses and releases its exclusive lock.
        // This guarantees that the subsequent transaction can immediately acquire a lock and not
        // fail with a LockTimeout error.
        assert.commandWorked(coll.insert({i: numDocs++}));

        // Start a session and introduce a document that is in a prepared state, but should be
        // ignored by the index build, at least until the transaction commits.
        const session = primaryDB.getMongo().startSession();
        const sessionDB = session.getDatabase('test');
        const sessionColl = sessionDB.getCollection(collName);
        session.startTransaction();
        assert.commandWorked(sessionColl.update({i: 0}, {i: "prepared"}));
        // Use w:1 because the secondary will be unable to replicate the prepare while an index
        // build is running.
        const prepareTimestamp = PrepareHelpers.prepareTransaction(session, {w: 1});

        // Let the index build continue until just before it completes. Set the failpoint just
        // before the second drain, which would take lock that conflicts with the prepared
        // transaction and prevent the index build from completing entirely.
        const failPointName = "hangAfterIndexBuildFirstDrain";
        clearRawMongoProgramOutput();
        assert.commandWorked(
            conn.adminCommand({configureFailPoint: failPointName, mode: "alwaysOn"}));

        // Unpause the index build from the first failpoint so that it can resume and pause at the
        // next failpoint.
        IndexBuildTest.resumeIndexBuilds(conn);
        assert.soon(
            () => rawMongoProgramOutput().indexOf("Hanging after index build first drain") >= 0);

        // Right before the index build completes, ensure no prepare conflicts were hit.
        IndexBuildTest.assertIndexBuildCurrentOpContents(testDB, opId, (op) => {
            printjson(op);
            assert.eq(undefined, op.prepareReadConflicts);
        });

        // Because prepare uses w:1, ensure it is majority committed before committing the
        // transaction.
        PrepareHelpers.awaitMajorityCommitted(replSetTest, prepareTimestamp);

        // Commit the transaction before completing the index build, releasing locks which will
        // allow the index build to complete.
        assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));

        // Allow the index build to complete.
        assert.commandWorked(conn.adminCommand({configureFailPoint: failPointName, mode: "off"}));

        awaitBuild();
        IndexBuildTest.waitForIndexBuildToStop(testDB, collName, "i_1");

        assert.eq(numDocs, coll.count());
        assert.eq(numDocs, coll.find().itcount());
    };

    runTest(replSetTest.getPrimary());

    replSetTest.stopSet();
})();