mirror of
https://github.com/mongodb/mongo.git
synced 2024-11-24 00:17:37 +01:00
771dabd098
GitOrigin-RevId: 744aa110a53786b23c62ff53f87a1418b5991e8d
201 lines
7.1 KiB
JavaScript
201 lines
7.1 KiB
JavaScript
/**
|
|
* Sanity checks that illegally attempting to use more than one router for the
|
|
* same transaction does not leave the server in an invalid state.
|
|
*
|
|
* @tags: [uses_transactions, uses_prepare_transaction, uses_multi_shard_transaction]
|
|
*/
|
|
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
import {
|
|
flushRoutersAndRefreshShardMetadata
|
|
} from "jstests/sharding/libs/sharded_transactions_helpers.js";
|
|
|
|
const dbName = "test";
|
|
const collName = "foo";
|
|
const ns = dbName + "." + collName;
|
|
|
|
function removeAllDocumentsFromTestCollection() {
|
|
assert.commandWorked(router0.getDB(dbName).foo.deleteMany({}));
|
|
}
|
|
|
|
function runTest(testFn) {
|
|
testFn();
|
|
removeAllDocumentsFromTestCollection();
|
|
}
|
|
|
|
let st = new ShardingTest({shards: 3, mongos: 2, causallyConsistent: true});
|
|
let router0 = st.s0;
|
|
let router1 = st.s1;
|
|
|
|
// Create a sharded collection with a chunk on each shard:
|
|
// shard0: [-inf, 0)
|
|
// shard1: [0, 10)
|
|
// shard2: [10, +inf)
|
|
assert.commandWorked(
|
|
st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
|
|
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}}));
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 0}}));
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 10}}));
|
|
assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {_id: 0}, to: st.shard1.shardName}));
|
|
assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {_id: 10}, to: st.shard2.shardName}));
|
|
|
|
flushRoutersAndRefreshShardMetadata(st, {ns});
|
|
|
|
// Test that trying to run start txn from two routers with the same transaction number fails
|
|
// through the second router if they target overlapping shards.
|
|
runTest(() => {
|
|
let lsid = {id: UUID()};
|
|
let txnNumber = 0;
|
|
|
|
// Start a new transaction on router 0 by inserting a document onto each shard.
|
|
assert.commandWorked(router0.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: -5}, {_id: 5}, {_id: 15}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(0),
|
|
startTransaction: true,
|
|
autocommit: false,
|
|
}));
|
|
|
|
// Try to start a new transaction with the same transaction number on router 1 by inserting
|
|
// a document onto each shard.
|
|
assert.commandFailedWithCode(router1.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: -50}, {_id: 4}, {_id: 150}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(0),
|
|
startTransaction: true,
|
|
autocommit: false,
|
|
// Because ordered writes are done serially for different shard targets and abort early
|
|
// on first error, this can leave the transaction on the other shards open.
|
|
// To ensure this router implicitly aborts the transaction on all participants (so
|
|
// that the next test case does not encounter an open transaction), make this
|
|
// router do an *unordered* write that touches all the same participants as the
|
|
// first router touched.
|
|
ordered: false,
|
|
}),
|
|
50911);
|
|
|
|
// The automatic abort-on-error path will occur when the above
|
|
// statement fails, so commit will fail.
|
|
assert.commandFailedWithCode(router0.getDB(dbName).adminCommand({
|
|
commitTransaction: 1,
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(0),
|
|
autocommit: false,
|
|
}),
|
|
ErrorCodes.NoSuchTransaction);
|
|
});
|
|
|
|
// Test that trying to run start txn from one router and running an operation for that same
|
|
// transaction from another router fails through the second router.
|
|
runTest(() => {
|
|
let lsid = {id: UUID()};
|
|
let txnNumber = 0;
|
|
let stmtId = 0;
|
|
|
|
// Start a new transaction on router 0 by inserting a document onto each shard.
|
|
assert.commandWorked(router0.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: -5}, {_id: 5}, {_id: 15}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
startTransaction: true,
|
|
autocommit: false,
|
|
}));
|
|
|
|
++stmtId;
|
|
|
|
// Try to continue the same transaction but through router 1. Should fail because no txn
|
|
// with this number exists on that router.
|
|
assert.commandFailed(router1.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: -50}, {_id: 4}, {_id: 150}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
autocommit: false,
|
|
}));
|
|
|
|
// Commit should succeed since the command from router 2 should never reach the shard.
|
|
assert.commandWorked(router0.getDB(dbName).adminCommand({
|
|
commitTransaction: 1,
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(0),
|
|
autocommit: false,
|
|
}));
|
|
});
|
|
|
|
// Test that trying to run start txn from one router, start txn on the second router with the
|
|
// same transaction number, and running operations on overlapping shards will lead to failure.
|
|
runTest(() => {
|
|
let lsid = {id: UUID()};
|
|
let txnNumber = 0;
|
|
let stmtId = 0;
|
|
|
|
// Start a new transaction on router 0 by inserting a document onto the first shard
|
|
assert.commandWorked(router0.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: -5}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
startTransaction: true,
|
|
autocommit: false,
|
|
}));
|
|
|
|
// Start a new transaction on router 1 with the same transaction number, targeting the last
|
|
// shard.
|
|
assert.commandWorked(router1.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: 15}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
startTransaction: true,
|
|
autocommit: false,
|
|
}));
|
|
|
|
++stmtId;
|
|
|
|
// Try to do an operation on the last shard through router 0. Fails because it sends
|
|
// startTxn: true to the new participant, which has already seen an operation from router 1.
|
|
// Implicitly aborts the transaction when the error is thrown.
|
|
assert.commandFailedWithCode(router0.getDB(dbName).runCommand({
|
|
insert: collName,
|
|
documents: [{_id: 50}],
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
autocommit: false,
|
|
}),
|
|
50911);
|
|
|
|
// Commit through router 0 should fail.
|
|
assert.commandFailedWithCode(router0.getDB(dbName).adminCommand({
|
|
commitTransaction: 1,
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
autocommit: false,
|
|
}),
|
|
ErrorCodes.NoSuchTransaction);
|
|
|
|
// Commit through router 1 should fail.
|
|
assert.commandFailedWithCode(router1.getDB(dbName).adminCommand({
|
|
commitTransaction: 1,
|
|
lsid: lsid,
|
|
txnNumber: NumberLong(txnNumber),
|
|
stmtId: NumberInt(stmtId),
|
|
autocommit: false,
|
|
}),
|
|
ErrorCodes.NoSuchTransaction);
|
|
});
|
|
|
|
st.stop();
|