summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/temporarily_unavailable_error_query_exec.js
blob: 277cf0b745385d82cee434ed9c426e47fec88535 (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
/**
 * Validates TemporarilyUnavailableException handling in query execution. Complements the
 * temporarily_unavailable_error.js noPassthrough test which validates behaviour outside of query
 * execution.
 *
 * @tags: [
 *   # Exclude in-memory engine, rollbacks due to pinned cache content relying on eviction.
 *   requires_journaling,
 *   requires_persistence,
 *   requires_replication,
 *   requires_wiredtiger,
 * ]
 */
(function() {
"use strict";

const maxRetries = 3;

const replSet = new ReplSetTest({
    nodes: 1,
    nodeOptions: {
        wiredTigerCacheSizeGB: 1,
        setParameter: {
            logComponentVerbosity: tojson({control: 1, write: 1}),
            enableTemporarilyUnavailableExceptions: true,
            // Lower these values from the defaults to speed up the test.
            temporarilyUnavailableMaxRetries: maxRetries,
            temporarilyUnavailableBackoffBaseMs: 10,
        }
    },
});
replSet.startSet();
replSet.initiate();
const db = replSet.getPrimary().getDB("test");

// Insert a large document that should pin at least tens of MB of dirty data in WiredTiger. This
// is adapted from the reproducer in the SERVER-61909 ticket description.
assert.commandWorked(db.c.createIndex({x: "text"}));
let doc = {a: 1, x: []};
for (let j = 0; j < 200000; j++) {
    doc.x.push("" + Math.random() + Math.random());
}
assert.commandWorked(db.c.insertOne(doc));

// Shrink the WiredTiger cache so as to reliably get a TemporarilyUnavailableException.
assert.commandWorked(
    db.adminCommand({setParameter: 1, "wiredTigerEngineRuntimeConfig": "cache_size=32M"}));

function temporarilyUnavailableNonTransaction(op) {
    jsTestLog("Temporarily unavailable error on non-transactional " + op);

    const serverStatusBefore = db.serverStatus();

    assert(op === "delete" || op === "update");

    const ret = op === "delete" ? db.c.remove({a: 1}) : db.c.update({a: 1}, {a: 2});

    assert.eq(ret["nRemoved"], 0);
    assert.commandFailedWithCode(ret, ErrorCodes.TemporarilyUnavailable, ret);

    const serverStatusAfter = db.serverStatus();
    // temporarilyUnavailableErrors is incremented by maxRetries + 1, because the last time the
    // exception is not retried.
    assert.gte(serverStatusAfter.metrics.operation.temporarilyUnavailableErrors,
               serverStatusBefore.metrics.operation.temporarilyUnavailableErrors + maxRetries + 1);
    assert.gte(serverStatusAfter.metrics.operation.temporarilyUnavailableErrorsEscaped,
               serverStatusBefore.metrics.operation.temporarilyUnavailableErrorsEscaped + 1);
}

function temporarilyUnavailableInTransactionIsConvertedToWriteConflict(op) {
    jsTestLog("Temporarily unavailable error on transactional " + op);

    assert(op === "delete" || op === "update");

    // Inside a transaction, TemporarilyUnavailable errors should be converted to
    // WriteConflicts and tagged as TransientTransactionErrors.
    const serverStatusBefore = db.serverStatus();
    const session = db.getMongo().startSession();
    session.startTransaction();
    const sessionDB = session.getDatabase("test");
    const ret = op === "delete" ? sessionDB.c.remove({a: 1}) : sessionDB.c.update({a: 1}, {a: 2});

    assert.commandFailedWithCode(ret, ErrorCodes.WriteConflict, ret);
    assert(ret.hasOwnProperty("errorLabels"), ret);
    assert.contains("TransientTransactionError", ret.errorLabels, ret);
    session.abortTransaction();

    const serverStatusAfter = db.serverStatus();
    assert.gt(
        serverStatusAfter.metrics.operation.temporarilyUnavailableErrorsConvertedToWriteConflict,
        serverStatusBefore.metrics.operation.temporarilyUnavailableErrorsConvertedToWriteConflict);
    assert.eq(serverStatusAfter.metrics.operation.temporarilyUnavailableErrors,
              serverStatusBefore.metrics.operation.temporarilyUnavailableErrors);
    assert.eq(serverStatusAfter.metrics.operation.temporarilyUnavailableErrorsEscaped,
              serverStatusBefore.metrics.operation.temporarilyUnavailableErrorsEscaped);
}

for (const op of ["delete", "update"]) {
    temporarilyUnavailableNonTransaction(op);
    temporarilyUnavailableInTransactionIsConvertedToWriteConflict(op);
}

replSet.stopSet();
})();