diff options
author | Tess Avitabile <tess.avitabile@mongodb.com> | 2019-05-22 17:50:23 -0400 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2019-05-28 09:20:42 -0400 |
commit | f70fb4c734e342afe6561960edd53667f16e9eb1 (patch) | |
tree | c17c111beb5b1843e6ca1b48f5a7a44ebfde8667 /jstests | |
parent | 793a9fa884e2785d39a59e638add8f638b46b43e (diff) | |
download | mongo-f70fb4c734e342afe6561960edd53667f16e9eb1.tar.gz |
SERVER-41107 Create concurrency test that accesses a collection in a transaction after catalog changes
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/concurrency/fsm_workloads/access_collection_in_transaction_after_catalog_changes.js | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/jstests/concurrency/fsm_workloads/access_collection_in_transaction_after_catalog_changes.js b/jstests/concurrency/fsm_workloads/access_collection_in_transaction_after_catalog_changes.js new file mode 100644 index 00000000000..1ad738f437b --- /dev/null +++ b/jstests/concurrency/fsm_workloads/access_collection_in_transaction_after_catalog_changes.js @@ -0,0 +1,342 @@ +'use strict'; + +/** + * Transactions with local (and majority) readConcern perform untimestamped reads and do not check + * the min visible snapshot for collections, so they can access collections whose catalog + * information does not match the state of the collection in the transaction's snapshot. This test + * exercises this behavior by starting a transaction on one collection, then performing an operation + * on a second collection, where another thread may have performed a DDL operation on the second + * collection since the transaction started. The goal of the test is to ensure the server does not + * crash in this scenario. + * + * Do not run in sharding suites because the first transaction statement is expect to succeed + * unconditionally, which need not be true in a sharded cluster. + * @tags: [uses_transactions, requires_replication] + */ + +var $config = (function() { + + var states = (function() { + + function init(db, collName) { + this.session = db.getMongo().startSession(); + } + + function runOpInTxn( + session, db, startCollName, ddlDBName, ddlCollName, loggingCollName, opName, op) { + const startColl = session.getDatabase(db.getName())[startCollName]; + const ddlColl = session.getDatabase(ddlDBName)[ddlCollName]; + + // Start the transaction and run an operation on 'startColl'. + session.startTransaction(); + assertWhenOwnColl.eq(1, startColl.find({_id: "startTxnDoc"}).itcount()); + + // Run the specified operation on 'ddlColl'. Another thread may have performed a DDL + // operation on 'ddlColl' since the transaction started. The operation may fail with one + // of the allowed error codes, but it must not crash the server. + let success = false; + try { + op(ddlColl); + success = true; + } catch (e) { + assertWhenOwnColl.contains(e.code, + [ + ErrorCodes.LockTimeout, + ErrorCodes.WriteConflict, + ErrorCodes.SnapshotUnavailable, + ErrorCodes.OperationNotSupportedInTransaction + ], + () => tojson(e)); + } + + // Commit or abort the transaction. + if (success) { + assertWhenOwnColl.commandWorked(session.commitTransaction_forTesting()); + } else { + // The failed operation already aborted the transaction. Run abortTransaction to + // update the transaction state in the shell. + assertWhenOwnColl.commandFailedWithCode(session.abortTransaction_forTesting(), + ErrorCodes.NoSuchTransaction); + } + + // Record whether the operation succeeded or failed. + assertWhenOwnColl.commandWorked( + db[loggingCollName].insert({op: opName, success: success})); + } + + /** + * The following functions will be run by threads performing transaction operations. + */ + + function aggregate(db, collName) { + const op = function(ddlColl) { + ddlColl.aggregate([{$limit: 1}]).itcount(); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "aggregate", + op); + } + + function distinct(db, collName) { + const op = function(ddlColl) { + ddlColl.distinct("x"); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "distinct", + op); + } + + function findAndModify(db, collName) { + const op = function(ddlColl) { + ddlColl.findAndModify({query: {}, sort: {x: 1}, update: {$inc: {x: 1}}}); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "findAndModify", + op); + } + + function findCollScan(db, collName) { + const op = function(ddlColl) { + ddlColl.findOne(); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "findCollScan", + op); + } + + function findGetMore(db, collName) { + const op = function(ddlColl) { + ddlColl.find().batchSize(1).itcount(); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "findGetMore", + op); + } + + function findIdScan(db, collName) { + const op = function(ddlColl) { + ddlColl.findOne({_id: 0}); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "findIdScan", + op); + } + + function findSecondaryIndexScan(db, collName) { + const op = function(ddlColl) { + ddlColl.findOne({x: 1}); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "findSecondaryIndexScan", + op); + } + + function insert(db, collName) { + const op = function(ddlColl) { + let res = ddlColl.insert({x: 1}); + if (res instanceof WriteResult && res.hasWriteError()) { + throw _getErrorWithCode(res.getWriteError(), res.getWriteError().errmsg); + } else if (!res.ok) { + assertWhenOwnColl.commandWorked(res); + } + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "insert", + op); + } + + function remove(db, collName) { + const op = function(ddlColl) { + assertWhenOwnColl.commandWorked(ddlColl.remove({}, {justOne: true})); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "remove", + op); + } + + function update(db, collName) { + const op = function(ddlColl) { + assertWhenOwnColl.commandWorked(ddlColl.update({}, {$inc: {x: 1}})); + }; + runOpInTxn(this.session, + db, + collName, + this.ddlDBName, + this.ddlCollName, + this.loggingCollName, + "update", + op); + } + + /** + * The following functions will be run by threads performing DDL operations. + */ + + function createColl(db, collName) { + // Insert a document to ensure the collection exists and provide data that can be + // accessed in the transaction states. + assertWhenOwnColl.commandWorked( + db.getSiblingDB(this.ddlDBName)[this.ddlCollName].insert({x: 1})); + } + + function createIndex(db, collName) { + assertWhenOwnColl.commandWorked( + db.getSiblingDB(this.ddlDBName)[this.ddlCollName].createIndex({x: 1})); + } + + function dropColl(db, collName) { + assertWhenOwnColl.commandWorkedOrFailedWithCode( + db.getSiblingDB(this.ddlDBName).runCommand({drop: this.ddlCollName}), + ErrorCodes.NamespaceNotFound); + } + + function renameColl(db, collName) { + const ddlCollFullName = db.getSiblingDB(this.ddlDBName)[this.ddlCollName].getFullName(); + const renameCollFullName = + db.getSiblingDB(this.ddlDBName)[this.renameCollName].getFullName(); + assertWhenOwnColl.commandWorkedOrFailedWithCode( + db.adminCommand( + {renameCollection: ddlCollFullName, to: renameCollFullName, dropTarget: true}), + ErrorCodes.NamespaceNotFound); + } + + return { + init: init, + aggregate: aggregate, + distinct: distinct, + findAndModify: findAndModify, + findCollScan: findCollScan, + findGetMore: findGetMore, + findIdScan: findIdScan, + findSecondaryIndexScan: findSecondaryIndexScan, + insert: insert, + remove: remove, + update: update, + createColl: createColl, + createIndex: createIndex, + dropColl: dropColl, + renameColl: renameColl + }; + })(); + + function setup(db, collName, cluster) { + assertWhenOwnColl.commandWorked(db[collName].insert({_id: "startTxnDoc"})); + assertWhenOwnColl.commandWorked(db.runCommand({create: this.loggingCollName})); + } + + function teardown(db, collName, cluster) { + // Report the number of successful and failed transaction operations of each type. This test + // does not provide value if all transaction operations fail with LockTimeout, since then we + // are not accessing the DDL collection. + let res = + db[this.loggingCollName] + .aggregate([{$match: {success: true}}, {$group: {_id: "$op", count: {$sum: 1}}}]) + .toArray(); + jsTestLog("Successful transaction operations: " + tojson(res)); + + res = db[this.loggingCollName] + .aggregate([{$match: {success: false}}, {$group: {_id: "$op", count: {$sum: 1}}}]) + .toArray(); + jsTestLog("Failed transaction operations: " + tojson(res)); + } + + var randomTxnState = { + aggregate: 0.1, + distinct: 0.1, + findAndModify: 0.1, + findCollScan: 0.1, + findGetMore: 0.1, + findIdScan: 0.1, + findSecondaryIndexScan: 0.1, + insert: 0.1, + remove: 0.1, + update: 0.1 + }; + + var randomDDLState = {createColl: 0.4, createIndex: 0.2, dropColl: 0.2, renameColl: 0.2}; + + var transitions = { + // 80% of threads perform transaction operations, and 20% perform DDL operations. + init: {aggregate: 0.8, createColl: 0.2}, + + // Transaction states. + aggregate: randomTxnState, + distinct: randomTxnState, + findAndModify: randomTxnState, + findCollScan: randomTxnState, + findGetMore: randomTxnState, + findIdScan: randomTxnState, + findSecondaryIndexScan: randomTxnState, + insert: randomTxnState, + remove: randomTxnState, + update: randomTxnState, + + // DDL states. + createColl: randomDDLState, + createIndex: randomDDLState, + dropColl: randomDDLState, + renameColl: randomDDLState + }; + + return { + threadCount: 10, + iterations: 100, + startState: 'init', + states: states, + transitions: transitions, + data: { + ddlCollName: "ddl_coll", + ddlDBName: "access_collection_in_transaction_after_catalog_changes_ddl_db", + loggingCollName: "access_collection_in_transaction_after_catalog_changes_logging", + renameCollName: "rename_coll" + }, + setup: setup, + teardown: teardown + }; + +})(); |