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
126
127
128
129
|
// Tests mongos behavior on stale database version errors received in a transaction.
//
// @tags: [requires_sharding, uses_transactions, uses_multi_shard_transaction]
(function() {
"use strict";
load("jstests/sharding/libs/sharded_transactions_helpers.js");
const dbName = "test";
const collName = "foo";
const st = new ShardingTest({shards: 2, mongos: 1, config: 1});
enableStaleVersionAndSnapshotRetriesWithinTransactions(st);
// Set up two unsharded collections in different databases with shard0 as their primary.
assert.writeOK(st.s.getDB(dbName)[collName].insert({_id: 0}, {writeConcern: {w: "majority"}}));
assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
st.ensurePrimaryShard(dbName, st.shard0.shardName);
const session = st.s.startSession();
const sessionDB = session.getDatabase(dbName);
//
// Stale database version on first overall command should succeed.
//
session.startTransaction();
// No database versioned requests have been sent to Shard0, so it is stale.
assert.commandWorked(sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}));
assert.commandWorked(session.commitTransaction_forTesting());
//
// Stale database version on second command to a shard should fail.
//
st.ensurePrimaryShard(dbName, st.shard1.shardName);
session.startTransaction();
// Find is not database versioned so it will not trigger SDV or a refresh on Shard0.
assert.commandWorked(sessionDB.runCommand({find: collName, filter: {_id: 0}}));
// Distinct is database versioned, so it will trigger SDV. The router will retry and the retry
// will discover the transaction was aborted, because a previous statement had completed on
// Shard0.
let res = assert.commandFailedWithCode(
sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}),
ErrorCodes.NoSuchTransaction);
assert.eq(res.errorLabels, ["TransientTransactionError"]);
assertNoSuchTransactionOnAllShards(
st, session.getSessionId(), session.getTxnNumber_forTesting());
assert.commandFailedWithCode(session.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
//
// Stale database version on first command to a new shard should succeed.
//
// Create a new database on Shard0.
const otherDbName = "other_test";
const otherCollName = "bar";
assert.writeOK(
st.s.getDB(otherDbName)[otherCollName].insert({_id: 0}, {writeConcern: {w: "majority"}}));
assert.commandWorked(st.s.adminCommand({enableSharding: otherDbName}));
st.ensurePrimaryShard(otherDbName, st.shard0.shardName);
const sessionOtherDB = session.getDatabase(otherDbName);
// Advance the router's cached last committed opTime for Shard0, so it chooses a read timestamp
// after the collection is created on shard1, to avoid SnapshotUnavailable.
assert.commandWorked(
sessionOtherDB.runCommand({find: otherCollName})); // Not database versioned.
assert.writeOK(sessionDB[collName].insert({_id: 1}, {writeConcern: {w: "majority"}}));
session.startTransaction();
// Target the first database which is on Shard1.
assert.commandWorked(sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}));
// Targets the new database on Shard0 which is stale, so a database versioned request should
// trigger SDV.
assert.commandWorked(
sessionOtherDB.runCommand({distinct: otherCollName, key: "_id", query: {_id: 0}}));
assert.commandWorked(session.commitTransaction_forTesting());
//
// The final StaleDbVersion error should be returned if the router exhausts its retries.
//
st.ensurePrimaryShard(dbName, st.shard0.shardName);
st.ensurePrimaryShard(otherDbName, st.shard1.shardName);
// Disable database metadata refreshes on the stale shard so it will indefinitely return a stale
// version error.
assert.commandWorked(st.rs0.getPrimary().adminCommand(
{configureFailPoint: "skipDatabaseVersionMetadataRefresh", mode: "alwaysOn"}));
session.startTransaction();
// Target Shard1, to verify the transaction on it is implicitly aborted later.
assert.commandWorked(sessionOtherDB.runCommand({find: otherCollName}));
// Target the first database which is on Shard0. The shard is stale and won't refresh its
// metadata, so mongos should exhaust its retries and implicitly abort the transaction.
res = assert.commandFailedWithCode(
sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}),
ErrorCodes.StaleDbVersion);
assert.eq(res.errorLabels, ["TransientTransactionError"]);
// Verify all shards aborted the transaction.
assertNoSuchTransactionOnAllShards(
st, session.getSessionId(), session.getTxnNumber_forTesting());
assert.commandFailedWithCode(session.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
assert.commandWorked(st.rs0.getPrimary().adminCommand(
{configureFailPoint: "skipDatabaseVersionMetadataRefresh", mode: "off"}));
disableStaleVersionAndSnapshotRetriesWithinTransactions(st);
st.stop();
})();
|