11package cache
22
33import (
4+ "runtime"
45 "sync"
56 "time"
67
@@ -12,6 +13,15 @@ import (
1213 "github.com/Code-Hex/go-generics-cache/policy/simple"
1314)
1415
16+ // janitor for collecting expired items and cleaning them
17+ // this object is inspired from
18+ // https://github.com/patrickmn/go-cache/blob/46f407853014144407b6c2ec7ccc76bf67958d93/cache.go
19+ // many thanks to go-cache project
20+ type janitor struct {
21+ Interval time.Duration
22+ stop chan bool
23+ }
24+
1525// Interface is a common-cache interface.
1626type Interface [K comparable , V any ] interface {
1727 Get (key K ) (value V , ok bool )
3545type Item [K comparable , V any ] struct {
3646 Key K
3747 Value V
38- Expiration time. Duration
48+ Expiration int64
3949}
4050
4151var nowFunc = time .Now
@@ -44,14 +54,23 @@ var nowFunc = time.Now
4454type ItemOption func (* itemOptions )
4555
4656type itemOptions struct {
47- expiration time.Duration // default none
57+ expiration int64 // default none
58+ }
59+
60+ // Expired returns true if the item has expired.
61+ func (item itemOptions ) Expired () bool {
62+ if item .expiration == 0 {
63+ return false
64+ }
65+
66+ return nowFunc ().UnixNano () > item .expiration
4867}
4968
5069// WithExpiration is an option to set expiration time for any items.
5170// If the expiration is zero or negative value, it treats as w/o expiration.
5271func WithExpiration (exp time.Duration ) ItemOption {
5372 return func (o * itemOptions ) {
54- o .expiration = exp
73+ o .expiration = nowFunc (). Add ( exp ). UnixNano ()
5574 }
5675}
5776
@@ -70,10 +89,11 @@ func newItem[K comparable, V any](key K, val V, opts ...ItemOption) *Item[K, V]
7089
7190// Cache is a thread safe cache.
7291type Cache [K comparable , V any ] struct {
73- cache Interface [K , * Item [K , V ]]
74- expirations map [K ]chan struct {}
92+ cache Interface [K , * Item [K , V ]]
93+ // expirations map[K]chan struct{}
7594 // mu is used to do lock in some method process.
76- mu sync.Mutex
95+ mu sync.Mutex
96+ janitor * janitor
7797}
7898
7999// Option is an option for cache.
@@ -132,23 +152,77 @@ func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {
132152 for _ , optFunc := range opts {
133153 optFunc (o )
134154 }
135- return & Cache [K , V ]{
136- cache : o .cache ,
137- expirations : make (map [K ]chan struct {}, 0 ),
155+
156+ cache := & Cache [K , V ]{
157+ cache : o .cache ,
158+ }
159+
160+ // @TODO change the ticker timer default value
161+ cache .runJanitor (cache , time .Minute )
162+ runtime .SetFinalizer (cache , cache .stopJanitor )
163+
164+ return cache
165+ }
166+
167+ func (_ * Cache [K , V ]) stopJanitor (c * Cache [K , V ]) {
168+ if c .janitor != nil {
169+ c .janitor .stop <- true
170+ }
171+
172+ c .janitor = nil
173+ }
174+
175+ func (_ * Cache [K , V ]) runJanitor (c * Cache [K , V ], ci time.Duration ) {
176+ c .stopJanitor (c )
177+
178+ j := & janitor {
179+ Interval : ci ,
180+ stop : make (chan bool ),
138181 }
182+
183+ c .janitor = j
184+
185+ go func () {
186+ ticker := time .NewTicker (j .Interval )
187+ for {
188+ select {
189+ case <- ticker .C :
190+ c .DeleteExpired ()
191+ case <- j .stop :
192+ ticker .Stop ()
193+ return
194+ }
195+ }
196+ }()
139197}
140198
141199// Get looks up a key's value from the cache.
142200func (c * Cache [K , V ]) Get (key K ) (value V , ok bool ) {
143201 c .mu .Lock ()
144202 defer c .mu .Unlock ()
145203 item , ok := c .cache .Get (key )
204+
146205 if ! ok {
147206 return
148207 }
208+
209+ // if is expired, delete is and return nil instead
210+ if item .Expiration > 0 && nowFunc ().UnixNano () > item .Expiration {
211+ c .cache .Delete (key )
212+ return value , false
213+ }
214+
149215 return item .Value , true
150216}
151217
218+ // DeleteExpired all expired items from the cache.
219+ func (c * Cache [K , V ]) DeleteExpired () {
220+ for _ , keys := range c .cache .Keys () {
221+ // delete all expired items by using get method
222+ _ , _ = c .Get (keys )
223+ }
224+ }
225+
152226// Set sets a value to the cache with key. replacing any existing value.
153227func (c * Cache [K , V ]) Set (key K , val V , opts ... ItemOption ) {
154228 c .mu .Lock ()
@@ -159,30 +233,7 @@ func (c *Cache[K, V]) Set(key K, val V, opts ...ItemOption) {
159233 return
160234 }
161235
162- if _ , ok := c .cache .Get (key ); ok {
163- c .doneWatchExpiration (key )
164- }
165-
166236 c .cache .Set (key , item )
167- c .installExpirationWatcher (item .Key , item .Expiration )
168- }
169-
170- func (c * Cache [K , V ]) installExpirationWatcher (key K , exp time.Duration ) {
171- done := make (chan struct {})
172- c .expirations [key ] = done
173- go func () {
174- select {
175- case <- time .After (exp ):
176- c .Delete (key )
177- case <- done :
178- }
179- }()
180- }
181-
182- func (c * Cache [K , V ]) doneWatchExpiration (key K ) {
183- if ch , ok := c .expirations [key ]; ok {
184- close (ch )
185- }
186237}
187238
188239// Keys returns the keys of the cache. the order is relied on algorithms.
0 commit comments