Bound stale dedup order storage

This commit is contained in:
vekovius 2026-08-18 22:01:29 -05:00
parent 4816a13244
commit c28dc110cc
2 changed files with 48 additions and 8 deletions

View File

@ -43,6 +43,10 @@ final class LRUDeduplicationCache<Value> {
map.count
}
var orderStorageCountForTesting: Int {
order.count
}
/// Checks if a key exists in the cache
func contains(_ key: String) -> Bool {
map[key] != nil
@ -65,11 +69,13 @@ final class LRUDeduplicationCache<Value> {
}
trimIfNeeded()
compactIfNeeded()
}
/// Removes a specific key from the cache
func remove(_ key: String) {
map.removeValue(forKey: key)
guard map.removeValue(forKey: key) != nil else { return }
compactIfNeeded()
}
/// Clears all entries from the cache
@ -93,13 +99,6 @@ final class LRUDeduplicationCache<Value> {
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 {
continue
@ -110,6 +109,24 @@ final class LRUDeduplicationCache<Value> {
}
return false
}
private func compactIfNeeded() {
let unconsumedCount = order.count - head
let staleNodeCount = unconsumedCount - map.count
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
}
} else {
order.removeFirst(head)
}
head = 0
}
}
// MARK: - Content Normalizer

View File

@ -166,6 +166,29 @@ struct LRUDeduplicationCacheTests {
#expect(cache.contains("c"))
}
@Test func repeatedRemovalAndReinsertionCompactsStaleNodes() {
let cache = LRUDeduplicationCache<Int>(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)
#expect(cache.orderStorageCountForTesting <= 32)
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() {