Files
mongo/jstests/sharding/transactions_snapshot_errors_subsequent_statements.js
Zac 591928c619 SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
2025-08-21 17:27:09 +00:00

131 lines
5.3 KiB
JavaScript

// Tests mongos behavior on snapshot errors encountered during subsequent statements in a
// multi-statement transaction. In particular, verifies that snapshot errors beyond the first
// command in a transaction are fatal to the entire transaction.
//
// Runs against an unsharded collection, a sharded collection with all chunks on one shard, and a
// sharded collection with one chunk on both shards.
//
// @tags: [requires_sharding, uses_transactions, uses_multi_shard_transaction]
import {ShardingTest} from "jstests/libs/shardingtest.js";
import {findChunksUtil} from "jstests/sharding/libs/find_chunks_util.js";
import {
assertNoSuchTransactionOnAllShards,
disableStaleVersionAndSnapshotRetriesWithinTransactions,
enableStaleVersionAndSnapshotRetriesWithinTransactions,
kSnapshotErrors,
setFailCommandOnShards,
} from "jstests/sharding/libs/sharded_transactions_helpers.js";
const dbName = "test";
const collName = "foo";
const ns = dbName + "." + collName;
const kCommandTestCases = [
{name: "aggregate", command: {aggregate: collName, pipeline: [], cursor: {}}},
{name: "distinct", command: {distinct: collName, query: {}, key: "_id"}},
{name: "find", command: {find: collName}},
{
// findAndModify can only target one shard, even in the two shard case.
name: "findAndModify",
command: {findAndModify: collName, query: {_id: 1}, update: {$set: {x: 1}}},
},
{name: "insert", command: {insert: collName, documents: [{_id: 1}, {_id: 11}]}},
{
name: "update",
command: {
update: collName,
updates: [
{q: {_id: 1}, u: {$set: {_id: 2}}},
{q: {_id: 11}, u: {$set: {_id: 12}}},
],
},
},
{
name: "delete",
command: {
delete: collName,
deletes: [
{q: {_id: 2}, limit: 1},
{q: {_id: 12}, limit: 1},
],
},
},
// We cannot test killCursors because mongos discards the response from any killCursors
// requests that may be sent to shards.
];
function runTest(st, collName, errorCode, isSharded) {
const session = st.s.startSession();
const sessionDB = session.getDatabase(dbName);
for (let commandTestCase of kCommandTestCases) {
const commandName = commandTestCase.name;
const commandBody = commandTestCase.command;
const failCommandOptions = {namespace: ns, errorCode, failCommands: [commandName]};
if (isSharded && commandName === "distinct") {
// Distinct isn't allowed on sharded collections in a multi-document transaction.
print("Skipping distinct test case for sharded collections");
continue;
}
// Successfully start a transaction on one shard.
session.startTransaction({readConcern: {level: "snapshot"}});
assert.commandWorked(sessionDB.runCommand({find: collName, filter: {_id: 15}}));
// Verify the command must fail on a snapshot error from a subsequent statement.
setFailCommandOnShards(st, {times: 1}, failCommandOptions, 1);
const res = assert.commandFailedWithCode(sessionDB.runCommand(commandBody), errorCode);
assert.eq(res.errorLabels, ["TransientTransactionError"]);
assertNoSuchTransactionOnAllShards(st, session.getSessionId(), session.getTxnNumber_forTesting());
assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction);
}
}
const st = new ShardingTest({shards: 2, mongos: 1});
enableStaleVersionAndSnapshotRetriesWithinTransactions(st);
jsTestLog("Unsharded transaction");
assert.commandWorked(st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
assert.commandWorked(st.s.getDB(dbName)[collName].insert({_id: 5}, {writeConcern: {w: "majority"}}));
// Single shard case simulates the storage engine discarding an in-use snapshot.
for (let errorCode of kSnapshotErrors) {
runTest(st, collName, errorCode, false /* isSharded */);
}
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}}));
// Set up 2 chunks, [minKey, 10), [10, maxKey), each with one document (includes the document
// already inserted).
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 10}}));
assert.commandWorked(st.s.getDB(dbName)[collName].insert({_id: 15}, {writeConcern: {w: "majority"}}));
jsTestLog("One shard transaction");
assert.eq(2, findChunksUtil.countChunksForNs(st.s.getDB("config"), ns, {shard: st.shard0.shardName}));
assert.eq(0, findChunksUtil.countChunksForNs(st.s.getDB("config"), ns, {shard: st.shard1.shardName}));
for (let errorCode of kSnapshotErrors) {
runTest(st, collName, errorCode, true /* isSharded */);
}
jsTestLog("Two shard transaction");
assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {_id: 15}, to: st.shard1.shardName}));
assert.eq(1, findChunksUtil.countChunksForNs(st.s.getDB("config"), ns, {shard: st.shard0.shardName}));
assert.eq(1, findChunksUtil.countChunksForNs(st.s.getDB("config"), ns, {shard: st.shard1.shardName}));
// Multi shard case simulates adding a new participant that can no longer support the already
// chosen read timestamp.
for (let errorCode of kSnapshotErrors) {
runTest(st, collName, errorCode, true /* isSharded */);
}
disableStaleVersionAndSnapshotRetriesWithinTransactions(st);
st.stop();