From 0f41388bd78668ceae6d5c12b05868bd0ca8fd1f Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sat, 29 Mar 2025 23:24:06 -0400 Subject: Add periodic cleanup job for DecayMap (#8) (#158) * Add periodic cleanup job for DecayMap see https://github.com/TecharoHQ/anubis/issues/8 * Refactor: Improve DecayMap cleanup tests and add Len method - Refactored DecayMap cleanup tests to use the new Len method for more precise assertions. - Added a Len method to DecayMap to retrieve the number of entries. - Simplified conditional checks in Get method. * chore(changelog): add entry * fix(tests): Use Impl.expire for decaymap cleanup Signed-off-by: Jason Cameron --------- Signed-off-by: Jason Cameron --- decaymap/decaymap.go | 20 ++++++++++++++++++++ decaymap/decaymap_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) (limited to 'decaymap') diff --git a/decaymap/decaymap.go b/decaymap/decaymap.go index 7498bb5..57ee6c2 100644 --- a/decaymap/decaymap.go +++ b/decaymap/decaymap.go @@ -85,3 +85,23 @@ func (m *Impl[K, V]) Set(key K, value V, ttl time.Duration) { expiry: time.Now().Add(ttl), } } + +// Cleanup removes all expired entries from the DecayMap. +func (m *Impl[K, V]) Cleanup() { + m.lock.Lock() + defer m.lock.Unlock() + + now := time.Now() + for key, entry := range m.data { + if now.After(entry.expiry) { + delete(m.data, key) + } + } +} + +// Len returns the number of entries in the DecayMap. +func (m *Impl[K, V]) Len() int { + m.lock.RLock() + defer m.lock.RUnlock() + return len(m.data) +} diff --git a/decaymap/decaymap_test.go b/decaymap/decaymap_test.go index c930e08..c1830ed 100644 --- a/decaymap/decaymap_test.go +++ b/decaymap/decaymap_test.go @@ -29,3 +29,32 @@ func TestImpl(t *testing.T) { t.Error("got value even though it was supposed to be expired") } } + +func TestCleanup(t *testing.T) { + dm := New[string, string]() + + dm.Set("test1", "hi1", 1*time.Second) + dm.Set("test2", "hi2", 2*time.Second) + dm.Set("test3", "hi3", 3*time.Second) + + dm.expire("test1") // Force expire test1 + dm.expire("test2") // Force expire test2 + + dm.Cleanup() + + finalLen := dm.Len() // Get the length after cleanup + + if finalLen != 1 { // "test3" should be the only one left + t.Errorf("Cleanup failed to remove expired entries. Expected length 1, got %d", finalLen) + } + + if _, ok := dm.Get("test1"); ok { // Verify Get still behaves correctly after Cleanup + t.Error("test1 should not be found after cleanup") + } + if _, ok := dm.Get("test2"); ok { + t.Error("test2 should not be found after cleanup") + } + if val, ok := dm.Get("test3"); !ok || val != "hi3" { + t.Error("test3 should still be found after cleanup") + } +} -- cgit v1.2.3