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
121
122
123
124
125
|
/**
* Tests that the mongo shell retries exactly once on retryable errors.
*
* @tags: [requires_replication]
*/
(function() {
"use strict";
const rst = new ReplSetTest({nodes: 2});
rst.startSet();
rst.initiate();
const dbName = "test";
const collName = jsTest.name();
const rsConn = new Mongo(rst.getURL());
const db = rsConn.startSession({retryWrites: true}).getDatabase(dbName);
// We configure the mongo shell to log its retry attempts so there are more diagnostics
// available in case this test ever fails.
TestData.logRetryAttempts = true;
/**
* The testCommandIsRetried() function serves as the fixture for writing test cases which run
* commands against the server and assert that the mongo shell retries them correctly.
*
* The 'testFn' parameter is a function that performs an arbitrary number of operations against
* the database. The command requests that the mongo shell attempts to send to the server
* (including any command requests which are retried) are then specified as the sole argument to
* the 'assertFn' parameter.
*
* The testFn(enableCapture, disableCapture) function can also selectively turn on and off the
* capturing of command requests by calling the functions it receives for its first and second
* parameters, respectively.
*/
function testCommandIsRetried(testFn, assertFn) {
const mongoRunCommandOriginal = Mongo.prototype.runCommand;
const cmdObjsSeen = [];
let shouldCaptureCmdObjs = true;
Mongo.prototype.runCommand = function runCommandSpy(dbName, cmdObj, options) {
if (shouldCaptureCmdObjs) {
cmdObjsSeen.push(cmdObj);
}
return mongoRunCommandOriginal.apply(this, arguments);
};
try {
assert.doesNotThrow(() => testFn(
() => {
shouldCaptureCmdObjs = true;
},
() => {
shouldCaptureCmdObjs = false;
}));
} finally {
Mongo.prototype.runCommand = mongoRunCommandOriginal;
}
if (cmdObjsSeen.length === 0) {
throw new Error("Mongo.prototype.runCommand() was never called: " + testFn.toString());
}
assertFn(cmdObjsSeen);
}
testCommandIsRetried(
function testInsertRetriedOnWriteConcernError(enableCapture, disableCapture) {
disableCapture();
const secondary = rst.getSecondary();
secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "alwaysOn"});
try {
enableCapture();
const res = db[collName].insert({}, {writeConcern: {w: 2, wtimeout: 1000}});
assert.commandFailedWithCode(res, ErrorCodes.WriteConcernFailed);
disableCapture();
} finally {
// We disable the failpoint in a finally block to prevent a misleading fassert()
// message from being logged by the secondary when it is shut down with the
// failpoint enabled.
secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "off"});
}
},
function assertInsertRetriedExactlyOnce(cmdObjsSeen) {
assert.eq(2, cmdObjsSeen.length, () => tojson(cmdObjsSeen));
assert(cmdObjsSeen.every(cmdObj => Object.keys(cmdObj)[0] === "insert"),
() => "expected both attempts to be insert requests: " + tojson(cmdObjsSeen));
assert.eq(
cmdObjsSeen[0], cmdObjsSeen[1], "command request changed between retry attempts");
});
testCommandIsRetried(
function testUpdateRetriedOnRetryableCommandError(enableCapture, disableCapture) {
disableCapture();
const primary = rst.getPrimary();
primary.adminCommand({
configureFailPoint: "onPrimaryTransactionalWrite",
data: {
closeConnection: false,
failBeforeCommitExceptionCode: ErrorCodes.InterruptedDueToReplStateChange
},
mode: {times: 1}
});
enableCapture();
const res = db[collName].update({}, {$set: {a: 1}});
assert.commandWorked(res);
disableCapture();
primary.adminCommand({configureFailPoint: "onPrimaryTransactionalWrite", mode: "off"});
},
function assertUpdateRetriedExactlyOnce(cmdObjsSeen) {
assert.eq(2, cmdObjsSeen.length, () => tojson(cmdObjsSeen));
assert(cmdObjsSeen.every(cmdObj => Object.keys(cmdObj)[0] === "update"),
() => "expected both attempts to be update requests: " + tojson(cmdObjsSeen));
assert.eq(
cmdObjsSeen[0], cmdObjsSeen[1], "command request changed between retry attempts");
});
rst.stopSet();
})();
|