summaryrefslogtreecommitdiff
path: root/jstests/core/txns/abort_transaction_thread_does_not_block_on_locks.js
blob: 5b899e73689a22dbfa6df3e59a9d1c2093403075 (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
// Tests that the asychronous thread that aborts expired transactions will not get stuck behind a
// drop command blocked on two transactions. A drop cmd requires a database exclusive lock, which
// will block behind transactions holding database intent locks. Aborting a transaction must not
// require taking further locks that would queue up behind the drop cmd's database exclusive lock
// request.
//
// @tags: [uses_transactions]
(function() {
"use strict";

const dbName = "test";
const collName = "abort_transaction_thread_does_not_block_on_locks";
const testDB = db.getSiblingDB(dbName);
const testColl = testDB[collName];
const sessionOptions = {
    causalConsistency: false
};

let dropRes = testDB.runCommand({drop: collName, writeConcern: {w: "majority"}});
if (!dropRes.ok) {
    assert.commandFailedWithCode(dropRes, ErrorCodes.NamespaceNotFound);
}

const bulk = testColl.initializeUnorderedBulkOp();
for (let i = 0; i < 4; ++i) {
    bulk.insert({_id: i});
}
assert.commandWorked(bulk.execute({w: "majority"}));

const res =
    assert.commandWorked(db.adminCommand({getParameter: 1, transactionLifetimeLimitSeconds: 1}));
const originalTransactionLifetimeLimitSeconds = res.transactionLifetimeLimitSeconds;

try {
    let transactionLifeTime = 10;
    jsTest.log("Decrease transactionLifetimeLimitSeconds to " + transactionLifeTime + " seconds.");
    assert.commandWorked(
        db.adminCommand({setParameter: 1, transactionLifetimeLimitSeconds: transactionLifeTime}));

    // Set up two transactions with IX locks and cursors.

    let session1 = db.getMongo().startSession(sessionOptions);
    let sessionDb1 = session1.getDatabase(dbName);
    let sessionColl1 = sessionDb1[collName];

    let session2 = db.getMongo().startSession(sessionOptions);
    let sessionDb2 = session2.getDatabase(dbName);
    let sessionColl2 = sessionDb2[collName];

    let firstTxnNumber = 1;
    let secondTxnNumber = 2;

    jsTest.log("Setting up first transaction with an open cursor and IX lock");
    let cursorRes1 = assert.commandWorked(sessionDb1.runCommand({
        find: collName,
        batchSize: 2,
        readConcern: {level: "snapshot"},
        txnNumber: NumberLong(firstTxnNumber),
        stmtId: NumberInt(0),
        startTransaction: true,
        autocommit: false
    }));
    assert(cursorRes1.hasOwnProperty("cursor"), tojson(cursorRes1));
    assert.neq(0, cursorRes1.cursor.id, tojson(cursorRes1));

    jsTest.log("Setting up second transaction with an open cursor and IX lock");
    let cursorRes2 = assert.commandWorked(sessionDb2.runCommand({
        find: collName,
        batchSize: 2,
        readConcern: {level: "snapshot"},
        txnNumber: NumberLong(secondTxnNumber),
        stmtId: NumberInt(0),
        startTransaction: true,
        autocommit: false
    }));
    assert(cursorRes2.hasOwnProperty("cursor"), tojson(cursorRes2));
    assert.neq(0, cursorRes2.cursor.id, tojson(cursorRes2));

    jsTest.log("Perform a drop. This will block until both transactions finish. The " +
               "transactions should expire in " + transactionLifeTime * 1.5 + " seconds or less.");
    assert.commandWorked(testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}));

    // Verify and cleanup.

    jsTest.log("Drop finished. Verifying that the transactions were aborted as expected");
    assert.commandFailedWithCode(sessionDb1.adminCommand({
        commitTransaction: 1,
        txnNumber: NumberLong(firstTxnNumber),
        stmtId: NumberInt(2),
        autocommit: false
    }),
                                 ErrorCodes.NoSuchTransaction);
    assert.commandFailedWithCode(sessionDb2.adminCommand({
        commitTransaction: 1,
        txnNumber: NumberLong(secondTxnNumber),
        stmtId: NumberInt(2),
        autocommit: false
    }),
                                 ErrorCodes.NoSuchTransaction);

    session1.endSession();
    session2.endSession();
} finally {
    assert.commandWorked(db.adminCommand({
        setParameter: 1,
        transactionLifetimeLimitSeconds: originalTransactionLifetimeLimitSeconds
    }));
}
}());