11package internal
22
33import (
4+ "container/heap"
45 "time"
56)
67
@@ -22,6 +23,7 @@ type Entry struct {
2223 Element interface {}
2324 Exp time.Time
2425 timer * time.Timer
26+ index int
2527 cancel chan struct {}
2628}
2729
@@ -50,6 +52,7 @@ func (e *Entry) stopTimer() {
5052// of the Cache interface to minimize the effort required to implement interface.
5153type Cache struct {
5254 coll Collection
55+ heap expiringHeap
5356 entries map [interface {}]* Entry
5457 onEvicted func (key , value interface {})
5558 onExpired func (key , value interface {})
@@ -68,16 +71,14 @@ func (c *Cache) Peek(key interface{}) (interface{}, bool) {
6871}
6972
7073func (c * Cache ) get (key interface {}, peek bool ) (v interface {}, found bool ) {
74+ // Run GC inline before return the entry.
75+ c .gc ()
76+
7177 e , ok := c .entries [key ]
7278 if ! ok {
7379 return
7480 }
7581
76- if ! e .Exp .IsZero () && time .Now ().UTC ().After (e .Exp ) {
77- c .evict (e )
78- return
79- }
80-
8182 if ! peek {
8283 c .coll .Move (e )
8384 }
@@ -101,6 +102,9 @@ func (c *Cache) Store(key, value interface{}) {
101102
102103// StoreWithTTL sets the key value with TTL overrides the default.
103104func (c * Cache ) StoreWithTTL (key , value interface {}, ttl time.Duration ) {
105+ // Run GC inline before pushing the new entry.
106+ c .gc ()
107+
104108 if e , ok := c .entries [key ]; ok {
105109 c .removeEntry (e )
106110 }
@@ -112,6 +116,7 @@ func (c *Cache) StoreWithTTL(key, value interface{}, ttl time.Duration) {
112116 e .startTimer (ttl , c .onExpired )
113117 }
114118 e .Exp = time .Now ().UTC ().Add (ttl )
119+ heap .Push (& c .heap , e )
115120 }
116121
117122 c .entries [key ] = e
@@ -205,6 +210,11 @@ func (c *Cache) removeEntry(e *Entry) {
205210 c .coll .Remove (e )
206211 e .stopTimer ()
207212 delete (c .entries , e .Key )
213+ // Remove entry from the heap, the entry may does not exist because
214+ // it has zero ttl or already popped up by gc
215+ if len (c .heap ) > 0 && e .index < len (c .heap ) && e .Key == c .heap [e .index ].Key {
216+ heap .Remove (& c .heap , e .index )
217+ }
208218}
209219
210220// evict remove entry and fire on evicted callback.
@@ -215,6 +225,19 @@ func (c *Cache) evict(e *Entry) {
215225 }
216226}
217227
228+ func (c * Cache ) gc () {
229+ now := time .Now ()
230+ for {
231+ // Return from gc if the heap is empty or the next element is not yet
232+ // expired
233+ if len (c .heap ) == 0 || now .Before (c .heap [0 ].Exp ) {
234+ return
235+ }
236+ e := heap .Pop (& c .heap ).(* Entry )
237+ c .removeEntry (e )
238+ }
239+ }
240+
218241// TTL returns entries default TTL.
219242func (c * Cache ) TTL () time.Duration {
220243 return c .ttl
@@ -250,3 +273,34 @@ func New(c Collection, cap int) *Cache {
250273 entries : make (map [interface {}]* Entry ),
251274 }
252275}
276+
277+ // expiringHeap is a min-heap ordered by expiration time of its entries. The
278+ // expiring cache uses this as a priority queue to efficiently organize entries
279+ // which will be garbage collected once they expire.
280+ type expiringHeap []* Entry
281+
282+ var _ heap.Interface = & expiringHeap {}
283+
284+ func (cq expiringHeap ) Len () int {
285+ return len (cq )
286+ }
287+
288+ func (cq expiringHeap ) Less (i , j int ) bool {
289+ return cq [i ].Exp .Before (cq [j ].Exp )
290+ }
291+
292+ func (cq expiringHeap ) Swap (i , j int ) {
293+ cq [i ].index , cq [j ].index = cq [j ].index , cq [i ].index
294+ cq [i ], cq [j ] = cq [j ], cq [i ]
295+ }
296+
297+ func (cq * expiringHeap ) Push (c interface {}) {
298+ c .(* Entry ).index = len (* cq )
299+ * cq = append (* cq , c .(* Entry ))
300+ }
301+
302+ func (cq * expiringHeap ) Pop () interface {} {
303+ c := (* cq )[cq .Len ()- 1 ]
304+ * cq = (* cq )[:cq .Len ()- 1 ]
305+ return c
306+ }
0 commit comments