summaryrefslogtreecommitdiff
path: root/jstests/concurrency/fsm_workloads/update_array.js
blob: 192626c24308e78c51f8471cbd0ef9fea848b7d2 (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
'use strict';

/**
 * update_array.js
 *
 * Each thread does a $push or $pull on a random doc, pushing or pulling its
 * thread id. After each push or pull, the thread does a .findOne() to verify
 * that its thread id is present or absent (respectively). This is correct even
 * though other threads in the workload may be modifying the array between the
 * update and the find, because thread ids are unique.
 */

// For isMongod and supportsDocumentLevelConcurrency.
load('jstests/concurrency/fsm_workload_helpers/server_types.js');

var $config = (function() {

    var states = (function() {

        // db: explicitly passed to avoid accidentally using the global `db`
        // res: WriteResult
        // nModifiedPossibilities: array of allowed values for res.nModified
        function assertUpdateSuccess(db, res, nModifiedPossibilities) {
            assertAlways.eq(0, res.nUpserted, tojson(res));

            if (isMongod(db) && supportsDocumentLevelConcurrency(db)) {
                // Storage engines which support document-level concurrency will automatically retry
                // any operations when there are conflicts, so the update should have succeeded if
                // a matching document existed.
                assertWhenOwnColl.contains(1, nModifiedPossibilities, tojson(res));
                if (db.getMongo().writeMode() === 'commands') {
                    assertWhenOwnColl.contains(res.nModified, nModifiedPossibilities, tojson(res));
                }
            } else {
                // On storage engines that do not support document-level concurrency, it is possible
                // that the update will not update all matching documents. This can happen if
                // another thread updated a target document during a yield, triggering an
                // invalidation.
                assertWhenOwnColl.contains(res.nMatched, [0, 1], tojson(res));
                if (db.getMongo().writeMode() === 'commands') {
                    assertWhenOwnColl.contains(res.nModified, [0, 1], tojson(res));
                }
            }
        }

        function doPush(db, collName, docIndex, value) {
            var res = db[collName].update({_id: docIndex}, {$push: {arr: value}});

            // assert the update reported success
            assertUpdateSuccess(db, res, [1]);

            // find the doc and make sure it was updated
            var doc = db[collName].findOne({_id: docIndex});
            assertWhenOwnColl(function() {
                assertWhenOwnColl.neq(null, doc);
                assertWhenOwnColl(doc.hasOwnProperty('arr'),
                                  'doc should have contained a field named "arr": ' + tojson(doc));

                // If the document was invalidated during a yield, then we may not have updated
                // anything. The $push operator always modifies the matched document, so if we
                // matched something, then we must have updated it.
                if (res.nMatched > 0) {
                    assertWhenOwnColl.contains(value,
                                               doc.arr,
                                               "doc.arr doesn't contain value (" + value +
                                                   ') after $push: ' + tojson(doc.arr));
                }
            });
        }

        function doPull(db, collName, docIndex, value) {
            var res = db[collName].update({_id: docIndex}, {$pull: {arr: value}});

            // assert the update reported success
            assertUpdateSuccess(db, res, [0, 1]);

            // find the doc and make sure it was updated
            var doc = db[collName].findOne({_id: docIndex});
            assertWhenOwnColl(function() {
                assertWhenOwnColl.neq(null, doc);

                // If the document was invalidated during a yield, then we may not have updated
                // anything. If the update matched a document, then the $pull operator would have
                // removed all occurrences of 'value' from the array (meaning that there should be
                // none left).
                if (res.nMatched > 0) {
                    assertWhenOwnColl.eq(-1,
                                         doc.arr.indexOf(value),
                                         'doc.arr contains removed value (' + value +
                                             ') after $pull: ' + tojson(doc.arr));
                }
            });
        }

        return {
            push: function push(db, collName) {
                var docIndex = Random.randInt(this.numDocs);
                var value = this.tid;

                doPush(db, collName, docIndex, value);
            },

            pull: function pull(db, collName) {
                var docIndex = Random.randInt(this.numDocs);
                var value = this.tid;

                doPull(db, collName, docIndex, value);
            }
        };

    })();

    var transitions = {
        push: {push: 0.8, pull: 0.2},
        pull: {push: 0.8, pull: 0.2}
    };

    function setup(db, collName, cluster) {
        // index on 'arr', the field being updated
        assertAlways.commandWorked(db[collName].ensureIndex({arr: 1}));
        for (var i = 0; i < this.numDocs; ++i) {
            var res = db[collName].insert({_id: i, arr: []});
            assertWhenOwnColl.writeOK(res);
            assertWhenOwnColl.eq(1, res.nInserted);
        }
    }

    return {
        threadCount: 5,
        iterations: 10,
        startState: 'push',
        states: states,
        transitions: transitions,
        data: {numDocs: 10},
        setup: setup
    };

})();