From 532118079aeac69c7a00bc9b42840e2b318f43d4 Mon Sep 17 00:00:00 2001 From: ycycyyc <944696498@qq.com> Date: Wed, 24 Jun 2026 14:08:28 +0800 Subject: [PATCH] SERVER-129760: Repeatedly creating and dropping the same TTL index causes the TTL job to run multiple times within a single cycle --- src/mongo/db/ttl/ttl_collection_cache.cpp | 11 +-- src/mongo/db/ttl/ttl_collection_cache.h | 2 +- .../db/ttl/ttl_collection_cache_test.cpp | 68 +++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/mongo/db/ttl/ttl_collection_cache.cpp b/src/mongo/db/ttl/ttl_collection_cache.cpp index 2727b95cf090d..61f4050b958fc 100644 --- a/src/mongo/db/ttl/ttl_collection_cache.cpp +++ b/src/mongo/db/ttl/ttl_collection_cache.cpp @@ -53,14 +53,17 @@ TTLCollectionCache& TTLCollectionCache::get(ServiceContext* ctx) { } void TTLCollectionCache::registerTTLInfo(UUID uuid, const Info& info) { - { - std::lock_guard lock(_ttlInfosLock); - _ttlInfos[uuid].push_back(info); - } + std::lock_guard lock(_ttlInfosLock); + _deregisterTTLInfo_inlock(uuid, info); + _ttlInfos[uuid].push_back(info); } void TTLCollectionCache::_deregisterTTLInfo(UUID uuid, const Info& info) { std::lock_guard lock(_ttlInfosLock); + _deregisterTTLInfo_inlock(uuid, info); +} + +void TTLCollectionCache::_deregisterTTLInfo_inlock(UUID uuid, const Info& info) { auto infoIt = _ttlInfos.find(uuid); if (infoIt == _ttlInfos.end()) { LOGV2_DEBUG(9150100, diff --git a/src/mongo/db/ttl/ttl_collection_cache.h b/src/mongo/db/ttl/ttl_collection_cache.h index 60c50c8e2e6bd..9701a56b93dee 100644 --- a/src/mongo/db/ttl/ttl_collection_cache.h +++ b/src/mongo/db/ttl/ttl_collection_cache.h @@ -88,7 +88,6 @@ class TTLCollectionCache { ExpireAfterSecondsType _expireAfterSecondsType; }; - // Caller is responsible for ensuring no duplicates are registered. void registerTTLInfo(UUID uuid, const Info& info); void deregisterTTLIndexByName(UUID uuid, const IndexName& indexName); void deregisterTTLClusteredIndex(UUID uuid); @@ -109,6 +108,7 @@ class TTLCollectionCache { * Shared implementation for deregistering TTL infos. */ void _deregisterTTLInfo(UUID uuid, const Info& info); + void _deregisterTTLInfo_inlock(UUID uuid, const Info& info); std::mutex _ttlInfosLock; InfoMap _ttlInfos; diff --git a/src/mongo/db/ttl/ttl_collection_cache_test.cpp b/src/mongo/db/ttl/ttl_collection_cache_test.cpp index d95418a327400..89ee1eb27ff7e 100644 --- a/src/mongo/db/ttl/ttl_collection_cache_test.cpp +++ b/src/mongo/db/ttl/ttl_collection_cache_test.cpp @@ -133,6 +133,74 @@ TEST(TTLCollectionCacheTest, Basic) { EXPECT_EQ(infos.size(), 0U); } +// Test that registering the same TTL index more than once does not create duplicate infos. +// This guards against SERVER-129760, where repeatedly creating and dropping the same TTL index +// caused the TTL job to run multiple times within a single cycle. +TEST(TTLCollectionCacheTest, RegisterSameIndexTwiceDoesNotDuplicate) { + TTLCollectionCache cache; + EXPECT_EQ(cache.getTTLInfos().size(), 0); + + auto uuid = UUID::gen(); + auto infoInvalid = TTLCollectionCache::Info{ + "collA_ttl_1", TTLCollectionCache::Info::ExpireAfterSecondsType::kInvalid}; + auto infoInt = TTLCollectionCache::Info{ + "collA_ttl_1", TTLCollectionCache::Info::ExpireAfterSecondsType::kInt}; + + cache.registerTTLInfo(uuid, infoInvalid); + // Re-registering the same index name replaces the existing info rather than duplicating it. + cache.registerTTLInfo(uuid, infoInt); + + auto infos = cache.getTTLInfos(); + EXPECT_EQ(infos.size(), 1U); + ASSERT_TRUE(infos.contains(uuid)); + ASSERT_EQ(infos[uuid].size(), 1U); + EXPECT_EQ(infos[uuid][0].getIndexName(), "collA_ttl_1"); + // The most recent registration's expireAfterSecondsType wins. + EXPECT_FALSE(infos[uuid][0].isExpireAfterSecondsInvalid()); +} + +// Test that registering a clustered TTL info more than once does not create duplicate infos. +TEST(TTLCollectionCacheTest, RegisterSameClusteredIndexTwiceDoesNotDuplicate) { + TTLCollectionCache cache; + EXPECT_EQ(cache.getTTLInfos().size(), 0); + + auto uuid = UUID::gen(); + auto infoClustered = TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}; + + cache.registerTTLInfo(uuid, infoClustered); + cache.registerTTLInfo(uuid, infoClustered); + + auto infos = cache.getTTLInfos(); + EXPECT_EQ(infos.size(), 1U); + ASSERT_TRUE(infos.contains(uuid)); + ASSERT_EQ(infos[uuid].size(), 1U); + EXPECT_TRUE(infos[uuid][0].isClustered()); +} + +// Test that registering distinct TTL indexes on the same UUID keeps all of them. +TEST(TTLCollectionCacheTest, RegisterDistinctIndexesKeepsAll) { + TTLCollectionCache cache; + EXPECT_EQ(cache.getTTLInfos().size(), 0); + + auto uuid = UUID::gen(); + auto infoIndex1 = TTLCollectionCache::Info{ + "collA_ttl_1", TTLCollectionCache::Info::ExpireAfterSecondsType::kInt}; + auto infoIndex2 = TTLCollectionCache::Info{ + "collA_ttl_2", TTLCollectionCache::Info::ExpireAfterSecondsType::kInt}; + auto infoClustered = TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}; + + cache.registerTTLInfo(uuid, infoIndex1); + cache.registerTTLInfo(uuid, infoIndex2); + cache.registerTTLInfo(uuid, infoClustered); + // Re-registering an existing index should not affect the others. + cache.registerTTLInfo(uuid, infoIndex1); + + auto infos = cache.getTTLInfos(); + EXPECT_EQ(infos.size(), 1U); + ASSERT_TRUE(infos.contains(uuid)); + ASSERT_EQ(infos[uuid].size(), 3U); +} + TEST(TTLCollectionCacheTest, DeregisterUntrackedUUIDDoesNotThrow) { TTLCollectionCache cache; EXPECT_EQ(cache.getTTLInfos().size(), 0);