summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDianna Hohensee <dianna.hohensee@10gen.com>2019-04-01 21:30:22 -0400
committerDianna Hohensee <dianna.hohensee@10gen.com>2019-04-02 15:16:41 -0400
commitb55d0e6297219efd14fe4d27d0063d61d29a5f43 (patch)
treeb7b00573a306bedce0804fecdf81140b130323aa /src
parenteb9aa240af19bb9ac2624e0dddf2b13836cce196 (diff)
downloadmongo-b55d0e6297219efd14fe4d27d0063d61d29a5f43.tar.gz
SERVER-38139 prepareTransaction will error if transaction operations were done against temporary collections
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/catalog/collection.h8
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp4
-rw-r--r--src/mongo/db/catalog/collection_impl.h2
-rw-r--r--src/mongo/db/catalog/collection_mock.h4
-rw-r--r--src/mongo/db/transaction_participant.cpp24
-rw-r--r--src/mongo/db/transaction_participant_test.cpp69
6 files changed, 97 insertions, 14 deletions
diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h
index 85598d29f51..f661e720a74 100644
--- a/src/mongo/db/catalog/collection.h
+++ b/src/mongo/db/catalog/collection.h
@@ -387,7 +387,13 @@ public:
StringData newLevel,
StringData newAction) = 0;
- // -----------
+ /**
+ * Returns true if this is a temporary collection.
+ *
+ * Calling this function is somewhat costly because it requires accessing the storage engine's
+ * cache of collection information.
+ */
+ virtual bool isTemporary(OperationContext* opCtx) const = 0;
//
// Stats
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index 448846149f8..117ec3d003d 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -731,6 +731,10 @@ StatusWith<RecordData> CollectionImpl::updateDocumentWithDamages(
return newRecStatus;
}
+bool CollectionImpl::isTemporary(OperationContext* opCtx) const {
+ return _details->getCollectionOptions(opCtx).temp;
+}
+
bool CollectionImpl::isCapped() const {
return _cappedNotifier.get();
}
diff --git a/src/mongo/db/catalog/collection_impl.h b/src/mongo/db/catalog/collection_impl.h
index 09e345c4ef7..98eabf774db 100644
--- a/src/mongo/db/catalog/collection_impl.h
+++ b/src/mongo/db/catalog/collection_impl.h
@@ -295,7 +295,7 @@ public:
StringData newLevel,
StringData newAction) final;
- // -----------
+ bool isTemporary(OperationContext* opCtx) const final;
//
// Stats
diff --git a/src/mongo/db/catalog/collection_mock.h b/src/mongo/db/catalog/collection_mock.h
index ed16d6d058b..fb4ddb39bec 100644
--- a/src/mongo/db/catalog/collection_mock.h
+++ b/src/mongo/db/catalog/collection_mock.h
@@ -222,6 +222,10 @@ public:
std::abort();
}
+ bool isTemporary(OperationContext* opCtx) const {
+ std::abort();
+ }
+
bool isCapped() const {
std::abort();
}
diff --git a/src/mongo/db/transaction_participant.cpp b/src/mongo/db/transaction_participant.cpp
index 06794e680c2..9220b028fa5 100644
--- a/src/mongo/db/transaction_participant.cpp
+++ b/src/mongo/db/transaction_participant.cpp
@@ -972,6 +972,28 @@ Timestamp TransactionParticipant::Participant::prepareTransaction(
}
});
+ auto& completedTransactionOperations = retrieveCompletedTransactionOperations(opCtx);
+
+ // Ensure that no transaction operations were done against temporary collections.
+ // Transactions should not operate on temporary collections because they are for internal use
+ // only and are deleted on both repl stepup and server startup.
+
+ // Create a set of collection UUIDs through which to iterate, so that we do not recheck the same
+ // collection multiple times: it is a costly check.
+ stdx::unordered_set<UUID, UUID::Hash> transactionOperationUuids;
+ for (const auto& transactionOp : completedTransactionOperations) {
+ transactionOperationUuids.insert(transactionOp.getUuid().get());
+ }
+ for (const auto& uuid : transactionOperationUuids) {
+ auto collection = UUIDCatalog::get(opCtx).lookupCollectionByUUID(uuid);
+ uassert(ErrorCodes::OperationNotSupportedInTransaction,
+ str::stream() << "prepareTransaction failed because one of the transaction "
+ "operations was done against a temporary collection '"
+ << collection->ns()
+ << "'.",
+ !collection->isTemporary(opCtx));
+ }
+
boost::optional<OplogSlotReserver> oplogSlotReserver;
OplogSlot prepareOplogSlot;
{
@@ -1026,7 +1048,7 @@ Timestamp TransactionParticipant::Participant::prepareTransaction(
opCtx->recoveryUnit()->setPrepareTimestamp(prepareOplogSlot.opTime.getTimestamp());
opCtx->getWriteUnitOfWork()->prepare();
opCtx->getServiceContext()->getOpObserver()->onTransactionPrepare(
- opCtx, reservedSlots, retrieveCompletedTransactionOperations(opCtx));
+ opCtx, reservedSlots, completedTransactionOperations);
abortGuard.dismiss();
diff --git a/src/mongo/db/transaction_participant_test.cpp b/src/mongo/db/transaction_participant_test.cpp
index 7f0c8fba28e..20ab156251e 100644
--- a/src/mongo/db/transaction_participant_test.cpp
+++ b/src/mongo/db/transaction_participant_test.cpp
@@ -60,7 +60,6 @@ namespace mongo {
namespace {
const NamespaceString kNss("TestDB", "TestColl");
-const OptionalCollectionUUID kUUID;
/**
* Creates an OplogEntry with given parameters and preset defaults for this test suite.
@@ -236,6 +235,20 @@ protected:
_opObserver = mockObserver.get();
opObserverRegistry->addObserver(std::move(mockObserver));
+ {
+ // Set up a collection so that TransactionParticipant::prepareTransaction() can safely
+ // access it.
+ AutoGetOrCreateDb autoDb(opCtx(), kNss.db(), MODE_X);
+ auto db = autoDb.getDb();
+ ASSERT_TRUE(db);
+
+ WriteUnitOfWork wuow(opCtx());
+ CollectionOptions options;
+ options.uuid = _uuid;
+ db->createCollection(opCtx(), kNss.ns(), options);
+ wuow.commit();
+ }
+
opCtx()->setLogicalSessionId(_sessionId);
opCtx()->setTxnNumber(_txnNumber);
}
@@ -272,6 +285,7 @@ protected:
const LogicalSessionId _sessionId{makeLogicalSessionIdForTest()};
const TxnNumber _txnNumber{20};
+ const OptionalCollectionUUID _uuid = UUID::gen();
OpObserverMock* _opObserver = nullptr;
};
@@ -430,7 +444,7 @@ TEST_F(TxnParticipantTest, SameTransactionPreservesStoredStatements) {
// We must have stashed transaction resources to re-open the transaction.
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
ASSERT_BSONOBJ_EQ(operation.toBSON(),
txnParticipant.getTransactionOperationsForTest()[0].toBSON());
@@ -452,7 +466,7 @@ TEST_F(TxnParticipantTest, AbortClearsStoredStatements) {
auto sessionCheckout = checkOutSession();
auto txnParticipant = TransactionParticipant::get(opCtx());
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
ASSERT_BSONOBJ_EQ(operation.toBSON(),
txnParticipant.getTransactionOperationsForTest()[0].toBSON());
@@ -515,6 +529,39 @@ TEST_F(TxnParticipantTest, PrepareSucceedsWithNestedLocks) {
ASSERT_TRUE(txnParticipant.transactionIsCommitted());
}
+TEST_F(TxnParticipantTest, PrepareFailsOnTemporaryCollection) {
+ NamespaceString tempCollNss(kNss.db(), "tempCollection");
+ UUID tempCollUUID = UUID::gen();
+
+ // Create a temporary collection so that we can write to it.
+ {
+ AutoGetOrCreateDb autoDb(opCtx(), kNss.db(), MODE_X);
+ auto db = autoDb.getDb();
+ ASSERT_TRUE(db);
+
+ WriteUnitOfWork wuow(opCtx());
+ CollectionOptions options;
+ options.uuid = tempCollUUID;
+ options.temp = true;
+ db->createCollection(opCtx(), tempCollNss.ns(), options);
+ wuow.commit();
+ }
+
+ // Set up a transaction on the temp collection
+ auto outerScopedSession = checkOutSession();
+ auto txnParticipant = TransactionParticipant::get(opCtx());
+
+ txnParticipant.unstashTransactionResources(opCtx(), "insert");
+
+ auto operation =
+ repl::OplogEntry::makeInsertOperation(tempCollNss, tempCollUUID, BSON("TestValue" << 0));
+ txnParticipant.addTransactionOperation(opCtx(), operation);
+
+ ASSERT_THROWS_CODE(txnParticipant.prepareTransaction(opCtx(), {}),
+ AssertionException,
+ ErrorCodes::OperationNotSupportedInTransaction);
+}
+
TEST_F(TxnParticipantTest, CommitTransactionSetsCommitTimestampOnPreparedTransaction) {
auto sessionCheckout = checkOutSession();
@@ -969,7 +1016,7 @@ TEST_F(TxnParticipantTest, CannotInsertInPreparedTransaction) {
auto txnParticipant = TransactionParticipant::get(opCtx());
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
txnParticipant.prepareTransaction(opCtx(), {});
@@ -987,7 +1034,7 @@ TEST_F(TxnParticipantTest, ImplictAbortDoesNotAbortPreparedTransaction) {
auto txnParticipant = TransactionParticipant::get(opCtx());
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
txnParticipant.prepareTransaction(opCtx(), {});
@@ -1006,7 +1053,7 @@ DEATH_TEST_F(TxnParticipantTest,
auto txnParticipant = TransactionParticipant::get(opCtx());
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {});
@@ -1051,7 +1098,7 @@ TEST_F(TxnParticipantTest, TransactionTooLargeWhileBuilding) {
std::unique_ptr<uint8_t[]> bigData(new uint8_t[kBigDataSize]());
auto operation = repl::OplogEntry::makeInsertOperation(
kNss,
- kUUID,
+ _uuid,
BSON("_id" << 0 << "data" << BSONBinData(bigData.get(), kBigDataSize, BinDataGeneral)));
txnParticipant.addTransactionOperation(opCtx(), operation);
txnParticipant.addTransactionOperation(opCtx(), operation);
@@ -1161,7 +1208,7 @@ protected:
ASSERT(txnParticipant.inMultiDocumentTransaction());
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
txnParticipant.prepareTransaction(opCtx(), {});
@@ -3104,7 +3151,7 @@ TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterSlowCommit) {
setupAdditiveMetrics(metricValue, opCtx());
txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
serverGlobalParams.slowMS = 10;
@@ -3484,7 +3531,7 @@ TEST_F(TxnParticipantTest, RollbackResetsInMemoryStateOfPreparedTransaction) {
// Perform an insert as a part of a transaction so that we have a transaction operation.
txnParticipant.unstashTransactionResources(opCtx(), "insert");
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
ASSERT_BSONOBJ_EQ(operation.toBSON(),
txnParticipant.getTransactionOperationsForTest()[0].toBSON());
@@ -3678,7 +3725,7 @@ TEST_F(TxnParticipantTest,
ASSERT_TRUE(txnParticipant.getResponseMetadata().getReadOnly());
// Simulate an insert.
- auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0));
+ auto operation = repl::OplogEntry::makeInsertOperation(kNss, _uuid, BSON("TestValue" << 0));
txnParticipant.addTransactionOperation(opCtx(), operation);
ASSERT_FALSE(txnParticipant.getResponseMetadata().getReadOnly());
}