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 transaction API handles commit errors correctly.
*/
(function() {
"use strict";
load("jstests/libs/fail_point_util.js");
const kDbName = "testDb";
const kCollName = "testColl";
function makeSingleInsertTxn(doc) {
return [{
dbName: kDbName,
command: {
insert: kCollName,
documents: [doc],
}
}];
}
function runTxn(conn, commandInfos) {
return conn.adminCommand({testInternalTransactions: 1, commandInfos: commandInfos});
}
const st = new ShardingTest({config: 1, shards: 1});
const shardPrimary = st.rs0.getPrimary();
// Set up the test collection.
assert.commandWorked(st.s.getDB(kDbName)[kCollName].insert([{_id: 0}]));
//
// Error codes where the API should retry and eventually commit the transaction, either by retrying
// commit until it succeeds or retrying the entire transaction until it succeeds. Fail commands 10
// times to exhaust internal retries at layers below the transaction API.
//
// Retryable error. Note this error is not a NotPrimary error so it won't be rewritten by mongos.
let commitFailPoint =
configureFailPoint(shardPrimary,
"failCommand",
{
errorCode: ErrorCodes.ReadConcernMajorityNotAvailableYet,
failCommands: ["commitTransaction"],
failInternalCommands: true,
},
{times: 10});
let res = assert.commandWorked(runTxn(st.s, makeSingleInsertTxn({_id: 1})));
commitFailPoint.off();
// No command error with a retryable write concern error.
commitFailPoint = configureFailPoint(
shardPrimary,
"failCommand",
{
writeConcernError:
{code: NumberInt(ErrorCodes.ReadConcernMajorityNotAvailableYet), errmsg: "foo"},
failCommands: ["commitTransaction"],
failInternalCommands: true,
},
{times: 10});
res = assert.commandWorked(runTxn(st.s, makeSingleInsertTxn({_id: 2})));
commitFailPoint.off();
//
// Error codes where the API should not retry.
//
// Non-transient commit error with a non-retryable write concern error.
commitFailPoint = configureFailPoint(shardPrimary,
"failCommand",
{
errorCode: ErrorCodes.InternalError,
failCommands: ["commitTransaction"],
failInternalCommands: true,
},
{times: 10});
res = assert.commandFailedWithCode(runTxn(st.s, makeSingleInsertTxn({_id: 3})),
ErrorCodes.InternalError);
commitFailPoint.off();
// No commit error with a non-retryable write concern error.
commitFailPoint = configureFailPoint(
shardPrimary,
"failCommand",
{
writeConcernError: {code: NumberInt(ErrorCodes.InternalError), errmsg: "foo"},
failCommands: ["commitTransaction"],
failInternalCommands: true,
},
{times: 10});
// The internal transaction test command will rethrow a write concern error as a top-level error.
res = assert.commandFailedWithCode(runTxn(st.s, makeSingleInsertTxn({_id: 4})),
ErrorCodes.InternalError);
commitFailPoint.off();
// Non-transient commit error that is normally transient. Note NoSuchTransaction is not transient
// with a write concern error, which is what this is meant to simulate. Also note the fail command
// fail point can't take both a write concern error and write concern error so we "cheat" and
// override the error labels.
commitFailPoint = configureFailPoint(shardPrimary,
"failCommand",
{
errorCode: ErrorCodes.NoSuchTransaction,
errorLabels: [],
failCommands: ["commitTransaction"],
failInternalCommands: true,
},
{times: 10});
res = assert.commandFailedWithCode(runTxn(st.s, makeSingleInsertTxn({_id: 5})),
ErrorCodes.NoSuchTransaction);
commitFailPoint.off();
st.stop();
}());
|