-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.go
143 lines (109 loc) · 2.92 KB
/
cache.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
package cache
import (
"context"
"errors"
"fmt"
"sync"
"time"
)
var ErrContextIsNil = errors.New("context is nil")
//KeyExistsError: a custom error type that is returned when an attempt is
// made to add a key to the cache that already exists.
type KeyExistsError struct {
Key any
}
func (e *KeyExistsError) Error() string {
return fmt.Sprintf("key %v already exists", e.Key)
}
//VCache: a struct that implements a simple in-memory key-value cache with eviction.
type VCache struct {
durationRecordEvict int64
durationCheckNewTicker time.Duration
store map[any]*cacheValue
mu *sync.RWMutex
}
//cacheValue: a struct that contains a value and its expiration time.
type cacheValue struct {
value any
expirationTime int64
}
//New: a function that creates and returns a new Cache instance.
func New(timeCheckNewTicker time.Duration, timeRecordEvict time.Duration) *VCache {
return &VCache{
mu: &sync.RWMutex{},
store: make(map[any]*cacheValue),
durationCheckNewTicker: timeCheckNewTicker,
durationRecordEvict: int64(timeRecordEvict.Seconds()),
}
}
//StartEvict: a method that starts the eviction process in a separate goroutine.
// It stops when the context passed as an argument is done.
func (c *VCache) StartEvict(ctx context.Context) error {
if ctx == nil {
return ErrContextIsNil
}
ticker := time.NewTicker(c.durationCheckNewTicker)
go func() {
for {
select {
case <-ticker.C:
c.Evict()
case <-ctx.Done():
ticker.Stop()
return
}
}
}()
return nil
}
//Add: a method that adds a new key-value pair to the cache. Returns KeyExistsError if the key already exists.
func (c *VCache) Add(key any, value any) error {
c.mu.Lock()
if _, ok := c.store[key]; ok {
c.mu.Unlock()
return &KeyExistsError{Key: key}
}
c.store[key] = &cacheValue{
value: value,
expirationTime: time.Now().Unix() + c.durationRecordEvict,
}
c.mu.Unlock()
return nil
}
//Get: a method that retrieves a value from the cache by its key.
// Returns the value and a boolean indicating if the key was found.
func (c *VCache) Get(key any) (any, bool) {
c.mu.RLock()
val, foundKey := c.store[key]
if foundKey {
if time.Now().Unix() > val.expirationTime {
c.mu.RUnlock()
return nil, false
}
val.expirationTime = time.Now().Unix()
c.mu.RUnlock()
return val.value, foundKey
}
c.mu.RUnlock()
return nil, false
}
//Delete: a method that deletes a key-value pair from the cache.
func (c *VCache) Delete(key any) {
c.mu.Lock()
delete(c.store, key)
c.mu.Unlock()
}
//Evict: a method that evicts expired key-value pairs from the cache.
func (c *VCache) Evict() {
c.mu.Lock()
var evictedItems []interface{}
for key, val := range c.store {
if time.Now().Unix() > val.expirationTime {
evictedItems = append(evictedItems, key)
}
}
for _, key := range evictedItems {
delete(c.store, key)
}
c.mu.Unlock()
}