summaryrefslogtreecommitdiff
path: root/jstests/sharding/internal_txns/eager_reaping.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/sharding/internal_txns/eager_reaping.js')
-rw-r--r--jstests/sharding/internal_txns/eager_reaping.js215
1 files changed, 215 insertions, 0 deletions
diff --git a/jstests/sharding/internal_txns/eager_reaping.js b/jstests/sharding/internal_txns/eager_reaping.js
new file mode 100644
index 00000000000..cb584c77b70
--- /dev/null
+++ b/jstests/sharding/internal_txns/eager_reaping.js
@@ -0,0 +1,215 @@
+/*
+ * Tests that transaction records for retryable internal sessions are reaped eagerly when they are
+ * reaped early from memory.
+ *
+ * @tags: [requires_fcv_60, uses_transactions]
+ */
+(function() {
+"use strict";
+
+const st = new ShardingTest({shards: 1, config: 1});
+
+const kDbName = "testDb";
+const kCollName = "testColl";
+const mongosTestColl = st.s.getCollection(kDbName + "." + kCollName);
+assert.commandWorked(mongosTestColl.insert({x: 1})); // Set up the collection.
+
+function assertNumEntries(conn,
+ {sessionUUID, numImageCollectionEntries, numTransactionsCollEntries}) {
+ const filter = {"_id.id": sessionUUID};
+
+ const imageColl = conn.getCollection("config.image_collection");
+ assert.eq(numImageCollectionEntries,
+ imageColl.find(filter).itcount(),
+ tojson(imageColl.find().toArray()));
+
+ const transactionsColl = conn.getCollection("config.transactions");
+ assert.eq(numTransactionsCollEntries,
+ transactionsColl.find(filter).itcount(),
+ tojson(transactionsColl.find().toArray()));
+}
+
+function runInternalTxn(conn, lsid, txnNumber) {
+ const testInternalTxnCmdObj = {
+ testInternalTransactions: 1,
+ commandInfos: [{
+ dbName: kDbName,
+ command: {
+ // Use findAndModify to generate image collection entries.
+ findAndModify: kCollName,
+ query: {x: 1},
+ update: {$inc: {counter: 1}},
+ stmtId: NumberInt(3),
+ }
+ }],
+ lsid: lsid,
+
+ };
+ if (txnNumber !== undefined) {
+ testInternalTxnCmdObj.txnNumber = NumberLong(txnNumber);
+ }
+ assert.commandWorked(conn.adminCommand(testInternalTxnCmdObj));
+}
+
+function assertNumEntriesSoon(
+ shardConn, {sessionUUID, numImageCollectionEntries, numTransactionsCollEntries}) {
+ // Sleep a little so it's likely the reaping has finished and we can avoid spamming the logs.
+ sleep(100);
+ assert.soonNoExcept(() => {
+ assertNumEntries(shardConn,
+ {sessionUUID, numImageCollectionEntries, numTransactionsCollEntries});
+ return true;
+ }, "Expected internal transactions to be reaped eventually", undefined, 100 /* interval */);
+}
+
+function runTest(conn, shardConn) {
+ // Lower the threshold to speed up the test and verify it's respected.
+ const reapThreshold = 100;
+ assert.commandWorked(
+ shardConn.adminCommand({setParameter: 1, internalSessionsReapThreshold: reapThreshold}));
+
+ //
+ // Reaping happens at the threshold.
+ //
+
+ let parentLsid = {id: UUID()};
+
+ // No transaction records at first.
+ assertNumEntries(
+ shardConn,
+ {sessionUUID: parentLsid.id, numImageCollectionEntries: 0, numTransactionsCollEntries: 0});
+
+ // Records build up until the reap threshold.
+ for (let i = 0; i < reapThreshold; i++) {
+ runInternalTxn(conn, parentLsid, i);
+ }
+ assertNumEntries(shardConn, {
+ sessionUUID: parentLsid.id,
+ numImageCollectionEntries: reapThreshold,
+ numTransactionsCollEntries: reapThreshold
+ });
+
+ // Push the number of eagerly reaped sessions up to the threshold and verify this triggers
+ // reaping them.
+ runInternalTxn(conn, parentLsid, reapThreshold + 1);
+ assertNumEntriesSoon(
+ shardConn,
+ {sessionUUID: parentLsid.id, numImageCollectionEntries: 1, numTransactionsCollEntries: 1});
+
+ //
+ // Reaping can run more than once.
+ //
+
+ for (let i = 0; i < reapThreshold; i++) {
+ // We're on the same session as before, so pick higher txnNumbers than used before.
+ const txnNumber = i + reapThreshold + 1;
+ runInternalTxn(conn, parentLsid, txnNumber);
+ }
+ assertNumEntriesSoon(
+ shardConn,
+ {sessionUUID: parentLsid.id, numImageCollectionEntries: 1, numTransactionsCollEntries: 1});
+
+ //
+ // Buffered sessions are cleared on failover.
+ //
+
+ parentLsid = {id: UUID()};
+
+ const numBeforeFailover = (reapThreshold / 2) + 1;
+ for (let i = 0; i < numBeforeFailover; i++) {
+ runInternalTxn(conn, parentLsid, i);
+ }
+ assertNumEntries(shardConn, {
+ sessionUUID: parentLsid.id,
+ numImageCollectionEntries: numBeforeFailover,
+ numTransactionsCollEntries: numBeforeFailover
+ });
+
+ // Step down and back up the new primary and verify it only reaps newly expired internal
+ // sessions.
+
+ assert.commandWorked(
+ shardConn.adminCommand({replSetStepDown: ReplSetTest.kForeverSecs, force: true}));
+ assert.commandWorked(shardConn.adminCommand({replSetFreeze: 0}));
+ st.rs0.stepUp(shardConn);
+ shardConn = st.rs0.getPrimary();
+
+ const numAfterFailover = (reapThreshold / 2) + 1;
+ assert(numAfterFailover + numBeforeFailover > reapThreshold);
+ for (let i = 0; i < numAfterFailover; i++) {
+ const txnNumber = i + numBeforeFailover; // Account for txnNumbers used before failover.
+ runInternalTxn(conn, parentLsid, txnNumber);
+ }
+ assertNumEntries(shardConn, {
+ sessionUUID: parentLsid.id,
+ numImageCollectionEntries: numBeforeFailover + numAfterFailover,
+ numTransactionsCollEntries: numBeforeFailover + numAfterFailover
+ });
+
+ // Insert up to the threshold and verify a reap is triggered.
+ for (let i = 0; i < reapThreshold - numAfterFailover; i++) {
+ const txnNumber = i + 1000; // Account for txnNumbers used earlier.
+ runInternalTxn(conn, parentLsid, txnNumber);
+ }
+ assertNumEntriesSoon(shardConn, {
+ sessionUUID: parentLsid.id,
+ numImageCollectionEntries: numBeforeFailover,
+ numTransactionsCollEntries: numBeforeFailover
+ });
+
+ //
+ // Reaping ignores non-retryable sessions and parent sessions.
+ //
+
+ parentLsid = {id: UUID()};
+
+ runInternalTxn(conn, parentLsid); // Non-retryable transaction.
+ assert.commandWorked(conn.getDB("test").runCommand({
+ insert: "foo",
+ documents: [{x: 1}],
+ lsid: parentLsid,
+ txnNumber: NumberLong(0),
+ stmtId: NumberInt(0)
+ }));
+
+ // Run enough retryable transactions to trigger a reap.
+ for (let i = 0; i < reapThreshold + 1; i++) {
+ const txnNumber = i + 1; // Account for the retryable write's txnNumber.
+ runInternalTxn(conn, parentLsid, txnNumber);
+ }
+ // Expect 3: the parent entry, the non-retryable entry, and the latest retryable child. Only the
+ // retryable child has an image entry, so just expect 1 of those.
+ assertNumEntriesSoon(
+ shardConn,
+ {sessionUUID: parentLsid.id, numImageCollectionEntries: 1, numTransactionsCollEntries: 3});
+}
+
+// Validates behavior about the configurable reap threshold server parameter.
+function runParameterTest(conn, shardConn) {
+ // Must be a number.
+ assert.commandFailedWithCode(
+ shardConn.adminCommand({setParameter: 1, internalSessionsReapThreshold: "wontwork"}),
+ ErrorCodes.BadValue);
+
+ // Can't be set negative.
+ assert.commandFailedWithCode(
+ shardConn.adminCommand({setParameter: 1, internalSessionsReapThreshold: -1}),
+ ErrorCodes.BadValue);
+
+ // Can be set to 0 or a positive value.
+ assert.commandWorked(
+ shardConn.adminCommand({setParameter: 1, internalSessionsReapThreshold: 0}));
+ assert.commandWorked(
+ shardConn.adminCommand({setParameter: 1, internalSessionsReapThreshold: 12345}));
+
+ // Doesn't exist on mongos. This fails with no error code so check the errmsg.
+ const res = assert.commandFailed(
+ conn.adminCommand({setParameter: 1, internalSessionsReapThreshold: 222}));
+ assert(res.errmsg.includes("unrecognized parameter"), tojson(res));
+}
+
+runTest(st.s, st.rs0.getPrimary());
+runParameterTest(st.s, st.rs0.getPrimary());
+
+st.stop();
+})();