forked from AdguardTeam/golibs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
data.go
156 lines (133 loc) · 3.11 KB
/
data.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
package cache
import (
"sync"
"sync/atomic"
"unsafe"
)
type onDeleteType func(key, val []byte)
type cache struct {
items map[string]*item
// LRU: for removing the least recently used item on reaching cache size limit
// Note: slows down Get() due to an additional work with pointers
// Example: [ (sentinel) <-> item1 <-> item2 <-> (sentinel) ]
// When the item is accessed, it's moved to the end of the list.
usage listItem
lock sync.Mutex
size uint // current size in bytes (keys+values)
conf Config
// stats:
miss int32 // number of misses
hit int32 // number of hits
}
type item struct {
key []byte
value []byte
used listItem
}
const maxUint = (1 << (unsafe.Sizeof(uint(0)) * 8)) - 1
func newCache(conf Config) *cache {
c := cache{}
c.items = make(map[string]*item)
listInit(&c.usage)
c.conf = conf
if c.conf.MaxSize == 0 {
c.conf.MaxSize = maxUint
}
if c.conf.MaxCount == 0 {
c.conf.MaxCount = maxUint
}
if c.conf.MaxElementSize == 0 {
c.conf.MaxElementSize = c.conf.MaxSize
}
if c.conf.MaxElementSize > c.conf.MaxSize {
c.conf.MaxElementSize = c.conf.MaxSize
}
return &c
}
func (c *cache) Clear() {
c.lock.Lock()
c.items = make(map[string]*item)
listInit(&c.usage)
c.size = 0
c.lock.Unlock()
atomic.StoreInt32(&c.hit, 0)
atomic.StoreInt32(&c.miss, 0)
}
// Set value
func (c *cache) Set(key, val []byte) bool {
addSize := uint(len(key) + len(val))
if addSize > c.conf.MaxElementSize {
return false // too large data
}
it := item{}
it.key = key
it.value = val
c.lock.Lock()
if !c.conf.EnableLRU &&
(c.size+addSize > c.conf.MaxSize || uint(len(c.items)) == c.conf.MaxCount) {
c.lock.Unlock()
return false // cache is full
}
for c.size+addSize > c.conf.MaxSize || uint(len(c.items)) == c.conf.MaxCount {
first := listFirst(&c.usage)
it := (*item)(structPtr(unsafe.Pointer(first), unsafe.Offsetof(item{}.used)))
c.size -= uint(len(it.key) + len(it.value))
listUnlink(first)
delete(c.items, string(it.key))
if c.conf.OnDelete != nil {
c.lock.Unlock()
c.conf.OnDelete(it.key, it.value)
c.lock.Lock()
}
}
if c.conf.EnableLRU {
listAppend(&it.used, listLast(&c.usage))
}
it2, exists := c.items[string(key)]
if exists {
listUnlink(&it2.used)
c.size -= uint(len(it2.key) + len(it2.value))
}
c.items[string(key)] = &it
c.size += addSize
c.lock.Unlock()
return exists
}
// Get value
func (c *cache) Get(key []byte) []byte {
c.lock.Lock()
val, ok := c.items[string(key)]
if ok && c.conf.EnableLRU {
listUnlink(&val.used)
listAppend(&val.used, listLast(&c.usage))
}
c.lock.Unlock()
if !ok {
atomic.AddInt32(&c.miss, 1)
return nil
}
atomic.AddInt32(&c.hit, 1)
return val.value
}
// Del - delete element
func (c *cache) Del(key []byte) {
c.lock.Lock()
it, ok := c.items[string(key)]
if !ok {
c.lock.Unlock()
return
}
listUnlink(&it.used)
c.size -= uint(len(it.key) + len(it.value))
delete(c.items, string(key))
c.lock.Unlock()
}
// GetStats - get counters
func (c *cache) Stats() Stats {
s := Stats{}
s.Count = len(c.items)
s.Size = int(c.size)
s.Hit = int(atomic.LoadInt32(&c.hit))
s.Miss = int(atomic.LoadInt32(&c.miss))
return s
}