summaryrefslogtreecommitdiff
path: root/jstests/libs/override_methods/retry_writes_at_least_once.js
blob: f0562c958d18856ffe0fdcb5bf6e5889b703cc87 (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
/**
 * Overrides Mongo.prototype.runCommand and Mongo.prototype.runCommandWithMetadata to retry all
 * retryable writes at least once, randomly more than that, regardless of the outcome of the
 * command. Returns the result of the latest attempt.
 */
(function() {
    "use strict";

    load("jstests/libs/retryable_writes_util.js");

    Random.setRandomSeed();

    const kExtraRetryProbability = 0.2;

    // Store a session to access ServerSession#canRetryWrites.
    let _serverSession;

    const mongoRunCommandOriginal = Mongo.prototype.runCommand;
    const mongoRunCommandWithMetadataOriginal = Mongo.prototype.runCommandWithMetadata;

    Mongo.prototype.runCommand = function runCommand(dbName, cmdObj, options) {
        if (typeof _serverSession === "undefined") {
            _serverSession = this.startSession()._serverSession;
        }

        return runWithRetries(this, cmdObj, mongoRunCommandOriginal, arguments);
    };

    Mongo.prototype.runCommandWithMetadata = function runCommandWithMetadata(
        dbName, metadata, cmdObj) {
        if (typeof _serverSession === "undefined") {
            _serverSession = this.startSession()._serverSession;
        }

        return runWithRetries(this, cmdObj, mongoRunCommandWithMetadataOriginal, arguments);
    };

    function runWithRetries(mongo, cmdObj, clientFunction, clientFunctionArguments) {
        let cmdName = Object.keys(cmdObj)[0];

        // If the command is in a wrapped form, then we look for the actual command object
        // inside the query/$query object.
        if (cmdName === "query" || cmdName === "$query") {
            cmdObj = cmdObj[cmdName];
            cmdName = Object.keys(cmdObj)[0];
        }

        const isRetryableWriteCmd = RetryableWritesUtil.isRetryableWriteCmdName(cmdName);
        const canRetryWrites = _serverSession.canRetryWrites(cmdObj);

        let res = clientFunction.apply(mongo, clientFunctionArguments);

        if (isRetryableWriteCmd && canRetryWrites) {
            let retryAttempt = 1;
            do {
                print("*** Retry attempt: " + retryAttempt + ", for command: " + cmdName +
                      " with txnNumber: " + tojson(cmdObj.txnNumber) + ", and lsid: " +
                      tojson(cmdObj.lsid));
                ++retryAttempt;
                res = clientFunction.apply(mongo, clientFunctionArguments);
            } while (Random.rand() <= kExtraRetryProbability);
        }

        return res;
    }

    const startParallelShellOriginal = startParallelShell;

    startParallelShell = function(jsCode, port, noConnect) {
        let newCode;
        const overridesFile = "jstests/libs/override_methods/retry_writes_at_least_once.js";
        if (typeof(jsCode) === "function") {
            // Load the override file and immediately invoke the supplied function.
            newCode = `load("${overridesFile}"); (${jsCode})();`;
        } else {
            newCode = `load("${overridesFile}"); ${jsCode};`;
        }

        return startParallelShellOriginal(newCode, port, noConnect);
    };
})();