-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.go
138 lines (113 loc) · 2.69 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
package yetacache
import (
"sync"
"time"
)
const DefaultTTL time.Duration = 0
type cacheItem[V any] struct {
item V
expiresAt int64
}
type Cache[K comparable, V any] struct {
items map[K]cacheItem[V]
locker sync.RWMutex
wg sync.WaitGroup
ttl time.Duration
stop chan bool
cleanupInterval time.Duration
}
// New creates a new instance of cache.
func New[K comparable, V any](defaultExpiration, cleanupInterval time.Duration) *Cache[K, V] {
c := &Cache[K, V]{
items: make(map[K]cacheItem[V]),
ttl: defaultExpiration,
cleanupInterval: cleanupInterval,
stop: make(chan bool),
}
c.wg.Add(1)
go func(cleanupInterval time.Duration) {
defer c.wg.Done()
c.cleanupLoop(cleanupInterval)
}(cleanupInterval)
return c
}
// Has returns true if the cache item exists and has not expired.
func (c *Cache[K, V]) Has(id K) bool {
c.locker.RLock()
defer c.locker.RUnlock()
item, found := c.items[id]
if found {
return time.Now().UnixMicro() < item.expiresAt
}
return false
}
// Get retrieves an item from the cache by the provided key.
// The second return value indicates if the cache item exists and
// has not expired.
func (c *Cache[K, V]) Get(id K) (V, bool) {
c.locker.RLock()
defer c.locker.RUnlock()
var val V
item, found := c.items[id]
if found {
val = item.item
return val, time.Now().UnixMicro() < item.expiresAt
}
return val, false
}
// Set creates a new item from the provided key and value, adds
// it to the cache. If an item associated with the provided key already
// exists, the new item overwrites the existing one.
func (c *Cache[K, V]) Set(id K, value V, ttl time.Duration) {
c.locker.Lock()
defer c.locker.Unlock()
if ttl == DefaultTTL {
ttl = c.ttl
}
c.items[id] = cacheItem[V]{
item: value,
expiresAt: time.Now().Add(ttl).UnixMicro(),
}
}
// Delete deletes an item from the cache.
func (c *Cache[K, V]) Delete(id K) {
c.locker.Lock()
defer c.locker.Unlock()
delete(c.items, id)
}
// Clear deletes all items from the cache.
func (c *Cache[K, V]) Clear() {
c.locker.Lock()
defer c.locker.Unlock()
if len(c.items) > 0 {
for key := range c.items {
delete(c.items, key)
}
}
}
// StopCleanup stops the internal cleanup cycle
// that removes expired items.
func (c *Cache[K, V]) StopCleanup() {
close(c.stop)
c.wg.Wait()
}
func (c *Cache[K, V]) cleanupLoop(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
var now int64
for {
select {
case <-c.stop:
return
case <-ticker.C:
c.locker.Lock()
now = time.Now().UnixMicro()
for key, item := range c.items {
if now > item.expiresAt {
delete(c.items, key)
}
}
c.locker.Unlock()
}
}
}