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();
})();
|