diff --git a/bitchat/Services/MessageDeduplicationService.swift b/bitchat/Services/MessageDeduplicationService.swift index c41b942e..6eba2e59 100644 --- a/bitchat/Services/MessageDeduplicationService.swift +++ b/bitchat/Services/MessageDeduplicationService.swift @@ -28,7 +28,6 @@ final class LRUDeduplicationCache { private var map: [String: StoredEntry] = [:] private var order: [OrderEntry] = [] private var head: Int = 0 - private var staleNodeCount: Int = 0 private var nextGeneration: UInt64 = 0 private let capacity: Int @@ -44,11 +43,6 @@ final class LRUDeduplicationCache { map.count } - #if DEBUG - /// Order-node storage exposed only for bounded-storage regression tests. - var _orderStorageCountForTesting: Int { order.count } - #endif - /// Checks if a key exists in the cache func contains(_ key: String) -> Bool { map[key] != nil @@ -71,14 +65,11 @@ final class LRUDeduplicationCache { } trimIfNeeded() - compactIfNeeded() } /// Removes a specific key from the cache func remove(_ key: String) { - guard map.removeValue(forKey: key) != nil else { return } - staleNodeCount += 1 - compactIfNeeded() + map.removeValue(forKey: key) } /// Clears all entries from the cache @@ -86,7 +77,6 @@ final class LRUDeduplicationCache { map.removeAll() order.removeAll() head = 0 - staleNodeCount = 0 nextGeneration = 0 } @@ -103,11 +93,15 @@ final class LRUDeduplicationCache { let candidate = order[head] head += 1 + // Periodically compact the consumed prefix, matching the previous + // cache behavior without scanning unconsumed order nodes. + if head >= 32 && head * 2 >= order.count { + order.removeFirst(head) + head = 0 + } + guard let liveEntry = map[candidate.key], liveEntry.generation == candidate.generation else { - if staleNodeCount > 0 { - staleNodeCount -= 1 - } continue } @@ -116,24 +110,6 @@ final class LRUDeduplicationCache { } return false } - - private func compactIfNeeded() { - let unconsumedCount = order.count - head - let shouldDropConsumedPrefix = head >= 32 && head * 2 >= order.count - let shouldRemoveStaleNodes = staleNodeCount >= 32 && staleNodeCount * 2 >= unconsumedCount - - guard shouldDropConsumedPrefix || shouldRemoveStaleNodes else { return } - - if shouldRemoveStaleNodes { - order = order[head...].filter { candidate in - map[candidate.key]?.generation == candidate.generation - } - staleNodeCount = 0 - } else { - order.removeFirst(head) - } - head = 0 - } } // MARK: - Content Normalizer diff --git a/bitchatTests/MessageDeduplicationServiceTests.swift b/bitchatTests/MessageDeduplicationServiceTests.swift index 3d5f0bc4..5028b614 100644 --- a/bitchatTests/MessageDeduplicationServiceTests.swift +++ b/bitchatTests/MessageDeduplicationServiceTests.swift @@ -166,31 +166,6 @@ struct LRUDeduplicationCacheTests { #expect(cache.contains("c")) } - @Test func repeatedRemovalAndReinsertionCompactsStaleNodes() { - let cache = LRUDeduplicationCache(capacity: 3) - cache.record("a", value: 0) - - for value in 1...1_001 { - cache.remove("a") - cache.record("a", value: value) - } - - #expect(cache.count == 1) - #expect(cache.value(for: "a") == 1_001) - #if DEBUG - #expect(cache._orderStorageCountForTesting <= 32) - #endif - - cache.record("b", value: 1) - cache.record("c", value: 2) - cache.record("d", value: 3) - - #expect(!cache.contains("a")) - #expect(cache.contains("b")) - #expect(cache.contains("c")) - #expect(cache.contains("d")) - } - // MARK: - Edge Cases @Test func emptyKey_works() {