summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorJosef Ahmad <josef.ahmad@mongodb.com>2022-09-28 14:48:05 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-09-28 15:13:56 +0000
commitcbc2fa05af5f8ffe0f2c8f3e37bdd9ac34b3ac85 (patch)
tree1f822ba933ffe05e2c1b4ff174b7962c148f41c3 /jstests
parenta2bde0ebc259bf57218f16baa9f8d2a4cbb789e4 (diff)
downloadmongo-cbc2fa05af5f8ffe0f2c8f3e37bdd9ac34b3ac85.tar.gz
SERVER-67538 Make multi-doc txns return WCE on index catalog changes
Background: SERVER-47866 stopped bumping the collection's minimum visibility timestamp on catalog changes related to an index; only the index's minimum visibility snapshot continues to be updated. One side effect of this change is that a multi-document transaction can read a at a snapshot where the index is not yet ready and commit at a timestamp when the index is ready, which not intended behaviour and can open the opportunity for a race to happen. This patch introduces a check for the indices' minimum visible timestamp. Attempting to write to an index entry while reading at an incompatible timestamp returns a write conflict exception. Locking rules guarantee that we see a consistent in-memory view of the indices' minimum visible snapshot. (cherry picked from commit a4bd3ce3607d2c3020d7efa3501240ae4b1a1b03) (cherry picked from commit 4e80712214658e3c70cecef42680618068448e7f)
Diffstat (limited to 'jstests')
-rw-r--r--jstests/noPassthrough/txn_index_catalog_changes.js80
1 files changed, 80 insertions, 0 deletions
diff --git a/jstests/noPassthrough/txn_index_catalog_changes.js b/jstests/noPassthrough/txn_index_catalog_changes.js
new file mode 100644
index 00000000000..b29a8129a8b
--- /dev/null
+++ b/jstests/noPassthrough/txn_index_catalog_changes.js
@@ -0,0 +1,80 @@
+/**
+ * Verifies that a multi-document transaction aborts with WriteConflictError if an index build has
+ * committed since the transaction's read snapshot.
+ *
+ * @tags: [
+ * requires_replication,
+ * ]
+ */
+(function() {
+'use strict';
+
+const replTest = new ReplSetTest({nodes: 2});
+replTest.startSet();
+replTest.initiate();
+
+const primary = replTest.getPrimary();
+const db = primary.getDB('test');
+
+// Transaction inserting an index key.
+{
+ assert.commandWorked(db['c'].insertOne({_id: 0, num: 0}));
+
+ const s0 = db.getMongo().startSession();
+ s0.startTransaction();
+ assert.commandWorked(s0.getDatabase('test')['c'].deleteOne({_id: 0}));
+ s0.commitTransaction();
+
+ const clusterTime = s0.getClusterTime().clusterTime;
+
+ assert.commandWorked(db['c'].createIndex({num: 1}));
+
+ // Start a transaction whose snapshot predates the completion of the index build, and which
+ // reserves an oplog entry after the index build commits.
+ try {
+ const s1 = db.getMongo().startSession();
+ s1.startTransaction({readConcern: {level: "snapshot", atClusterTime: clusterTime}});
+ s1.getDatabase('test').c.insertOne({_id: 1, num: 1});
+
+ // Transaction should have failed.
+ assert(0);
+ } catch (e) {
+ assert(e.hasOwnProperty("errorLabels"), tojson(e));
+ assert.contains("TransientTransactionError", e.errorLabels, tojson(e));
+ assert.eq(e["code"], ErrorCodes.WriteConflict, tojson(e));
+ }
+}
+
+db.c.drop();
+
+// Transaction deleting an index key.
+{
+ assert.commandWorked(db.createCollection('c'));
+
+ const s0 = db.getMongo().startSession();
+ s0.startTransaction();
+ assert.commandWorked(s0.getDatabase('test')['c'].insertOne({_id: 0, num: 0}));
+ s0.commitTransaction();
+
+ const clusterTime = s0.getClusterTime().clusterTime;
+
+ assert.commandWorked(db['c'].createIndex({num: 1}));
+
+ // Start a transaction whose snapshot predates the completion of the index build, and which
+ // reserves an oplog entry after the index build commits.
+ try {
+ const s1 = db.getMongo().startSession();
+ s1.startTransaction({readConcern: {level: "snapshot", atClusterTime: clusterTime}});
+ s1.getDatabase('test').c.deleteOne({_id: 0});
+
+ // Transaction should have failed.
+ assert(0);
+ } catch (e) {
+ assert(e.hasOwnProperty("errorLabels"), tojson(e));
+ assert.contains("TransientTransactionError", e.errorLabels, tojson(e));
+ assert.eq(e["code"], ErrorCodes.WriteConflict, tojson(e));
+ }
+}
+
+replTest.stopSet();
+})();