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

/**
 * Creates several bank accounts. On each iteration, each thread:
 *  - chooses two accounts and amount of money being transfer
 *  - or checks the balance of each account
 *
 * @tags: [uses_transactions, assumes_snapshot_transactions]
 */

// For withTxnAndAutoRetry.
load('jstests/concurrency/fsm_workload_helpers/auto_retry_transaction.js');

var $config = (function() {
    function computeTotalOfAllBalances(documents) {
        return documents.reduce((total, account) => total + account.balance, 0);
    }

    var states = (function() {
        function getAllDocuments(session, collection, numDocs, txnHelperOptions) {
            let documents;
            withTxnAndAutoRetry(session, () => {
                documents = collection.find().toArray();

                assertWhenOwnColl.eq(numDocs, documents.length, () => tojson(documents));
            }, txnHelperOptions);
            return documents;
        }

        function init(db, collName) {
            this.session = db.getMongo().startSession({causalConsistency: true});
        }

        function checkMoneyBalance(db, collName) {
            const collection = this.session.getDatabase(db.getName()).getCollection(collName);
            const documents = getAllDocuments(this.session, collection, this.numAccounts, {
                retryOnKilledSession: this.retryOnKilledSession
            });
            assertWhenOwnColl.eq(this.numAccounts * this.initialValue,
                                 computeTotalOfAllBalances(documents),
                                 () => tojson(documents));
        }

        function transferMoney(db, collName) {
            const transferFrom = Random.randInt(this.numAccounts);
            let transferTo = Random.randInt(this.numAccounts);
            while (transferFrom === transferTo) {
                transferTo = Random.randInt(this.numAccounts);
            }

            // We make 'transferAmount' non-zero in order to guarantee that the documents matched by
            // the update operations are modified.
            const transferAmount = Random.randInt(this.initialValue / 10) + 1;

            const collection = this.session.getDatabase(db.getName()).getCollection(collName);
            withTxnAndAutoRetry(this.session, () => {
                let res = collection.runCommand('update', {
                    updates: [{q: {_id: transferFrom}, u: {$inc: {balance: -transferAmount}}}],
                });

                assertAlways.commandWorked(res);
                assertWhenOwnColl.eq(res.n, 1, () => tojson(res));
                assertWhenOwnColl.eq(res.nModified, 1, () => tojson(res));

                res = collection.runCommand('update', {
                    updates: [{q: {_id: transferTo}, u: {$inc: {balance: transferAmount}}}],
                });

                assertAlways.commandWorked(res);
                assertWhenOwnColl.eq(res.n, 1, () => tojson(res));
                assertWhenOwnColl.eq(res.nModified, 1, () => tojson(res));
            }, {retryOnKilledSession: this.retryOnKilledSession});
        }

        return {init: init, transferMoney: transferMoney, checkMoneyBalance: checkMoneyBalance};
    })();

    function setup(db, collName, cluster) {
        assertWhenOwnColl.commandWorked(db.runCommand({create: collName}));

        const bulk = db[collName].initializeUnorderedBulkOp();
        for (let i = 0; i < this.numAccounts; ++i) {
            bulk.insert({_id: i, balance: this.initialValue});
        }

        const res = bulk.execute({w: 'majority'});
        assertWhenOwnColl.commandWorked(res);
        assertWhenOwnColl.eq(this.numAccounts, res.nInserted);

        if (cluster.isSharded()) {
            // Advance each router's cluster time to be >= the time of the writes, so the first
            // global snapshots chosen by each is guaranteed to include the inserted documents.
            cluster.synchronizeMongosClusterTimes();
        }
    }

    function teardown(db, collName, cluster) {
        const documents = db[collName].find().toArray();
        assertWhenOwnColl.eq(this.numAccounts * this.initialValue,
                             computeTotalOfAllBalances(documents),
                             () => tojson(documents));
    }

    var transitions = {
        init: {transferMoney: 1},
        transferMoney: {transferMoney: 0.9, checkMoneyBalance: 0.1},
        checkMoneyBalance: {transferMoney: 1}
    };

    return {
        threadCount: 10,
        iterations: 100,
        startState: 'init',
        states: states,
        transitions: transitions,
        data: {numAccounts: 20, initialValue: 2000, retryOnKilledSession: false},
        setup: setup,
        teardown: teardown
    };
})();