diff options
author | Matthew Saltz <matthew.saltz@mongodb.com> | 2019-09-05 15:19:37 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-09-05 15:19:37 +0000 |
commit | 59079867e7bba25e1201f50d6e470d7157825f84 (patch) | |
tree | 18434bc39256df461889fb57eac91dca11955a1c /jstests | |
parent | 1d5d6a2fa25ebf81e96052c32425816f6d64d2e1 (diff) | |
download | mongo-59079867e7bba25e1201f50d6e470d7157825f84.tar.gz |
SERVER-38028 Block new transactions on sessions behind prepared transactions
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/core/txns/new_transactions_on_session_with_prepared_txn_block_behind_prepare.js | 155 | ||||
-rw-r--r-- | jstests/core/txns/no_new_transactions_when_prepared_transaction_in_progress.js | 56 |
2 files changed, 155 insertions, 56 deletions
diff --git a/jstests/core/txns/new_transactions_on_session_with_prepared_txn_block_behind_prepare.js b/jstests/core/txns/new_transactions_on_session_with_prepared_txn_block_behind_prepare.js new file mode 100644 index 00000000000..f83de45dd3d --- /dev/null +++ b/jstests/core/txns/new_transactions_on_session_with_prepared_txn_block_behind_prepare.js @@ -0,0 +1,155 @@ +/** + * Tests that new transactions on a session block behind an existing prepared transaction on the + * session. + * + * @tags: [uses_transactions, uses_prepare_transaction] + */ + +(function() { +"use strict"; +load("jstests/core/txns/libs/prepare_helpers.js"); +load("jstests/libs/curop_helpers.js"); // for waitForCurOpByFailPoint(). +load("jstests/libs/parallel_shell_helpers.js"); + +/** + * Launches a parallel shell to start a new transaction on the session with the given lsid. It + * performs an insert and then commits. Assumes that there will be an already-prepared transaction + * on the session, and blocks using a failpoint until the transaction in the parallel shell has + * begun to block behind the prepared transaction. + */ +function runConcurrentTransactionOnSession(dbName, collName, lsid) { + var awaitShell; + try { + // Turn on failpoint that parallel shell will hit when blocked on prepare. + assert.commandWorked(db.adminCommand( + {configureFailPoint: 'waitAfterNewStatementBlocksBehindPrepare', mode: "alwaysOn"})); + + function runTransactionOnSession(dbName, collName, lsid) { + // Use txnNumber : 1 since the active txnNumber will be 0. + const txnNumber = NumberLong(1); + // Try to do an insert in a new transaction on the same session. Note that we're + // manually including the lsid and stmtId instead of using the session object directly + // since there's no way to share a session with the parallel shell. + assert.commandWorked(db.getSiblingDB(dbName).runCommand({ + insert: collName, + documents: [{x: "blocks_behind_prepare"}], + readConcern: {level: "snapshot"}, + lsid: lsid, + txnNumber: txnNumber, + stmtId: NumberInt(0), + startTransaction: true, + autocommit: false + })); + + assert.commandWorked(db.adminCommand( + {commitTransaction: 1, lsid: lsid, txnNumber: txnNumber, autocommit: false})); + } + // Launch a parallel shell to start a new transaction, insert a document, and commit. These + // operations should block behind the previous prepared transaction on the session. + awaitShell = + startParallelShell(funWithArgs(runTransactionOnSession, dbName, collName, lsid)); + + // Wait until parallel shell insert is blocked on prepare. + waitForCurOpByFailPointNoNS(db, "waitAfterNewStatementBlocksBehindPrepare"); + } finally { + // Disable failpoint to allow the parallel shell to continue - it should still be blocked on + // prepare. This is needed in a finally block so that if something fails we're guaranteed to + // turn this failpoint off, so that it doesn't cause problems for subsequent tests. + assert.commandWorked(db.adminCommand( + {configureFailPoint: 'waitAfterNewStatementBlocksBehindPrepare', mode: "off"})); + } + + return awaitShell; +} + +/** + * Common variables and setup. + */ +const dbName = "test"; +const collName = jsTestName(); +const testDB = db.getSiblingDB(dbName); + +testDB.runCommand({drop: collName}); +assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}})); + +(() => { + jsTestLog( + "New transactions on a session should block behind an existing prepared transaction on that session until it aborts."); + + const session = testDB.getMongo().startSession(); + const sessionDb = session.getDatabase(dbName); + const sessionColl = sessionDb.getCollection(collName); + const lsid = session.getSessionId(); + + // Start and prepare a transaction. + session.startTransaction(); + assert.commandWorked(sessionColl.insert({x: "foo"})); + PrepareHelpers.prepareTransaction(session); + + // Launch a concurrent transaction which should block behind the active prepared transaction. + const awaitShell = runConcurrentTransactionOnSession(dbName, collName, lsid); + + // Abort the original transaction - this should allow the parallel shell to continue and start a + // new transaction. + assert.commandWorked(session.abortTransaction_forTesting()); + + awaitShell(); + + session.endSession(); +})(); + +(() => { + jsTestLog( + "New transactions on a session should block behind an existing prepared transaction on that session until it commits."); + + const session = testDB.getMongo().startSession(); + const sessionDb = session.getDatabase(dbName); + const sessionColl = sessionDb.getCollection(collName); + const lsid = session.getSessionId(); + + // Start and prepare a transaction. + session.startTransaction(); + assert.commandWorked(sessionColl.insert({x: "foo"})); + const prepareTimestamp = PrepareHelpers.prepareTransaction(session); + + // Launch a concurrent transaction which should block behind the active prepared transaction. + const awaitShell = runConcurrentTransactionOnSession(dbName, collName, lsid); + + // Commit the original transaction - this should allow the parallel shell to continue and start + // a new transaction. Not using PrepareHelpers.commitTransaction because it calls + // commitTransaction twice, and the second call races with the second transaction the test + // started. + assert.commandWorked(session.getDatabase('admin').adminCommand( + {commitTransaction: 1, commitTimestamp: prepareTimestamp})); + + awaitShell(); + + session.endSession(); +})(); + +(() => { + jsTestLog( + "Test error precedence when executing a malformed command during a prepared transaction."); + + const session = testDB.getMongo().startSession(); + const sessionDb = session.getDatabase(dbName); + const sessionColl = sessionDb.getCollection(collName); + session.startTransaction(); + assert.commandWorked(sessionColl.insert({_id: "insert-1"})); + PrepareHelpers.prepareTransaction(session); + + // The following command specifies txnNumber: 2 without startTransaction: true. + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "no_such_txn"}], + txnNumber: NumberLong(2), + stmtId: NumberInt(0), + autocommit: false + }), + ErrorCodes.NoSuchTransaction); + + assert.commandWorked(session.abortTransaction_forTesting()); + + session.endSession(); +})(); +}()); diff --git a/jstests/core/txns/no_new_transactions_when_prepared_transaction_in_progress.js b/jstests/core/txns/no_new_transactions_when_prepared_transaction_in_progress.js deleted file mode 100644 index ce41fb98620..00000000000 --- a/jstests/core/txns/no_new_transactions_when_prepared_transaction_in_progress.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Tests that we cannot start a new transaction when a prepared transaction exists on the session. - * @tags: [uses_transactions, uses_prepare_transaction] - * - */ - -(function() { -"use strict"; -load("jstests/core/txns/libs/prepare_helpers.js"); - -const dbName = "test"; -const collName = "no_new_transactions_when_prepared_transaction_in_progress"; -const testDB = db.getSiblingDB(dbName); - -testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}); - -assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}})); - -const sessionOptions = { - causalConsistency: false -}; -const session = testDB.getMongo().startSession(sessionOptions); -const sessionDb = session.getDatabase(dbName); -const sessionColl = sessionDb.getCollection(collName); - -jsTestLog("Test starting a new transaction while an existing prepared transaction exists on the " + - "session."); -session.startTransaction(); -assert.commandWorked(sessionColl.insert({_id: "insert-1"})); -PrepareHelpers.prepareTransaction(session); -assert.commandFailedWithCode(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "cannot_start"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(1), - stmtId: NumberInt(0), - startTransaction: true, - autocommit: false -}), - ErrorCodes.PreparedTransactionInProgress); - -jsTestLog( - "Test error precedence when executing a malformed command during a prepared transaction."); -// The following command specifies txnNumber: 2 without startTransaction: true. -assert.commandFailedWithCode(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "no_such_txn"}], - txnNumber: NumberLong(2), - stmtId: NumberInt(0), - autocommit: false -}), - ErrorCodes.NoSuchTransaction); -assert.commandWorked(session.abortTransaction_forTesting()); - -session.endSession(); -}()); |