TEST_F(KVDropPendingIdentReaperTest, AddDropPendingIdentWithDuplicateDropTimestampButDifferentIdent) { auto engine = getEngine(); KVDropPendingIdentReaper reaper(engine); Timestamp dropTimestamp{Seconds(100), 0}; NamespaceString nss1("test.foo"); constexpr auto ident1 = "ident1"_sd; NamespaceString nss2("test.bar"); constexpr auto ident2 = "ident2"_sd; reaper.addDropPendingIdent(dropTimestamp, nss1, ident1); reaper.addDropPendingIdent(dropTimestamp, nss2, ident2); // getAllIdents() returns a set of drop-pending idents known to the reaper. auto dropPendingIdents = reaper.getAllIdents(); ASSERT_EQUALS(2U, dropPendingIdents.size()); ASSERT(dropPendingIdents.find(ident1.toString()) != dropPendingIdents.cend()); ASSERT(dropPendingIdents.find(ident2.toString()) != dropPendingIdents.cend()); // Check earliest drop timestamp. ASSERT_EQUALS(dropTimestamp, *reaper.getEarliestDropTimestamp()); // This should have no effect. auto opCtx = makeOpCtx(); reaper.dropIdentsOlderThan(opCtx.get(), dropTimestamp); ASSERT_EQUALS(0U, engine->droppedIdents.size()); // Drop all idents managed by reaper and confirm number of drops. reaper.dropIdentsOlderThan(opCtx.get(), makeTimestampWithNextInc(dropTimestamp)); ASSERT_EQUALS(2U, engine->droppedIdents.size()); ASSERT_EQUALS(ident1, engine->droppedIdents.front()); ASSERT_EQUALS(ident2, engine->droppedIdents.back()); }
TEST_F(SyncTailTest, MultiApplyAssignsOperationsToWriterThreadsBasedOnNamespaceHash) { // This test relies on implementation details of how multiApply uses hashing to distribute ops // to threads. It is possible for this test to fail, even if the implementation of multiApply is // correct. If it fails, consider adjusting the namespace names (to adjust the hash values) or // the number of threads in the pool. NamespaceString nss1("test.t0"); NamespaceString nss2("test.t1"); OldThreadPool writerPool(3); stdx::mutex mutex; std::vector<MultiApplier::Operations> operationsApplied; auto applyOperationFn = [&mutex, &operationsApplied]( MultiApplier::OperationPtrs* operationsForWriterThreadToApply) { stdx::lock_guard<stdx::mutex> lock(mutex); operationsApplied.emplace_back(); for (auto&& opPtr : *operationsForWriterThreadToApply) { operationsApplied.back().push_back(*opPtr); } }; auto op1 = makeInsertDocumentOplogEntry({Timestamp(Seconds(1), 0), 1LL}, nss1, BSON("x" << 1)); auto op2 = makeInsertDocumentOplogEntry({Timestamp(Seconds(2), 0), 1LL}, nss2, BSON("x" << 2)); NamespaceString nssForInsert; std::vector<BSONObj> operationsWrittenToOplog; _storageInterface->insertDocumentsFn = [&mutex, &nssForInsert, &operationsWrittenToOplog]( OperationContext* txn, const NamespaceString& nss, const std::vector<BSONObj>& docs) { stdx::lock_guard<stdx::mutex> lock(mutex); nssForInsert = nss; operationsWrittenToOplog = docs; return Status::OK(); }; auto lastOpTime = unittest::assertGet(multiApply(_txn.get(), &writerPool, {op1, op2}, applyOperationFn)); ASSERT_EQUALS(op2.getOpTime(), lastOpTime); // Each writer thread should be given exactly one operation to apply. std::vector<OpTime> seen; { stdx::lock_guard<stdx::mutex> lock(mutex); ASSERT_EQUALS(operationsApplied.size(), 2U); for (auto&& operationsAppliedByThread : operationsApplied) { ASSERT_EQUALS(1U, operationsAppliedByThread.size()); const auto& oplogEntry = operationsAppliedByThread.front(); ASSERT_TRUE(std::find(seen.cbegin(), seen.cend(), oplogEntry.getOpTime()) == seen.cend()); ASSERT_TRUE(oplogEntry == op1 || oplogEntry == op2); seen.push_back(oplogEntry.getOpTime()); } } // Check ops in oplog. stdx::lock_guard<stdx::mutex> lock(mutex); ASSERT_EQUALS(2U, operationsWrittenToOplog.size()); ASSERT_EQUALS(NamespaceString(rsOplogName), nssForInsert); ASSERT_EQUALS(op1.raw, operationsWrittenToOplog[0]); ASSERT_EQUALS(op2.raw, operationsWrittenToOplog[1]); }
TEST_F(SyncTailTest, MultiApplyAssignsOperationsToWriterThreadsBasedOnNamespaceHash) { NamespaceString nss1("test.t0"); NamespaceString nss2("test.t1"); OldThreadPool writerPool(2); // Ensure that namespaces are hashed to different threads in pool. ASSERT_EQUALS(0U, StringMapTraits::hash(nss1.ns()) % writerPool.getNumThreads()); ASSERT_EQUALS(1U, StringMapTraits::hash(nss2.ns()) % writerPool.getNumThreads()); stdx::mutex mutex; std::vector<MultiApplier::Operations> operationsApplied; auto applyOperationFn = [&mutex, &operationsApplied]( MultiApplier::OperationPtrs* operationsForWriterThreadToApply) { stdx::lock_guard<stdx::mutex> lock(mutex); operationsApplied.emplace_back(); for (auto&& opPtr : *operationsForWriterThreadToApply) { operationsApplied.back().push_back(*opPtr); } }; auto op1 = makeInsertDocumentOplogEntry({Timestamp(Seconds(1), 0), 1LL}, nss1, BSON("x" << 1)); auto op2 = makeInsertDocumentOplogEntry({Timestamp(Seconds(2), 0), 1LL}, nss2, BSON("x" << 2)); NamespaceString nssForInsert; std::vector<BSONObj> operationsWrittenToOplog; _storageInterface->insertDocumentsFn = [&mutex, &nssForInsert, &operationsWrittenToOplog]( OperationContext* txn, const NamespaceString& nss, const std::vector<BSONObj>& docs) { stdx::lock_guard<stdx::mutex> lock(mutex); nssForInsert = nss; operationsWrittenToOplog = docs; return Status::OK(); }; auto lastOpTime = unittest::assertGet(multiApply(_txn.get(), &writerPool, {op1, op2}, applyOperationFn)); ASSERT_EQUALS(op2.getOpTime(), lastOpTime); // Each writer thread should be given exactly one operation to apply. std::vector<OpTime> seen; { stdx::lock_guard<stdx::mutex> lock(mutex); ASSERT_EQUALS(writerPool.getNumThreads(), operationsApplied.size()); for (auto&& operationsAppliedByThread : operationsApplied) { ASSERT_EQUALS(1U, operationsAppliedByThread.size()); const auto& oplogEntry = operationsAppliedByThread.front(); ASSERT_TRUE(std::find(seen.cbegin(), seen.cend(), oplogEntry.getOpTime()) == seen.cend()); ASSERT_TRUE(oplogEntry == op1 || oplogEntry == op2); seen.push_back(oplogEntry.getOpTime()); } } // Check ops in oplog. stdx::lock_guard<stdx::mutex> lock(mutex); ASSERT_EQUALS(2U, operationsWrittenToOplog.size()); ASSERT_EQUALS(NamespaceString(rsOplogName), nssForInsert); ASSERT_EQUALS(op1.raw, operationsWrittenToOplog[0]); ASSERT_EQUALS(op2.raw, operationsWrittenToOplog[1]); }
TEST_F(SyncTailTest, MultiSyncApplyGroupsInsertOperationByNamespaceBeforeApplying) { int seconds = 0; auto makeOp = [&seconds](const NamespaceString& nss) { return makeInsertDocumentOplogEntry( {Timestamp(Seconds(seconds), 0), 1LL}, nss, BSON("_id" << seconds++)); }; NamespaceString nss1("test." + _agent.getSuiteName() + "_" + _agent.getTestName() + "_1"); NamespaceString nss2("test." + _agent.getSuiteName() + "_" + _agent.getTestName() + "_2"); auto createOp1 = makeCreateCollectionOplogEntry({Timestamp(Seconds(seconds++), 0), 1LL}, nss1); auto createOp2 = makeCreateCollectionOplogEntry({Timestamp(Seconds(seconds++), 0), 1LL}, nss2); auto insertOp1a = makeOp(nss1); auto insertOp1b = makeOp(nss1); auto insertOp2a = makeOp(nss2); auto insertOp2b = makeOp(nss2); MultiApplier::Operations operationsApplied; auto syncApply = [&operationsApplied](OperationContext*, const BSONObj& op, bool) { operationsApplied.push_back(OplogEntry(op)); return Status::OK(); }; MultiApplier::OperationPtrs ops = { &createOp1, &createOp2, &insertOp1a, &insertOp2a, &insertOp1b, &insertOp2b}; ASSERT_OK(multiSyncApply_noAbort(_txn.get(), &ops, syncApply)); ASSERT_EQUALS(4U, operationsApplied.size()); ASSERT_EQUALS(createOp1, operationsApplied[0]); ASSERT_EQUALS(createOp2, operationsApplied[1]); // Check grouped insert operations in namespace "nss1". ASSERT_EQUALS(insertOp1a.getOpTime(), operationsApplied[2].getOpTime()); ASSERT_EQUALS(insertOp1a.ns, operationsApplied[2].ns); ASSERT_EQUALS(BSONType::Array, operationsApplied[2].o.type()); auto group1 = operationsApplied[2].o.Array(); ASSERT_EQUALS(2U, group1.size()); ASSERT_EQUALS(insertOp1a.o.Obj(), group1[0].Obj()); ASSERT_EQUALS(insertOp1b.o.Obj(), group1[1].Obj()); // Check grouped insert operations in namespace "nss2". ASSERT_EQUALS(insertOp2a.getOpTime(), operationsApplied[3].getOpTime()); ASSERT_EQUALS(insertOp2a.ns, operationsApplied[3].ns); ASSERT_EQUALS(BSONType::Array, operationsApplied[3].o.type()); auto group2 = operationsApplied[3].o.Array(); ASSERT_EQUALS(2U, group2.size()); ASSERT_EQUALS(insertOp2a.o.Obj(), group2[0].Obj()); ASSERT_EQUALS(insertOp2b.o.Obj(), group2[1].Obj()); }