-
Notifications
You must be signed in to change notification settings - Fork 4
/
memory_cache.go
158 lines (135 loc) · 3.42 KB
/
memory_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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package robin
import (
"errors"
"math"
"sync"
"time"
)
const expirationInterval = 30
type MemoryCache interface {
Keep(key string, value interface{}, ttl time.Duration) error
Forget(key string)
Read(string) (interface{}, bool)
Have(string) bool
}
type memoryCacheEntity struct {
utcCreated int64
utcAbsExp int64
key string
value interface{}
item *Item
sync.Mutex
}
type memoryCacheStore struct {
usage sync.Map
pq *PriorityQueue
sync.Mutex
}
var (
store *memoryCacheStore
once sync.Once
)
var (
errNewCacheHasExpired = errors.New("memory cache: the TTL(Time To Live) is less than the current time")
)
// Memory returns memoryCacheStore instance.
func Memory() MemoryCache {
return memoryCache()
}
// memoryCache returns memoryCacheStore dingleton instance
func memoryCache() *memoryCacheStore {
once.Do(func() {
store = new(memoryCacheStore)
store.pq = NewPriorityQueue(1024)
Every(expirationInterval).Seconds().AfterExecuteTask().Do(store.flushExpiredItems)
})
return store
}
// isExpired returns true if the item has expired with the locker..
func (m *memoryCacheEntity) isExpired() bool {
m.Lock()
defer m.Unlock()
return time.Now().UTC().UnixNano() > m.utcAbsExp
}
// expired to set the item has expired with the locker.
func (m *memoryCacheEntity) expired() {
m.Lock()
m.utcAbsExp = 0
m.Unlock()
m.item.expired()
}
// loadMemoryCacheEntry returns memoryCacheEntity if it exists in the cache
func (m *memoryCacheStore) loadMemoryCacheEntry(key string) (*memoryCacheEntity, bool) {
val, yes := m.usage.Load(key)
if !yes {
return nil, false
}
return val.(*memoryCacheEntity), true
}
// Keep insert an item into the memory
func (m *memoryCacheStore) Keep(key string, val interface{}, ttl time.Duration) error {
nowUtc := time.Now().UTC().UnixNano()
utcAbsExp := nowUtc + ttl.Nanoseconds()
if utcAbsExp <= nowUtc {
return errNewCacheHasExpired
}
if e, exist := m.loadMemoryCacheEntry(key); exist && !e.isExpired() {
e.Lock()
e.utcCreated = nowUtc
e.utcAbsExp = utcAbsExp
e.value = val
e.Unlock()
e.item.Lock()
e.item.Priority = e.utcAbsExp
e.item.Unlock()
m.pq.Update(e.item)
} else {
cacheEntity := &memoryCacheEntity{key: key, value: val, utcCreated: nowUtc, utcAbsExp: utcAbsExp}
item := &Item{Value: key, Priority: cacheEntity.utcAbsExp}
cacheEntity.item = item
m.usage.Store(cacheEntity.key, cacheEntity)
m.pq.PushItem(item)
}
return nil
}
// Read returns the value if the key exists in the cache
func (m *memoryCacheStore) Read(key string) (interface{}, bool) {
m.Lock()
defer m.Unlock()
cacheEntity, exist := m.loadMemoryCacheEntry(key)
if !exist {
return nil, false
}
if cacheEntity.isExpired() {
return nil, false
}
return cacheEntity.value, true
}
// Have returns true if the memory has the item and it's not expired.
func (m *memoryCacheStore) Have(key string) bool {
_, exist := m.Read(key)
return exist
}
// Forget removes an item from the memory
func (m *memoryCacheStore) Forget(key string) {
if e, exist := m.loadMemoryCacheEntry(key); exist {
m.Lock()
e.expired()
m.pq.Update(e.item)
m.Unlock()
}
}
// flushExpiredItems remove has expired item from the memory
func (m *memoryCacheStore) flushExpiredItems() {
num, max, limit := 0, math.MaxInt32, time.Now().UTC().UnixNano()
for num < max {
item, yes := m.pq.TryDequeue(limit)
if !yes {
break
}
if key, ok := item.Value.(string); ok {
m.usage.Delete(key)
num++
}
}
}