summaryrefslogtreecommitdiff
path: root/jstests/concurrency/fsm_workloads/snapshot_read_catalog_operations.js
blob: d959a57394f264f15c162537d2186f0278fbe50a (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
'use strict';

/**
 * Perform snapshot reads that span a find and a getmore concurrently with CRUD operations. The
 * snapshot reads and CRUD operations will all contend for locks on db and collName. Since the
 * snapshot read does not release its locks until the transaction is committed, it is expected that
 * once the read has begun, catalog operations with conflicting locks will block until the read is
 * finished. Additionally, index operations running concurrently with the snapshot read may cause
 * the read to fail with a SnapshotUnavailable error.
 * @tags: [uses_transactions]
 */

load('jstests/concurrency/fsm_workload_helpers/snapshot_read_utils.js');
var $config = (function() {
    const data = {numIds: 100, numDocsToInsertPerThread: 5, valueToBeInserted: 1, batchSize: 50};

    const states = {
        init: function init(db, collName) {
            this.session = db.getMongo().startSession({causalConsistency: false});
            this.sessionDb = this.session.getDatabase(db.getName());
            this.txnNumber = 0;
            this.stmtId = 0;
        },

        snapshotFind: function snapshotFind(db, collName) {
            // The ascending snapshot find order is more likely to include documents read, updated,
            // and deleted by readDocs, updateDocs, and deleteDocs.
            // The descending snapshot find order is more likely to include documents inserted by
            // insertDocs.
            const sortOptions = [true, false];
            const sortByAscending = sortOptions[Random.randInt(2)];
            const readErrorCodes = [
                ErrorCodes.NoSuchTransaction,
                ErrorCodes.SnapshotUnavailable,
                ErrorCodes.LockTimeout
            ];
            const commitTransactionErrorCodes = readErrorCodes;
            doSnapshotFind(sortByAscending, collName, this, readErrorCodes);
            if (this.cursorId) {
                doSnapshotGetMore(collName, this, readErrorCodes, commitTransactionErrorCodes);
            }
        },

        insertDocs: function insertDocs(db, collName) {
            for (let i = 0; i < this.numDocsToInsertPerThread; ++i) {
                const res = db[collName].insert({value: this.valueToBeInserted});
                assertWhenOwnColl.writeOK(res);
                assertWhenOwnColl.eq(1, res.nInserted);
            }
        },

        updateDocs: function updateDocs(db, collName) {
            for (let i = 0; i < this.numIds; ++i) {
                try {
                    db[collName].update({_id: i}, {$inc: {value: 1}});
                } catch (e) {
                    // dropIndex can cause queries to throw if these queries yield.
                    assertAlways.contains(e.code,
                                          [ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
                                          'unexpected error code: ' + e.code + ': ' + e.message);
                }
            }
        },

        readDocs: function readDocs(db, collName) {
            for (let i = 0; i < this.numIds; ++i) {
                try {
                    db[collName].findOne({_id: i});
                } catch (e) {
                    // dropIndex can cause queries to throw if these queries yield.
                    assertAlways.contains(e.code,
                                          [ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
                                          'unexpected error code: ' + e.code + ': ' + e.message);
                }
            }
        },

        deleteDocs: function deleteDocs(db, collName) {
            let indexToDelete = Math.floor(Math.random() * this.numIds);
            try {
                db[collName].deleteOne({_id: indexToDelete});
            } catch (e) {
                // dropIndex can cause queries to throw if these queries yield.
                assertAlways.contains(e.code,
                                      [ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
                                      'unexpected error code: ' + e.code + ': ' + e.message);
            }
        },

        createIndex: function createIndex(db, collName) {
            db[collName].createIndex({value: 1}, {background: true});
        },

        dropIndex: function dropIndex(db, collName) {
            db[collName].dropIndex({value: 1});
        }
    };

    const transitions = {
        init: {
            snapshotFind: 0.15,
            insertDocs: 0.14,
            updateDocs: 0.14,
            deleteDocs: 0.14,
            readDocs: 0.14,
            createIndex: 0.15,
            dropIndex: 0.14,
        },
        snapshotFind: {
            insertDocs: 0.17,
            updateDocs: 0.16,
            deleteDocs: 0.17,
            readDocs: 0.16,
            createIndex: 0.17,
            dropIndex: 0.17,
        },
        insertDocs: {snapshotFind: 1.0},
        updateDocs: {snapshotFind: 1.0},
        readDocs: {snapshotFind: 1.0},
        deleteDocs: {snapshotFind: 1.0},
        createIndex: {snapshotFind: 1.0},
        dropIndex: {snapshotFind: 1.0},
    };

    function setup(db, collName, cluster) {
        assertWhenOwnColl.commandWorked(db.runCommand({create: collName}));
        for (let i = 0; i < this.numIds; ++i) {
            const res = db[collName].insert({_id: i, value: this.valueToBeInserted});
            assert.writeOK(res);
            assert.eq(1, res.nInserted);
        }
    }

    function teardown(db, collName, cluster) {
    }

    const skip = function skip(cluster) {
        // TODO(SERVER-34570) remove isSharded() check once transactions are supported in sharded
        // environments.
        if (cluster.isSharded() || cluster.isStandalone()) {
            return {skip: true, msg: 'only runs in a replica set.'};
        }
        return {skip: false};
    };

    return {
        threadCount: 5,
        iterations: 10,
        startState: 'init',
        states: states,
        transitions: transitions,
        setup: setup,
        teardown: teardown,
        data: data,
        skip: skip
    };

})();