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,16 @@ 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
+ ctx context.Context
95
+ cache Interface [K , * Item [K , V ]]
96
+ janitorInterval time.Duration
104
97
}
105
98
106
99
func newOptions [K comparable , V any ]() * options [K , V ] {
107
100
return & options [K , V ]{
108
- cache : simple .NewCache [K , * Item [K , V ]](),
101
+ ctx : context .Background (),
102
+ cache : simple .NewCache [K , * Item [K , V ]](),
103
+ janitorInterval : time .Minute ,
109
104
}
110
105
}
111
106
@@ -144,6 +139,15 @@ func AsClock[K comparable, V any](opts ...clock.Option) Option[K, V] {
144
139
}
145
140
}
146
141
142
+ // WithJanitorInterval is an option to specify how often cache should delete expired items.
143
+ //
144
+ // Default is 1 minute.
145
+ func WithJanitorInterval [K comparable , V any ](d time.Duration ) Option [K , V ] {
146
+ return func (o * options [K , V ]) {
147
+ o .janitorInterval = d
148
+ }
149
+ }
150
+
147
151
// New creates a new thread safe Cache.
148
152
//
149
153
// There are several Cache replacement policies available with you specified any options.
@@ -152,48 +156,33 @@ func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {
152
156
for _ , optFunc := range opts {
153
157
optFunc (o )
154
158
}
155
-
156
159
cache := & Cache [K , V ]{
157
160
cache : o .cache ,
158
161
}
159
-
160
- // @TODO change the ticker timer default value
161
- cache .runJanitor (cache , time .Minute )
162
- runtime .SetFinalizer (cache , cache .stopJanitor )
163
-
162
+ if o .ctx == context .Background () {
163
+ ctx , cancel := context .WithCancel (o .ctx )
164
+ cache .janitor = newJanitor (ctx , o .janitorInterval )
165
+ runtime .SetFinalizer (cache , func (self * Cache [K , V ]) {
166
+ cancel ()
167
+ })
168
+ } else {
169
+ cache .janitor = newJanitor (o .ctx , o .janitorInterval )
170
+ }
164
171
return cache
165
172
}
166
173
167
- func (_ * Cache [K , V ]) stopJanitor (c * Cache [K , V ]) {
168
- if c .janitor != nil {
169
- c .janitor .stop <- true
174
+ // NewContext creates a new thread safe Cache with context.
175
+ //
176
+ // There are several Cache replacement policies available with you specified any options.
177
+ func NewContext [K comparable , V any ](ctx context.Context , opts ... Option [K , V ]) * Cache [K , V ] {
178
+ o := newOptions [K , V ]()
179
+ for _ , optFunc := range opts {
180
+ optFunc (o )
170
181
}
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 ),
182
+ return & Cache [K , V ]{
183
+ cache : o .cache ,
184
+ janitor : newJanitor (o .ctx , o .janitorInterval ),
181
185
}
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
186
}
198
187
199
188
// Get looks up a key's value from the cache.
@@ -206,9 +195,9 @@ func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
206
195
return
207
196
}
208
197
209
- // if is expired, delete is and return nil instead
210
- if item . Expiration > 0 && nowFunc (). UnixNano () > item . Expiration {
211
- c . cache . Delete ( key )
198
+ // Returns nil if the item has been expired.
199
+ // Do not delete here and leave it to an external process such as Janitor.
200
+ if item . Expired () {
212
201
return value , false
213
202
}
214
203
@@ -217,9 +206,12 @@ func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
217
206
218
207
// DeleteExpired all expired items from the cache.
219
208
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 )
209
+ for _ , key := range c .cache .Keys () {
210
+ // if is expired, delete it and return nil instead
211
+ item , ok := c .cache .Get (key )
212
+ if ok && item .Expired () {
213
+ c .cache .Delete (key )
214
+ }
223
215
}
224
216
}
225
217
@@ -228,11 +220,6 @@ func (c *Cache[K, V]) Set(key K, val V, opts ...ItemOption) {
228
220
c .mu .Lock ()
229
221
defer c .mu .Unlock ()
230
222
item := newItem (key , val , opts ... )
231
- if item .Expiration <= 0 {
232
- c .cache .Set (key , item )
233
- return
234
- }
235
-
236
223
c .cache .Set (key , item )
237
224
}
238
225
0 commit comments