-
Notifications
You must be signed in to change notification settings - Fork 122
/
Copy pathbucket.go
161 lines (136 loc) · 3.58 KB
/
bucket.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
package ccache
import (
"strings"
"sync"
"time"
)
type bucket[T any] struct {
sync.RWMutex
lookup map[string]*Item[T]
}
func (b *bucket[T]) itemCount() int {
b.RLock()
defer b.RUnlock()
return len(b.lookup)
}
func (b *bucket[T]) forEachFunc(matches func(key string, item *Item[T]) bool) bool {
lookup := b.lookup
b.RLock()
defer b.RUnlock()
for key, item := range lookup {
if !matches(key, item) {
return false
}
}
return true
}
func (b *bucket[T]) get(key string) *Item[T] {
b.RLock()
defer b.RUnlock()
return b.lookup[key]
}
func (b *bucket[T]) setnx(key string, value T, duration time.Duration, track bool) *Item[T] {
b.RLock()
item := b.lookup[key]
b.RUnlock()
if item != nil {
return item
}
expires := time.Now().Add(duration).UnixNano()
newItem := newItem(key, value, expires, track)
b.Lock()
defer b.Unlock()
// check again under write lock
item = b.lookup[key]
if item != nil {
return item
}
b.lookup[key] = newItem
return newItem
}
func (b *bucket[T]) setnx2(key string, f func() T, duration time.Duration, track bool) (*Item[T], bool) {
b.RLock()
item := b.lookup[key]
b.RUnlock()
if item != nil {
return item, true
}
b.Lock()
defer b.Unlock()
// check again under write lock
item = b.lookup[key]
if item != nil {
return item, true
}
expires := time.Now().Add(duration).UnixNano()
newItem := newItem(key, f(), expires, track)
b.lookup[key] = newItem
return newItem, false
}
func (b *bucket[T]) set(key string, value T, duration time.Duration, track bool) (*Item[T], *Item[T]) {
expires := time.Now().Add(duration).UnixNano()
item := newItem(key, value, expires, track)
b.Lock()
existing := b.lookup[key]
b.lookup[key] = item
b.Unlock()
return item, existing
}
func (b *bucket[T]) remove(key string) *Item[T] {
b.Lock()
item := b.lookup[key]
delete(b.lookup, key)
b.Unlock()
return item
}
func (b *bucket[T]) delete(key string) {
b.Lock()
delete(b.lookup, key)
b.Unlock()
}
// This is an expensive operation, so we do what we can to optimize it and limit
// the impact it has on concurrent operations. Specifically, we:
// 1 - Do an initial iteration to collect matches. This allows us to do the
// "expensive" prefix check (on all values) using only a read-lock
// 2 - Do a second iteration, under write lock, for the matched results to do
// the actual deletion
// Also, this is the only place where the Bucket is aware of cache detail: the
// deletables channel. Passing it here lets us avoid iterating over matched items
// again in the cache. Further, we pass item to deletables BEFORE actually removing
// the item from the map. I'm pretty sure this is 100% fine, but it is unique.
// (We do this so that the write to the channel is under the read lock and not the
// write lock)
func (b *bucket[T]) deleteFunc(matches func(key string, item *Item[T]) bool, deletables chan *Item[T]) int {
lookup := b.lookup
items := make([]*Item[T], 0)
b.RLock()
for key, item := range lookup {
if matches(key, item) {
deletables <- item
items = append(items, item)
}
}
b.RUnlock()
if len(items) == 0 {
// avoid the write lock if we can
return 0
}
b.Lock()
for _, item := range items {
delete(lookup, item.key)
}
b.Unlock()
return len(items)
}
func (b *bucket[T]) deletePrefix(prefix string, deletables chan *Item[T]) int {
return b.deleteFunc(func(key string, item *Item[T]) bool {
return strings.HasPrefix(key, prefix)
}, deletables)
}
// we expect the caller to have acquired a write lock
func (b *bucket[T]) clear() {
for _, item := range b.lookup {
item.promotions = -2
}
b.lookup = make(map[string]*Item[T])
}