summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/txns/prepare_transaction_fails_on_temp_collections.js35
-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
7 files changed, 132 insertions, 14 deletions
diff --git a/jstests/core/txns/prepare_transaction_fails_on_temp_collections.js b/jstests/core/txns/prepare_transaction_fails_on_temp_collections.js
new file mode 100644
index 00000000000..ac752e37074
--- /dev/null
+++ b/jstests/core/txns/prepare_transaction_fails_on_temp_collections.js
@@ -0,0 +1,35 @@
+/**
+ * Tests that prepare transaction fails if the transaction operated on a temporary collection.
+ *
+ * Transactions should not operate on temporary collections because they are for internal use only
+ * and are deleted on both repl stepup and server startup.
+ *
+ * @tags: [uses_transactions, uses_prepare_transaction]
+ */
+
+(function() {
+ "use strict";
+
+ const dbName = "test";
+ const tempCollName = "prepare_transaction_fails_on_temp_collections";
+ const testDB = db.getSiblingDB(dbName);
+ const testTempColl = testDB.getCollection(tempCollName);
+
+ testTempColl.drop({writeConcern: {w: "majority"}});
+
+ jsTest.log("Creating a temporary collection ({temp:true}).");
+ assert.commandWorked(testDB.createCollection(tempCollName, {temp: true}));
+
+ const session = db.getMongo().startSession();
+ const sessionDB = session.getDatabase(dbName);
+ const sessionTempColl = sessionDB.getCollection(tempCollName);
+
+ jsTest.log("Setting up a transaction with an operation on a temporary collection.");
+ session.startTransaction();
+ assert.commandWorked(sessionTempColl.insert({x: 1000}));
+
+ jsTest.log("Calling prepareTransaction for a transaction with operations against a " +
+ "temporary collection should now fail.");
+ assert.commandFailedWithCode(sessionDB.adminCommand({prepareTransaction: 1}),
+ ErrorCodes.OperationNotSupportedInTransaction);
+})();
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());
}