summaryrefslogtreecommitdiff
path: root/jstests/concurrency/fsm_workloads/yield.js
blob: 0ef6aa9b1a0b2b8168e7b6324110c893eeb9ee08 (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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
'use strict';

load('jstests/concurrency/fsm_workload_helpers/server_types.js');  // for isMongod

/**
 * yield.js
 *
 * Designed to execute queries and make them yield as much as possible while also updating and
 * removing documents that they operate on.
 */
var $config = (function() {

    var data = {
        // Number of docs to insert at the beginning.
        nDocs: 200,
        // Batch size of queries to introduce more saving and restoring of states.
        batchSize: 3,
        // The words that can be found in the collection.
        words: ['these', 'are', 'test', 'words'],
        /*
         * Helper function to advance a cursor, and verify that the documents that come out are
         * what we'd expect.
         */
        advanceCursor: function advanceCursor(cursor, verifier) {
            // Keep track of the previous doc in case the verifier is trying to verify a sorted
            // query.
            var prevDoc = null;
            var doc = null;
            while (cursor.hasNext()) {
                prevDoc = doc;
                doc = cursor.next();
                assertAlways(verifier(doc, prevDoc),
                             'Verifier failed!\nQuery: ' + tojson(cursor._query) + '\n' +
                                 'Query plan: ' + tojson(cursor.explain()) + '\n' +
                                 'Previous doc: ' + tojson(prevDoc) + '\n' +
                                 'This doc: ' + tojson(doc));
            }
            assertAlways.eq(cursor.itcount(), 0);
        },
        /*
         * Many subclasses will want different behavior in the update stage. To change what types
         * of updates happen, they can simply override this function to return which update doc
         * the update state should use for the update query.
         */
        genUpdateDoc: function genUpdateDoc() {
            var newVal = Random.randInt(this.nDocs);
            return {
                $set: {a: newVal}
            };
        }
    };

    var states = {
        /*
         * Update a random document from the collection.
         */
        update: function update(db, collName) {
            var id = Random.randInt(this.nDocs);
            var randDoc = db[collName].findOne({_id: id});
            if (randDoc === null) {
                return;
            }
            var updateDoc = this.genUpdateDoc();
            assertAlways.writeOK(db[collName].update(randDoc, updateDoc));
        },

        /*
         * Remove a random document from the collection, then re-insert one to prevent losing
         * documents.
         */
        remove: function remove(db, collName) {
            var id = Random.randInt(this.nDocs);
            var doc = db[collName].findOne({_id: id});
            if (doc !== null) {
                var res = db[collName].remove({_id: id});
                assertAlways.writeOK(res);
                if (res.nRemoved > 0) {
                    assertAlways.writeOK(db[collName].insert(doc));
                }
            }
        },

        /*
         * Issue a query that will potentially yield and resume while documents are being updated.
         * Subclasses will implement this differently
         */
        query: function collScan(db, collName) {
            var nMatches = 100;
            var cursor = db[collName].find({a: {$lt: nMatches}}).batchSize(this.batchSize);
            var collScanVerifier = function collScanVerifier(doc, prevDoc) {
                return doc.a < nMatches;
            };

            this.advanceCursor(cursor, collScanVerifier);
        }
    };

    /*
     * Visual of FSM:
     *
     *            _
     *           / \
     *           V /
     *          remove
     *          ^    ^
     *         /      \
     *        v       v
     * -->update<---->query
     *     ^ \           ^ \
     *     \_/           \_/
     *
     */
    var transitions = {
        update: {update: 0.334, remove: 0.333, query: 0.333},
        remove: {update: 0.333, remove: 0.334, query: 0.333},
        query: {update: 0.333, remove: 0.333, query: 0.334}
    };

    /*
     * Sets up the indices, sets a failpoint and lowers some yielding parameters to encourage
     * more yielding, and inserts the documents to be used.
     */
    function setup(db, collName, cluster) {
        // Enable this failpoint to trigger more yields. In MMAPV1, if a record fetch is about to
        // page fault, the query will yield. This failpoint will mock page faulting on such
        // fetches every other time.

        cluster.executeOnMongodNodes(function enableFailPoint(db) {
            assertAlways.commandWorked(
                db.adminCommand({configureFailPoint: 'recordNeedsFetchFail', mode: 'alwaysOn'}));
        });

        // Lower the following parameters to force even more yields.
        cluster.executeOnMongodNodes(function lowerYieldParams(db) {
            assertAlways.commandWorked(
                db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 5}));
            assertAlways.commandWorked(
                db.adminCommand({setParameter: 1, internalQueryExecYieldPeriodMS: 1}));
        });
        // Set up some data to query.
        var N = this.nDocs;
        var bulk = db[collName].initializeUnorderedBulkOp();
        for (var i = 0; i < N; i++) {
            // Give each doc some word of text
            var word = this.words[i % this.words.length];
            bulk.find({_id: i}).upsert().updateOne(
                {$set: {a: i, b: N - i, c: i, d: N - i, yield_text: word}});
        }
        assertAlways.writeOK(bulk.execute());
    }

    /*
     * Reset parameters and disable failpoint.
     */
    function teardown(db, collName, cluster) {
        cluster.executeOnMongodNodes(function disableFailPoint(db) {
            assertAlways.commandWorked(
                db.adminCommand({configureFailPoint: 'recordNeedsFetchFail', mode: 'off'}));
        });
        cluster.executeOnMongodNodes(function resetYieldParams(db) {
            assertAlways.commandWorked(
                db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 128}));
            assertAlways.commandWorked(
                db.adminCommand({setParameter: 1, internalQueryExecYieldPeriodMS: 10}));
        });
    }

    return {
        threadCount: 5,
        iterations: 50,
        startState: 'update',
        states: states,
        transitions: transitions,
        setup: setup,
        teardown: teardown,
        data: data
    };

})();