diff options
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/session.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/session_test.cpp | 64 |
3 files changed, 77 insertions, 0 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 04346773588..049aeb9e702 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -625,6 +625,7 @@ env.Library( 'repl/oplog_entry', 's/sharding_api_d', 'views/views', + '$BUILD_DIR/mongo/db/commands/test_commands_enabled', ], LIBDEPS_PRIVATE=[ 'commands/server_status', @@ -1781,6 +1782,7 @@ env.CppUnitTest( LIBDEPS=[ 'query_exec', '$BUILD_DIR/mongo/db/repl/mock_repl_coord_server_fixture', + '$BUILD_DIR/mongo/client/read_preference' ], ) diff --git a/src/mongo/db/session.cpp b/src/mongo/db/session.cpp index 9f9d13b6a54..a7747ccf996 100644 --- a/src/mongo/db/session.cpp +++ b/src/mongo/db/session.cpp @@ -34,6 +34,7 @@ #include "mongo/db/catalog/index_catalog.h" #include "mongo/db/commands/feature_compatibility_version_documentation.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/concurrency/lock_state.h" #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/db_raii.h" @@ -316,6 +317,16 @@ void Session::beginOrContinueTxn(OperationContext* opCtx, return; } + // If the command specified a read preference that allows it to run on a secondary, and it is + // trying to execute an operation on a multi-statement transaction, then we throw an error. + // Transactions are only allowed to be run on a primary. + if (!getTestCommandsEnabled()) { + uassert(50789, + "readPreference=primary is the only allowed readPreference for multi-statement " + "transactions.", + !(autocommit && ReadPreferenceSetting::get(opCtx).canRunOnSecondary())); + } + invariant(!opCtx->lockState()->isLocked()); stdx::lock_guard<stdx::mutex> lg(_mutex); diff --git a/src/mongo/db/session_test.cpp b/src/mongo/db/session_test.cpp index d78f189901f..fd4a5674139 100644 --- a/src/mongo/db/session_test.cpp +++ b/src/mongo/db/session_test.cpp @@ -753,6 +753,70 @@ TEST_F(SessionTest, AutocommitRequiredOnEveryTxnOp) { session.beginOrContinueTxn(opCtx(), txnNum, false, boost::none); } +TEST_F(SessionTest, TransactionsOnlyPermitAllowedReadPreferences) { + const auto sessionId = makeLogicalSessionIdForTest(); + Session session(sessionId); + session.refreshFromStorageIfNeeded(opCtx()); + TxnNumber txnNum = 1; + + // + // Multi-statement transaction operations can only be run with 'readPreference=primary'. + // + + auto startTxnWithReadPref = [&](ReadPreference readPref, + boost::optional<bool> autocommit, + boost::optional<bool> startTransaction) { + txnNum++; + ReadPreferenceSetting::get(opCtx()) = ReadPreferenceSetting(readPref); + session.beginOrContinueTxn(opCtx(), txnNum, autocommit, startTransaction); + }; + + // Shouldn't throw. + startTxnWithReadPref(ReadPreference::PrimaryOnly, false, true); + ASSERT_TRUE(session.inSnapshotReadOrMultiDocumentTransaction()); + + // All unsupported read preferences should throw. + ASSERT_THROWS_CODE(startTxnWithReadPref(ReadPreference::PrimaryPreferred, false, true), + AssertionException, + 50789); + ASSERT_THROWS_CODE(startTxnWithReadPref(ReadPreference::SecondaryOnly, false, true), + AssertionException, + 50789); + ASSERT_THROWS_CODE(startTxnWithReadPref(ReadPreference::SecondaryPreferred, false, true), + AssertionException, + 50789); + ASSERT_THROWS_CODE( + startTxnWithReadPref(ReadPreference::Nearest, false, true), AssertionException, 50789); + + // + // Operations that are not on a multi-statement transaction are allowed to specify any + // readPreference. + // + + auto activeTxnNum = TxnNumber{-1}; + + // None of these should throw. Each should start a transaction with a new, higher, transaction + // number. + startTxnWithReadPref(ReadPreference::PrimaryOnly, boost::none, boost::none); + ASSERT_GT(session.getActiveTxnNumberForTest(), activeTxnNum); + activeTxnNum = session.getActiveTxnNumberForTest(); + + startTxnWithReadPref(ReadPreference::PrimaryPreferred, boost::none, boost::none); + ASSERT_GT(session.getActiveTxnNumberForTest(), activeTxnNum); + activeTxnNum = session.getActiveTxnNumberForTest(); + + startTxnWithReadPref(ReadPreference::SecondaryOnly, boost::none, boost::none); + ASSERT_GT(session.getActiveTxnNumberForTest(), activeTxnNum); + activeTxnNum = session.getActiveTxnNumberForTest(); + + startTxnWithReadPref(ReadPreference::SecondaryPreferred, boost::none, boost::none); + ASSERT_GT(session.getActiveTxnNumberForTest(), activeTxnNum); + activeTxnNum = session.getActiveTxnNumberForTest(); + + startTxnWithReadPref(ReadPreference::Nearest, boost::none, boost::none); + ASSERT_GT(session.getActiveTxnNumberForTest(), activeTxnNum); +} + TEST_F(SessionTest, SameTransactionPreservesStoredStatements) { const auto sessionId = makeLogicalSessionIdForTest(); Session session(sessionId); |