@@ -3,6 +3,7 @@ package sc
33import (
44 "runtime"
55 "time"
6+ "weak"
67)
78
89// cleaner is launched as a single goroutine to regularly clean up expired items from the cache.
@@ -11,16 +12,24 @@ import (
1112// See https://github.com/patrickmn/go-cache/blob/46f407853014144407b6c2ec7ccc76bf67958d93/cache.go#L1115 for more on this design.
1213type cleaner [K comparable , V any ] struct {
1314 closer chan struct {}
14- c * cache [K , V ]
15+ // We use weak pointer here in order to deal with an extremely-unlikely case where cached data itself
16+ // somehow has a reference to *Cache itself, forming a reference cycle.
17+ // If above is the case, and we're using strong reference here, the cleaner goroutine keeps a reference to this
18+ // reference cycle, therefore *Cache and *cache never being evicted, leading to memory leaks.
19+ //
20+ // This case can be tested in TestCleaningCacheFinalizer, where testers can manually check that stopCleaner function
21+ // is called, for example by adding `fmt.Println("cleanup called")` there.
22+ c weak.Pointer [cache [K , V ]]
1523}
1624
1725func startCleaner [K comparable , V any ](c * Cache [K , V ], interval time.Duration ) {
26+ closer := make (chan struct {})
1827 cl := & cleaner [K , V ]{
19- closer : make ( chan struct {}) ,
20- c : c .cache ,
28+ closer : closer ,
29+ c : weak . Make ( c .cache ) ,
2130 }
2231 go cl .run (interval )
23- runtime .SetFinalizer (c , stopCleaner ( cl ) )
32+ runtime .AddCleanup (c , stopCleaner , closer )
2433}
2534
2635func (cl * cleaner [K , V ]) run (interval time.Duration ) {
@@ -29,19 +38,17 @@ func (cl *cleaner[K, V]) run(interval time.Duration) {
2938 for {
3039 select {
3140 case <- ticker .C :
32- cl .c .cleanup ()
41+ c := cl .c .Value ()
42+ if c == nil {
43+ return
44+ }
45+ c .cleanup ()
3346 case <- cl .closer :
3447 return
3548 }
3649 }
3750}
3851
39- func (cl * cleaner [K , V ]) stop () {
40- cl .closer <- struct {}{}
41- }
42-
43- func stopCleaner [K comparable , V any ](cl * cleaner [K , V ]) func (* Cache [K , V ]) {
44- return func (_ * Cache [K , V ]) {
45- cl .stop ()
46- }
52+ func stopCleaner (closer chan <- struct {}) {
53+ close (closer )
4754}
0 commit comments