summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/auto_retry_on_network_error.js
blob: 5667b1c0a5549a765a7e847e35bc3bec90a8212f (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
/**
 * Tests that the auto_retry_on_network_error.js override automatically retries commands on network
 * errors for commands run under a session.
 * @tags: [requires_replication]
 */
(function() {
    "use strict";

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

    if (!RetryableWritesUtil.storageEngineSupportsRetryableWrites(jsTest.options().storageEngine)) {
        jsTestLog("Retryable writes are not supported, skipping test");
        return;
    }

    load("jstests/libs/override_methods/auto_retry_on_network_error.js");
    load("jstests/replsets/rslib.js");

    function stepDownPrimary(rst) {
        // Since we expect the mongo shell's connection to get severed as a result of running the
        // "replSetStepDown" command, we temporarily disable the retry on network error behavior.
        TestData.skipRetryOnNetworkError = true;
        try {
            const primary = rst.getPrimary();
            const error = assert.throws(function() {
                const res = primary.adminCommand({replSetStepDown: 1, force: true});
                print("replSetStepDown did not throw exception but returned: " + tojson(res));
            });
            assert(isNetworkError(error),
                   "replSetStepDown did not disconnect client; failed with " + tojson(error));

            // We use the reconnect() function to run a command against the former primary that
            // acquires the global lock to ensure that it has finished stepping down and has
            // therefore closed all of its client connections. This ensures commands sent on other
            // connections to the former primary trigger a network error rather than potentially
            // returning a "not master" error while the server is in the midst of closing client
            // connections.
            reconnect(primary);
        } finally {
            TestData.skipRetryOnNetworkError = false;
        }
    }

    const rst = new ReplSetTest({nodes: 1});
    rst.startSet();

    // awaitLastStableRecoveryTimestamp runs an 'appendOplogNote' command which is not retryable.
    rst.initiateWithAnyNodeAsPrimary(
        null, "replSetInitiate", {doNotWaitForStableRecoveryTimestamp: true});

    const dbName = "test";
    const collName = "auto_retry";

    // The override requires the connection to be run under a session. Use the replica set URL to
    // allow automatic re-targeting of the primary on NotMaster errors.
    const db = new Mongo(rst.getURL()).startSession({retryWrites: true}).getDatabase(dbName);

    // Commands with no stepdowns should work as normal.
    assert.commandWorked(db.runCommand({ping: 1}));
    assert.commandWorked(db.runCommandWithMetadata({ping: 1}, {}).commandReply);

    // Read commands are automatically retried on network errors.
    stepDownPrimary(rst);
    assert.commandWorked(db.runCommand({find: collName}));

    stepDownPrimary(rst);
    assert.commandWorked(db.runCommandWithMetadata({find: collName}, {}).commandReply);

    // Retryable write commands that can be retried succeed.
    stepDownPrimary(rst);
    assert.writeOK(db[collName].insert({x: 1}));

    stepDownPrimary(rst);
    assert.commandWorked(db.runCommandWithMetadata({
                               insert: collName,
                               documents: [{x: 2}, {x: 3}],
                               txnNumber: NumberLong(10),
                               lsid: {id: UUID()}
                           },
                                                   {})
                             .commandReply);

    // Retryable write commands that cannot be retried (i.e. no transaction number, no session id,
    // or are unordered) throw.
    stepDownPrimary(rst);
    assert.throws(function() {
        db.runCommand({insert: collName, documents: [{x: 1}, {x: 2}], ordered: false});
    });

    // The previous command shouldn't have been retried, so run a command to successfully re-target
    // the primary, so the connection to it can be closed.
    assert.commandWorked(db.runCommandWithMetadata({ping: 1}, {}).commandReply);

    stepDownPrimary(rst);
    assert.throws(function() {
        db.runCommandWithMetadata({insert: collName, documents: [{x: 1}, {x: 2}], ordered: false},
                                  {});
    });

    // getMore commands can't be retried because we won't know whether the cursor was advanced or
    // not.
    let cursorId = assert.commandWorked(db.runCommand({find: collName, batchSize: 0})).cursor.id;
    stepDownPrimary(rst);
    assert.throws(function() {
        db.runCommand({getMore: cursorId, collection: collName});
    });

    cursorId = assert.commandWorked(db.runCommand({find: collName, batchSize: 0})).cursor.id;
    stepDownPrimary(rst);
    assert.throws(function() {
        db.runCommandWithMetadata({getMore: cursorId, collection: collName}, {});
    });

    rst.stopSet();
})();