Skip to content

Commit 2b0ca73

Browse files
committed
fix: add Experimental GC
1 parent 0d3b0d0 commit 2b0ca73

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

cache.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package libcache
33

44
import (
5+
"context"
56
"sync"
67
"time"
78

@@ -81,6 +82,58 @@ type Cache interface {
8182
GC() time.Duration
8283
}
8384

85+
// GC runs a garbage collection to evict expired items from the cache on time.
86+
//
87+
// GC trace expired items based on read-write barrier, therefore it listen to
88+
// cache write events and capture the result of calling the GC method on cache
89+
// to trigger the garbage collection loop at the right point in time.
90+
//
91+
// GC is a long running function, it returns when ctx done, therefore the
92+
// caller must start it in its own goroutine.
93+
//
94+
// Experimental
95+
//
96+
// Notice: This func is EXPERIMENTAL and may be changed or removed in a
97+
// later release.
98+
func GC(ctx context.Context, cache Cache) {
99+
remaining := time.Duration(0)
100+
101+
t := time.NewTimer(remaining)
102+
defer t.Stop()
103+
104+
c := make(chan Event, 1)
105+
cache.Notify(c, Write)
106+
defer func() {
107+
cache.Ignore(c)
108+
close(c)
109+
}()
110+
111+
gc := func() {
112+
remaining = cache.GC()
113+
t.Stop()
114+
if remaining > 0 {
115+
t.Reset(remaining)
116+
}
117+
}
118+
119+
for {
120+
select {
121+
case e := <-c:
122+
if e.Expiry.IsZero() {
123+
continue
124+
}
125+
126+
if remaining == 0 || time.Until(e.Expiry) < remaining {
127+
gc()
128+
}
129+
case <-t.C:
130+
gc()
131+
case <-ctx.Done():
132+
return
133+
}
134+
}
135+
}
136+
84137
type cache struct {
85138
// mu guards unsafe cache.
86139
// Calls to mu.Unlock are currently not deferred,

cache_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package libcache_test
22

33
import (
4+
"context"
45
"fmt"
56
"math/rand"
67
"testing"
@@ -321,7 +322,7 @@ func TestNotify(t *testing.T) {
321322
}
322323
}
323324

324-
func TestGC(t *testing.T) {
325+
func TestCacheGC(t *testing.T) {
325326
for _, tt := range cacheTests {
326327
t.Run("Test"+tt.cont.String()+"CacheGC", func(t *testing.T) {
327328
cache := tt.cont.NewUnsafe(0)
@@ -338,6 +339,27 @@ func TestGC(t *testing.T) {
338339
}
339340
}
340341

342+
func TestGC(t *testing.T) {
343+
ctx, cancel := context.WithCancel(context.Background())
344+
defer cancel()
345+
346+
cache := libcache.LRU.New(0)
347+
go libcache.GC(ctx, cache)
348+
349+
cache.StoreWithTTL(1, 1, time.Millisecond*100)
350+
time.Sleep(time.Millisecond * 150)
351+
assert.Zero(t, cache.Len())
352+
353+
cache.StoreWithTTL(1, 1, time.Millisecond*100)
354+
cache.StoreWithTTL(2, 2, time.Millisecond*200)
355+
356+
time.Sleep(time.Millisecond * 150)
357+
assert.Equal(t, 1, cache.Len())
358+
359+
time.Sleep(time.Millisecond * 150)
360+
assert.Zero(t, cache.Len())
361+
}
362+
341363
func BenchmarkCache(b *testing.B) {
342364
for _, tt := range cacheTests {
343365
b.Run("Benchmark"+tt.cont.String()+"Cache", func(b *testing.B) {

0 commit comments

Comments
 (0)