summaryrefslogtreecommitdiff
path: root/jstests/replsets/ddl_ops_after_prepare_lock_failpoint.js
blob: 2d9a08458fbaf5fd04103b7fe1e595513b1ad295 (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
/**
 * Tests that using the "failNonIntentLocksIfWaitNeeded" failpoint allows us to immediately
 * fail DDL operations blocked behind prepare, as we know they will not be able to acquire locks
 * anyway. The DDL ops will fail here because they won't be able to get a MODE_X lock on the
 * global or database resources.
 *
 * @tags: [uses_transactions, uses_prepare_transaction]
 */

(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/libs/index_catalog_helpers.js");

const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();

const dbName = "test";
const collName = "ddl_ops_after_prepare_lock_failpoint";
const indexName = "test_index";

const primary = rst.getPrimary();
const testDB = primary.getDB(dbName);
const testColl = testDB.getCollection(collName);

// Create the collection we will be working with.
assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));

// Also build an index (on the same collection) which we will later attempt to drop.
assert.commandWorked(
    testDB.runCommand({createIndexes: collName, indexes: [{key: {"num": 1}, name: indexName}]}));

const session = primary.startSession({causalConsistency: false});
const sessionDB = session.getDatabase(dbName);
const sessionColl = sessionDB.getCollection(collName);

session.startTransaction();
assert.commandWorked(sessionColl.insert({_id: 42}));

PrepareHelpers.prepareTransaction(session);

assert.commandWorked(
    primary.adminCommand({configureFailPoint: "failNonIntentLocksIfWaitNeeded", mode: "alwaysOn"}));

/**
 * Tests that conflicting DDL ops fail immediately.
 */

// Collection names for DDL ops that will fail.
const collToDrop = collName;
const collToRenameFrom = collName;
const collToRenameTo = "rename_collection_to_fail";
const indexToCreate = "create_index_to_fail";
const indexToDrop = indexName;

let testDDLOps = () => {
    // Also attempt to delete our original collection (it is in conflict anyway, but should
    // fail to acquire the db lock in the first place).
    assert.throws(function() {
        testDB.getCollection(collToDrop).drop();
    });
    assert(testDB.getCollectionNames().includes(collToDrop));

    // Same goes for trying to rename it.
    assert.commandFailedWithCode(
        testDB.getCollection(collToRenameFrom).renameCollection(collToRenameTo),
        ErrorCodes.LockTimeout);
    assert(testDB.getCollectionNames().includes(collToRenameFrom));
    assert(!testDB.getCollectionNames().includes(collToRenameTo));

    assert.commandFailedWithCode(testDB.adminCommand({
        renameCollection: testDB.getCollection(collToRenameFrom).getFullName(),
        to: testDB.getSiblingDB('test2').getCollection(collToRenameTo).getFullName(),
    }),
                                 ErrorCodes.LockTimeout);

    // Attempt to add a new index to that collection.
    assert.commandFailedWithCode(
        testDB.runCommand(
            {createIndexes: collName, indexes: [{key: {"b": 1}, name: indexToCreate}]}),
        ErrorCodes.LockTimeout);
    assert.eq(null, IndexCatalogHelpers.findByName(testColl.getIndexes(), indexToCreate));

    // Try dropping the index we created originally. This should also fail.
    assert.commandFailedWithCode(testDB.runCommand({dropIndexes: collName, index: indexToDrop}),
                                 ErrorCodes.LockTimeout);
    assert.neq(null, IndexCatalogHelpers.findByName(testColl.getIndexes(), indexToDrop));
};

/**
 * Tests that CRUD operations on the same collection succeed.
 */

const docToInsert = {
    num: 100
};
const docToUpdateFrom = docToInsert;
const docToUpdateTo = {
    num: 101
};
const docToRemove = docToUpdateTo;

let testCRUDOps = (collConn) => {
    // Having an extra document in the collection is necessary to avoid prepare conflicts when
    // deleting adjacent documents. See SERVER-40167.
    assert.commandWorked(collConn.insert({num: 1}));

    assert.commandWorked(collConn.insert(docToInsert));
    assert.eq(100, collConn.findOne(docToInsert).num);

    // This will not encounter a prepare conflict because there is an index on "num" that
    // eliminates the need for using a collection scan.
    assert.commandWorked(collConn.update(docToUpdateFrom, docToUpdateTo));
    assert.eq(101, collConn.findOne(docToUpdateTo).num);

    assert.commandWorked(collConn.remove(docToRemove));
    assert.eq(null, collConn.findOne(docToUpdateFrom));
    assert.eq(null, collConn.findOne(docToUpdateTo));
};

// First test DDL ops (should fail).
testDDLOps();

// Then test operations outside of transactions (should succeed).
testCRUDOps(testColl);

// Also test operations as part of a transaction (should succeed).
testCRUDOps(
    primary.startSession({causalConsistency: false}).getDatabase(dbName).getCollection(collName));

assert.commandWorked(
    primary.adminCommand({configureFailPoint: "failNonIntentLocksIfWaitNeeded", mode: "off"}));

assert.commandWorked(session.abortTransaction_forTesting());
rst.stopSet();
})();