1
1
package cache
2
2
3
3
import (
4
+ "context"
4
5
"runtime"
5
6
"sync"
6
7
"time"
@@ -13,15 +14,6 @@ import (
13
14
"github.com/Code-Hex/go-generics-cache/policy/simple"
14
15
)
15
16
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
-
25
17
// Interface is a common-cache interface.
26
18
type Interface [K comparable , V any ] interface {
27
19
Get (key K ) (value V , ok bool )
45
37
type Item [K comparable , V any ] struct {
46
38
Key K
47
39
Value V
48
- Expiration int64
40
+ Expiration time.Time
41
+ }
42
+
43
+ // Expired returns true if the item has expired.
44
+ func (item * Item [K , V ]) Expired () bool {
45
+ if item .Expiration .IsZero () {
46
+ return false
47
+ }
48
+ return nowFunc ().After (item .Expiration )
49
49
}
50
50
51
51
var nowFunc = time .Now
@@ -54,23 +54,14 @@ var nowFunc = time.Now
54
54
type ItemOption func (* itemOptions )
55
55
56
56
type itemOptions struct {
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
57
+ expiration time.Time // default none
67
58
}
68
59
69
60
// WithExpiration is an option to set expiration time for any items.
70
61
// If the expiration is zero or negative value, it treats as w/o expiration.
71
62
func WithExpiration (exp time.Duration ) ItemOption {
72
63
return func (o * itemOptions ) {
73
- o .expiration = nowFunc ().Add (exp ). UnixNano ()
64
+ o .expiration = nowFunc ().Add (exp )
74
65
}
75
66
}
76
67
@@ -100,12 +91,14 @@ type Cache[K comparable, V any] struct {
100
91
type Option [K comparable , V any ] func (* options [K , V ])
101
92
102
93
type options [K comparable , V any ] struct {
103
- cache Interface [K , * Item [K , V ]]
94
+ cache Interface [K , * Item [K , V ]]
95
+ janitorInterval time.Duration
104
96
}
105
97
106
98
func newOptions [K comparable , V any ]() * options [K , V ] {
107
99
return & options [K , V ]{
108
- cache : simple .NewCache [K , * Item [K , V ]](),
100
+ cache : simple .NewCache [K , * Item [K , V ]](),
101
+ janitorInterval : time .Minute ,
109
102
}
110
103
}
111
104
@@ -144,56 +137,48 @@ func AsClock[K comparable, V any](opts ...clock.Option) Option[K, V] {
144
137
}
145
138
}
146
139
140
+ // WithJanitorInterval is an option to specify how often cache should delete expired items.
141
+ //
142
+ // Default is 1 minute.
143
+ func WithJanitorInterval [K comparable , V any ](d time.Duration ) Option [K , V ] {
144
+ return func (o * options [K , V ]) {
145
+ o .janitorInterval = d
146
+ }
147
+ }
148
+
147
149
// New creates a new thread safe Cache.
150
+ // This function will be stopped an internal janitor when the cache is
151
+ // no longer referenced anywhere.
148
152
//
149
153
// There are several Cache replacement policies available with you specified any options.
150
154
func New [K comparable , V any ](opts ... Option [K , V ]) * Cache [K , V ] {
151
155
o := newOptions [K , V ]()
152
156
for _ , optFunc := range opts {
153
157
optFunc (o )
154
158
}
155
-
159
+ ctx , cancel := context . WithCancel ( context . Background ())
156
160
cache := & Cache [K , V ]{
157
- cache : o .cache ,
161
+ cache : o .cache ,
162
+ janitor : newJanitor (ctx , o .janitorInterval ),
158
163
}
159
-
160
- // @TODO change the ticker timer default value
161
- cache .runJanitor (cache , time .Minute )
162
- runtime .SetFinalizer (cache , cache .stopJanitor )
163
-
164
+ runtime .SetFinalizer (cache , func (self * Cache [K , V ]) {
165
+ cancel ()
166
+ })
164
167
return cache
165
168
}
166
169
167
- func (_ * Cache [K , V ]) stopJanitor (c * Cache [K , V ]) {
168
- if c .janitor != nil {
169
- c .janitor .stop <- true
170
+ // NewContext creates a new thread safe Cache with context.
171
+ //
172
+ // There are several Cache replacement policies available with you specified any options.
173
+ func NewContext [K comparable , V any ](ctx context.Context , opts ... Option [K , V ]) * Cache [K , V ] {
174
+ o := newOptions [K , V ]()
175
+ for _ , optFunc := range opts {
176
+ optFunc (o )
170
177
}
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 ),
178
+ return & Cache [K , V ]{
179
+ cache : o .cache ,
180
+ janitor : newJanitor (ctx , o .janitorInterval ),
181
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
- }()
197
182
}
198
183
199
184
// Get looks up a key's value from the cache.
@@ -206,9 +191,9 @@ func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
206
191
return
207
192
}
208
193
209
- // if is expired, delete is and return nil instead
210
- if item . Expiration > 0 && nowFunc (). UnixNano () > item . Expiration {
211
- c . cache . Delete ( key )
194
+ // Returns nil if the item has been expired.
195
+ // Do not delete here and leave it to an external process such as Janitor.
196
+ if item . Expired () {
212
197
return value , false
213
198
}
214
199
@@ -217,9 +202,12 @@ func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
217
202
218
203
// DeleteExpired all expired items from the cache.
219
204
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 )
205
+ for _ , key := range c .cache .Keys () {
206
+ // if is expired, delete it and return nil instead
207
+ item , ok := c .cache .Get (key )
208
+ if ok && item .Expired () {
209
+ c .cache .Delete (key )
210
+ }
223
211
}
224
212
}
225
213
@@ -228,11 +216,6 @@ func (c *Cache[K, V]) Set(key K, val V, opts ...ItemOption) {
228
216
c .mu .Lock ()
229
217
defer c .mu .Unlock ()
230
218
item := newItem (key , val , opts ... )
231
- if item .Expiration <= 0 {
232
- c .cache .Set (key , item )
233
- return
234
- }
235
-
236
219
c .cache .Set (key , item )
237
220
}
238
221
0 commit comments