summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorWilliam Schultz <william.schultz@mongodb.com>2018-04-14 08:17:36 -0400
committerWilliam Schultz <william.schultz@mongodb.com>2018-04-14 08:17:36 -0400
commit6eafc0790bb3602551127fa831ea859a989c384f (patch)
tree8e715f633eb2dfca26b56c592fa3665104e21512 /src/mongo/db
parent4095f8a2effeedbd17de4792af839b20e1a8e8d5 (diff)
downloadmongo-6eafc0790bb3602551127fa831ea859a989c384f.tar.gz
SERVER-33580 Restrict multi-statement transactions to replica set primaries
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/SConscript2
-rw-r--r--src/mongo/db/session.cpp11
-rw-r--r--src/mongo/db/session_test.cpp64
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);