diff options
author | Dianna Hohensee <dianna.hohensee@10gen.com> | 2019-04-01 21:30:22 -0400 |
---|---|---|
committer | Dianna Hohensee <dianna.hohensee@10gen.com> | 2019-04-02 15:16:41 -0400 |
commit | b55d0e6297219efd14fe4d27d0063d61d29a5f43 (patch) | |
tree | b7b00573a306bedce0804fecdf81140b130323aa /src | |
parent | eb9aa240af19bb9ac2624e0dddf2b13836cce196 (diff) | |
download | mongo-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.h | 8 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.h | 2 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_mock.h | 4 | ||||
-rw-r--r-- | src/mongo/db/transaction_participant.cpp | 24 | ||||
-rw-r--r-- | src/mongo/db/transaction_participant_test.cpp | 69 |
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()); } |