-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcacher.go
290 lines (271 loc) · 8.27 KB
/
cacher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
package cacher
import (
"sync"
"time"
)
// Cacher is a fast, decentralised caching system, generic
// in nature and uses Go's in built mapping to store data.
// It has a plenty of features like TTL, Revaluation etc.
//
// Some of the main features are descried below:
//
// TTL (Time-To-Live): It allows us to expire a key after
// a specific time period.
// Eg: If TTL is set to 30 seconds, then each key of current
// Cacher will be expired after 30 seconds of their addition.
//
// Revaluation: This is another useful feature that allows us
// to keep keys cached as per their usage frequency.
// Working: Whenever the keys will be retrieved via (Cacher.Get)
// method, its expiry will be renewed and this will allow us to
// keep frequently used keys in the map without expiration.
type Cacher[C comparable, T any] struct {
ttl int64
mutex *sync.RWMutex
status status
cacheMap map[C]*value[T]
revaluate bool
cleanInterval time.Duration
}
// This struct contains the optional arguments which can be filled
// while creating a new Cacher instance.
//
// Parameters:
//
// TimeToLive (type time.Duration):
// It allows us to expire a key after a specific time period.
// Eg: If it is set to 30 seconds, then each key of current
// Cacher will be expired after 30 seconds of their addition.
//
// CleanInterval (type time.Duration):
// It is the time of interval between two cleaner windows.
// A cleaner window is that time frame when all the expired
// keys will be deleted from our cache mapping.
// Note: It TTL is set to a finite value and no value is passed
// to CleanInterval, it'll use a default time interval of 1 day
// for clean window.
// Eg: If CleanInterval is set to 1 hour, then cleaner
// window will be run after every 1 hour, and the expired keys
// which are present in our cache map will be deleted.
//
// Revaluate (type bool):
// It allows us to keep keys cached as per their usage frequency.
// Working: Whenever the keys will be retrieved via (Cacher.Get)
// method, its expiry will be renewed and this will allow us to
// keep frequently used keys in the map without expiration.
type NewCacherOpts struct {
TimeToLive time.Duration
CleanInterval time.Duration
Revaluate bool
}
// NewCacher is a generic function which creates a new Cacher instance.
//
// Generic parameters (for current Cacher instance):
//
// KeyT: It is the "static" type of keys of our cache.
// It accepts types which implement built-in comparable interface.
// Eg: If it is set to string, then keys will only be allowed
// as a string built-in data-type.
//
// ValueT: It is the type of values of our cache.
// It can be set to any type.
// Eg: If it is set to string, then value will only be allowed
// as a string built-in data-type.
//
// Input parameters:
//
// opts (type *NewCacherOpts):
// It contains optional parameters which you can use while creating
// a new Cacher instance.
//
// General Example:
// c := cacher.NewCacher[int, string](&cacher.NewCacherOpts{10*time.Minute, time.Hour, true})
// will create a new Cacher instance which will expire keys after 10
// minutes of their addition to the system, all the expired keys will
// be deleted from cache once in an hour. Keys will have their expiry
// revalueted on every c.Get call.
func NewCacher[KeyT comparable, ValueT any](opts *NewCacherOpts) *Cacher[KeyT, ValueT] {
if opts == nil {
opts = new(NewCacherOpts)
}
ttl := int64(opts.TimeToLive.Seconds())
c := Cacher[KeyT, ValueT]{
cacheMap: make(map[KeyT]*value[ValueT]),
mutex: new(sync.RWMutex),
ttl: ttl,
cleanInterval: opts.CleanInterval,
revaluate: opts.Revaluate,
}
if ttl != 0 {
if c.cleanInterval == 0 {
c.cleanInterval = time.Hour * 24
}
go c.cleaner()
}
return &c
}
// Set is used to set a new key-value pair to the current
// Cacher instance. It doesn't return anything.
func (c *Cacher[C, T]) Set(key C, val T) {
c.setRawValue(key, c.packValue(val, nil))
}
// SetWithTTL is used to set a new key-value pair to the current
// Cacher instance with a specific TTL. It doesn't return anything.
// It will expire the key after the input TTL, and TTL specified in
// this function will override the default TTL of current Cacher instance
// for this pair specifically.
func (c *Cacher[C, T]) SetWithTTL(key C, val T, ttl time.Duration) {
var _ttl = int64(ttl.Seconds())
c.setRawValue(key, c.packValue(val, &_ttl))
}
func (c *Cacher[C, T]) setRawValue(key C, val *value[T]) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.cacheMap[key] = val
}
// Set is used to get value of the input key. It returns
// value of input key with true while returns empty value
// with false if key is not found or has expired already
//
// Note: It will renew the expiration time of the input
// key which is retrieved if revaluation mode is on for
// current Cacher instance.
func (c *Cacher[C, T]) Get(key C) (value T, ok bool) {
rValue, ok := c.getRawValue(key)
if !ok {
return
}
val, expired := rValue.get(c.revaluate, c.ttl)
if !expired {
value = val
return
}
ok = false
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.cacheMap, key)
return
}
// GetAll is used to return all the unexpired key-value
// pairs present in the current Cacher instance, returns
// a slice of values.
//
// Note: It doesn't renew expiration time of any key
// even if the revaluation mode is turned on for the
// current Cacher instance.
func (c *Cacher[C, T]) GetAll() []T {
res := make([]T, c.NumKeys())
var i = 0
c.mutex.RLock()
defer c.mutex.RUnlock()
for _, rv := range c.cacheMap {
v, exp := rv.get(false, 0)
if exp {
continue
}
res[i] = v
i++
}
return res
}
// SegrigatorFunc takes the input as value of current key.
// Returned boolean is used for segrigation of keys for
// GetSome function.
type SegrigatorFunc[T any] func(value T) bool
// GetSome is used to get keys which satisfired a particular
// condition determined via SegrigatorFunc.
// It returns those values which satisfied the condition
// determined via SegrigatorFunc.
func (c *Cacher[C, T]) GetSome(cond SegrigatorFunc[T]) []T {
if cond == nil {
cond = func(T) bool { return true }
}
return c.getSome(cond)
}
// The inner function of GetSome
func (c *Cacher[C, T]) getSome(cond SegrigatorFunc[T]) []T {
// we can't determine length yet due to segrigations by the
// cond function.
res := []T{}
c.mutex.RLock()
defer c.mutex.RUnlock()
for _, rv := range c.cacheMap {
// No need to pass actual ttl since we ain't revaluating
v, exp := rv.get(false, 0)
if exp {
continue
}
if !cond(v) {
continue
}
res = append(res, v)
}
return res
}
// It returns the value of a key in the form of Value struct.
func (c *Cacher[C, T]) getRawValue(key C) (val *value[T], ok bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
val, ok = c.cacheMap[key]
return
}
// It packs the value to a a struct with expiry date.
func (c *Cacher[C, T]) packValue(val T, ttl *int64) *value[T] {
v := value[T]{
val: val,
}
if ttl != nil {
var _ttl_val = *ttl
if _ttl_val != 0 {
v.expiry = time.Now().Unix() + _ttl_val
}
} else {
if c.ttl != 0 {
v.expiry = time.Now().Unix() + c.ttl
}
}
return &v
}
// Delete is used to delete the input key from current
// Cacher instance. It doesn't return anything. If there
// is no such key, Delete is a no-op.
func (c *Cacher[C, T]) Delete(key C) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.cacheMap, key)
}
// DeleteSome is used to delete keys which satisfied a
// particular condition determined via SegrigatorFunc.
func (c *Cacher[C, T]) DeleteSome(cond SegrigatorFunc[T]) {
if cond == nil {
cond = func(T) bool { return true }
}
c.deleteSome(cond)
}
func (c *Cacher[C, T]) deleteSome(cond SegrigatorFunc[T]) {
c.mutex.Lock()
defer c.mutex.Unlock()
for k, v := range c.cacheMap {
if !cond(v.val) {
continue
}
delete(c.cacheMap, k)
}
}
// The Reset function deletes the current cache map
// and reallocates an empty one in place of it.
// Use it if you want to delete all keys at once.
// It doesn't return anything.
func (c *Cacher[C, T]) Reset() {
c.status = cacherReset
c.mutex.Lock()
defer c.mutex.Unlock()
c.cacheMap = make(map[C]*value[T])
}
// NumKeys counts the number of keys present in the
// current Cacher instance and returns that count.
func (c *Cacher[C, T]) NumKeys() int {
c.mutex.Lock()
defer c.mutex.Unlock()
return len(c.cacheMap)
}