diff --git a/README.md b/README.md index 6a9b556..eca7a5e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,10 @@ import ( ) func main() { + // Create a cache with a default expiration time of 5 minutes, and which + // purges expired items every 10 minutes c := cache.New(5*time.Minute, 10*time.Minute) + // Set the value of the key "key1" to "value1", with the default expiration time c.Set("key1", "value1", cache.DefaultExpiration) val, found := c.Get("key1") @@ -91,6 +94,8 @@ import ( ) func main() { + // Create a sharded cache with a default expiration time of 5 minutes, and which + // purges expired items every 10 minutes sc := cache.NewSharded(5*time.Minute, 10*time.Minute, 10) sc.Set("key1", "value1", cache.DefaultExpiration) diff --git a/cache_test.go b/cache_test.go index 9ba868e..4ca35ff 100644 --- a/cache_test.go +++ b/cache_test.go @@ -23,13 +23,13 @@ func TestCache_Set(t *testing.T) { }) t.Run("Set item with custom expiration", func(t *testing.T) { - cache.Set("key3", "value3", 1*time.Second) + cache.Set("key3", "value3", 100*time.Millisecond) if v, found := cache.Get("key3"); !found || v != "value3" { t.Errorf("Expected to find key3 with value 'value3', got %v", v) } // Wait for key3 to expire - time.Sleep(2 * time.Second) + time.Sleep(200 * time.Millisecond) if _, found := cache.Get("key3"); found { t.Error("Expected key3 to be expired") } @@ -77,7 +77,7 @@ func TestCache_Add(t *testing.T) { }) t.Run("Add item with custom expiration", func(t *testing.T) { - err := cache.Add("key3", "value3", 1*time.Second) + err := cache.Add("key3", "value3", 100*time.Millisecond) if err != nil { t.Errorf("Expected no error, got %v", err) } @@ -86,7 +86,7 @@ func TestCache_Add(t *testing.T) { } // Wait for key3 to expire - time.Sleep(2 * time.Second) + time.Sleep(200 * time.Millisecond) if _, found := cache.Get("key3"); found { t.Error("Expected key3 to be expired") } @@ -115,8 +115,8 @@ func TestCache_Replace(t *testing.T) { }) t.Run("Replace expired item", func(t *testing.T) { - cache.Set("key3", "value3", 1*time.Second) - time.Sleep(2 * time.Second) + cache.Set("key3", "value3", 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) err := cache.Replace("key3", "newValue3", DefaultExpiration) if err == nil { t.Error("Expected error when replacing expired item, got nil") @@ -143,8 +143,8 @@ func TestCache_Get(t *testing.T) { }) t.Run("Get expired item", func(t *testing.T) { - cache.Set("key3", "value3", 1*time.Second) - time.Sleep(2 * time.Second) + cache.Set("key3", "value3", 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) _, found := cache.Get("key3") if found { t.Error("Expected not to find an expired item, but found one") @@ -171,8 +171,8 @@ func TestCache_GetWithExpiration(t *testing.T) { }) t.Run("GetWithExpiration expired item", func(t *testing.T) { - cache.Set("key3", "value3", 1*time.Second) - time.Sleep(2 * time.Second) + cache.Set("key3", "value3", 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) _, _, found := cache.GetWithExpiration("key3") if found { t.Error("Expected not to find an expired item, but found one") @@ -222,8 +222,8 @@ func TestCache_DeleteExpired(t *testing.T) { }) t.Run("DeleteExpired with expired items", func(t *testing.T) { - cache.Set("key2", "value2", 1*time.Second) - time.Sleep(2 * time.Second) + cache.Set("key2", "value2", 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) cache.DeleteExpired() _, found := cache.Get("key2") if found { diff --git a/decrement_test.go b/decrement_test.go index 0e094a7..c163cea 100644 --- a/decrement_test.go +++ b/decrement_test.go @@ -34,8 +34,8 @@ func TestCache_Decrement(t *testing.T) { // Caso: Decrement int with expiration t.Run("Decrement int with expiration", func(t *testing.T) { - cache.Set("intExpKey", 10, 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("intExpKey", 10, 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire err := cache.Decrement("intExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -109,8 +109,8 @@ func TestCache_DecrementFloat(t *testing.T) { }) t.Run("Decrement float with expiration", func(t *testing.T) { - cache.Set("floatExpKey", float64(20.5), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("floatExpKey", float64(20.5), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire err := cache.DecrementFloat("floatExpKey", 10.5) if err == nil { t.Fatalf("Expected error, got nil") @@ -155,8 +155,8 @@ func TestCache_DecrementInt(t *testing.T) { }) t.Run("Decrement int with expiration", func(t *testing.T) { - cache.Set("intExpKey", 20, 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("intExpKey", 20, 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementInt("intExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -201,8 +201,8 @@ func TestCache_DecrementInt8(t *testing.T) { }) t.Run("Decrement int8 with expiration", func(t *testing.T) { - cache.Set("int8ExpKey", int8(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("int8ExpKey", int8(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementInt8("int8ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -247,8 +247,8 @@ func TestCache_DecrementInt16(t *testing.T) { }) t.Run("Decrement int16 with expiration", func(t *testing.T) { - cache.Set("int16ExpKey", int16(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("int16ExpKey", int16(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementInt16("int16ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -293,8 +293,8 @@ func TestCache_DecrementInt32(t *testing.T) { }) t.Run("Decrement int32 with expiration", func(t *testing.T) { - cache.Set("int32ExpKey", int32(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("int32ExpKey", int32(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementInt32("int32ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -339,8 +339,8 @@ func TestCache_DecrementInt64(t *testing.T) { }) t.Run("Decrement int64 with expiration", func(t *testing.T) { - cache.Set("int64ExpKey", int64(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("int64ExpKey", int64(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementInt64("int64ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -388,8 +388,8 @@ func TestCache_DecrementUint(t *testing.T) { // Caso: Decrement uint with expiration t.Run("Decrement uint with expiration", func(t *testing.T) { - cache.Set("uintExpKey", uint(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("uintExpKey", uint(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementUint("uintExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -438,8 +438,8 @@ func TestCache_DecrementUintptr(t *testing.T) { // Caso: Decrement uintptr with expiration t.Run("Decrement uintptr with expiration", func(t *testing.T) { - cache.Set("uintptrExpKey", uintptr(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("uintptrExpKey", uintptr(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementUintptr("uintptrExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -486,8 +486,8 @@ func TestCache_DecrementUint8(t *testing.T) { }) t.Run("Decrement uint8 with expiration", func(t *testing.T) { - cache.Set("uint8ExpKey", uint8(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("uint8ExpKey", uint8(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementUint8("uint8ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -532,8 +532,8 @@ func TestCache_DecrementUint16(t *testing.T) { }) t.Run("Decrement uint16 with expiration", func(t *testing.T) { - cache.Set("uint16ExpKey", uint16(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("uint16ExpKey", uint16(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementUint16("uint16ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -578,8 +578,8 @@ func TestCache_DecrementUint32(t *testing.T) { }) t.Run("Decrement uint32 with expiration", func(t *testing.T) { - cache.Set("uint32ExpKey", uint32(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("uint32ExpKey", uint32(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementUint32("uint32ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -624,8 +624,8 @@ func TestCache_DecrementUint64(t *testing.T) { }) t.Run("Decrement uint64 with expiration", func(t *testing.T) { - cache.Set("uint64ExpKey", uint64(20), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("uint64ExpKey", uint64(20), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementUint64("uint64ExpKey", 5) if err == nil { t.Fatalf("Expected error, got nil") @@ -670,8 +670,8 @@ func TestCache_DecrementFloat32(t *testing.T) { }) t.Run("Decrement float32 with expiration", func(t *testing.T) { - cache.Set("float32ExpKey", float32(20.5), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("float32ExpKey", float32(20.5), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementFloat32("float32ExpKey", 2.5) if err == nil { t.Fatalf("Expected error, got nil") @@ -717,8 +717,8 @@ func TestCache_DecrementFloat64(t *testing.T) { }) t.Run("Decrement float64 with expiration", func(t *testing.T) { - cache.Set("float64ExpKey", float64(20.5), 1*time.Second) - time.Sleep(2 * time.Second) // Espera para que expire + cache.Set("float64ExpKey", float64(20.5), 100*time.Millisecond) + time.Sleep(200 * time.Millisecond) // Espera para que expire _, err := cache.DecrementFloat64("float64ExpKey", 2.5) if err == nil { t.Fatalf("Expected error, got nil") diff --git a/item_test.go b/item_test.go index 9fe9d1f..2a3bc33 100644 --- a/item_test.go +++ b/item_test.go @@ -42,10 +42,10 @@ func TestCache_Items(t *testing.T) { cache.Set("key1", "value1", DefaultExpiration) cache.Set("key2", "value2", DefaultExpiration) - cache.Set("key3", "value3", 1*time.Second) + cache.Set("key3", "value3", 100*time.Millisecond) // Esperar a que caduque key3 - time.Sleep(2 * time.Second) + time.Sleep(200 * time.Millisecond) items := cache.Items() if len(items) != 2 { @@ -68,10 +68,10 @@ func TestCache_ItemCount(t *testing.T) { cache.Set("key1", "value1", DefaultExpiration) cache.Set("key2", "value2", DefaultExpiration) - cache.Set("key3", "value3", 1*time.Second) + cache.Set("key3", "value3", 100*time.Millisecond) // Esperar a que caduque key3 - time.Sleep(2 * time.Second) + time.Sleep(200 * time.Millisecond) count := cache.ItemCount() if count != 3 { diff --git a/sharded_cache_janitor.go b/sharded_cache_janitor.go index 32ec9f4..0322112 100644 --- a/sharded_cache_janitor.go +++ b/sharded_cache_janitor.go @@ -1,10 +1,13 @@ package cache -import "time" +import ( + "runtime" + "time" +) type shardedJanitor struct { Interval time.Duration - stop chan bool + stop chan struct{} } func (j *shardedJanitor) Run(sc *shardedCache) { @@ -20,15 +23,23 @@ func (j *shardedJanitor) Run(sc *shardedCache) { } } +// Stop sends a signal to stop the janitor's Run loop and waits for it to finish +func (j *shardedJanitor) Stop() { + close(j.stop) +} + func stopShardedJanitor(sc *unexportedShardedCache) { - sc.janitor.stop <- true + sc.janitor.Stop() } func runShardedJanitor(sc *shardedCache, ci time.Duration) { j := &shardedJanitor{ Interval: ci, - stop: make(chan bool), + stop: make(chan struct{}), } sc.janitor = j go j.Run(sc) + runtime.SetFinalizer(sc, func(sc *shardedCache) { + sc.janitor.Stop() + }) } diff --git a/sharded_cache_janitor_test.go b/sharded_cache_janitor_test.go index 42b2b92..8e9de60 100644 --- a/sharded_cache_janitor_test.go +++ b/sharded_cache_janitor_test.go @@ -15,13 +15,11 @@ func TestShardedJanitor(t *testing.T) { assert.NotNil(t, sc.janitor) assert.Equal(t, 1*time.Millisecond, sc.janitor.Interval) - // Allow time for the janitor to run and delete expired items - time.Sleep(5 * time.Millisecond) - - // Ensure janitor is running by checking if DeleteExpired is called sc.Set("key1", "value1", 1*time.Millisecond) time.Sleep(2 * time.Millisecond) - sc.janitor.Run(sc) + + time.Sleep(5 * time.Millisecond) + _, found := sc.Get("key1") assert.False(t, found) @@ -35,12 +33,13 @@ func TestShardedJanitor(t *testing.T) { assert.NotNil(t, sc.janitor) assert.Equal(t, 1*time.Millisecond, sc.janitor.Interval) - // Ensure the janitor stops stopShardedJanitor(&unexportedShardedCache{sc}) + + time.Sleep(10 * time.Millisecond) + select { case <-sc.janitor.stop: - // Success - case <-time.After(1 * time.Second): + default: t.Fatal("Janitor did not stop in time") } }) diff --git a/sharded_cache_test.go b/sharded_cache_test.go index 7849e52..c4dd721 100644 --- a/sharded_cache_test.go +++ b/sharded_cache_test.go @@ -142,9 +142,22 @@ func TestShardedCache_Items(t *testing.T) { items := sc.Items() - assert.Len(t, items, 2) - assert.Equal(t, "value1", items[0]["key1"].Object) - assert.Equal(t, "value2", items[1]["key2"].Object) + foundKey1 := false + foundKey2 := false + + for _, shardItems := range items { + if item, ok := shardItems["key1"]; ok { + assert.Equal(t, "value1", item.Object) + foundKey1 = true + } + if item, ok := shardItems["key2"]; ok { + assert.Equal(t, "value2", item.Object) + foundKey2 = true + } + } + + assert.True(t, foundKey1) + assert.True(t, foundKey2) }) }