1
1
package cache
2
2
3
3
import (
4
+ "runtime"
4
5
"sync"
5
6
"time"
6
7
@@ -12,6 +13,15 @@ import (
12
13
"github.com/Code-Hex/go-generics-cache/policy/simple"
13
14
)
14
15
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
+
15
25
// Interface is a common-cache interface.
16
26
type Interface [K comparable , V any ] interface {
17
27
Get (key K ) (value V , ok bool )
35
45
type Item [K comparable , V any ] struct {
36
46
Key K
37
47
Value V
38
- Expiration time. Duration
48
+ Expiration int64
39
49
}
40
50
41
51
var nowFunc = time .Now
@@ -44,14 +54,23 @@ var nowFunc = time.Now
44
54
type ItemOption func (* itemOptions )
45
55
46
56
type 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
48
67
}
49
68
50
69
// WithExpiration is an option to set expiration time for any items.
51
70
// If the expiration is zero or negative value, it treats as w/o expiration.
52
71
func WithExpiration (exp time.Duration ) ItemOption {
53
72
return func (o * itemOptions ) {
54
- o .expiration = exp
73
+ o .expiration = nowFunc (). Add ( exp ). UnixNano ()
55
74
}
56
75
}
57
76
@@ -70,10 +89,11 @@ func newItem[K comparable, V any](key K, val V, opts ...ItemOption) *Item[K, V]
70
89
71
90
// Cache is a thread safe cache.
72
91
type 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{}
75
94
// mu is used to do lock in some method process.
76
- mu sync.Mutex
95
+ mu sync.Mutex
96
+ janitor * janitor
77
97
}
78
98
79
99
// Option is an option for cache.
@@ -132,23 +152,77 @@ func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {
132
152
for _ , optFunc := range opts {
133
153
optFunc (o )
134
154
}
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 ),
138
181
}
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
+ }()
139
197
}
140
198
141
199
// Get looks up a key's value from the cache.
142
200
func (c * Cache [K , V ]) Get (key K ) (value V , ok bool ) {
143
201
c .mu .Lock ()
144
202
defer c .mu .Unlock ()
145
203
item , ok := c .cache .Get (key )
204
+
146
205
if ! ok {
147
206
return
148
207
}
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
+
149
215
return item .Value , true
150
216
}
151
217
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
+
152
226
// Set sets a value to the cache with key. replacing any existing value.
153
227
func (c * Cache [K , V ]) Set (key K , val V , opts ... ItemOption ) {
154
228
c .mu .Lock ()
@@ -159,30 +233,7 @@ func (c *Cache[K, V]) Set(key K, val V, opts ...ItemOption) {
159
233
return
160
234
}
161
235
162
- if _ , ok := c .cache .Get (key ); ok {
163
- c .doneWatchExpiration (key )
164
- }
165
-
166
236
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
- }
186
237
}
187
238
188
239
// Keys returns the keys of the cache. the order is relied on algorithms.
0 commit comments