forked from jellydator/ttlcache
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache-private.go
153 lines (127 loc) · 3.85 KB
/
cache-private.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
package ttlcache
import (
"container/list"
"time"
)
// updateExpirations updates the expiration queue and notifies
// the cache auto cleaner if needed.
// Not concurrently safe.
func (c *Cache[K, V]) updateExpirations(fresh bool, elem *list.Element) {
var oldExpiresAt time.Time
if !c.CacheItems.expQueue.isEmpty() {
oldExpiresAt = c.CacheItems.expQueue[0].Value.(*Item[K, V]).expiresAt
}
if fresh {
c.CacheItems.expQueue.push(elem)
} else {
c.CacheItems.expQueue.update(elem)
}
newExpiresAt := c.CacheItems.expQueue[0].Value.(*Item[K, V]).expiresAt
// check if the closest/soonest expiration timestamp changed
if newExpiresAt.IsZero() || (!oldExpiresAt.IsZero() && !newExpiresAt.Before(oldExpiresAt)) {
return
}
d := time.Until(newExpiresAt)
// It's possible that the auto cleaner isn't active or
// is busy, so we need to drain the channel before
// sending a new value.
// Also, since this method is called after locking the items' mutex,
// we can be sure that there is no other concurrent call of this
// method
if len(c.CacheItems.timerCh) > 0 {
// we need to drain this channel in a select with a default
// case because it's possible that the auto cleaner
// read this channel just after we entered this if
select {
case d1 := <-c.CacheItems.timerCh:
if d1 < d {
d = d1
}
default:
}
}
// since the channel has a size 1 buffer, we can be sure
// that the line below won't block (we can't overfill the buffer
// because we just drained it)
c.CacheItems.timerCh <- d
}
// set creates a new item, adds it to the cache and then returns it.
// Not concurrently safe.
func (c *Cache[K, V]) set(key K, value V, ttl time.Duration, touch bool) *Item[K, V] {
elem, _ := c.get(key, false)
if elem != nil {
// update/overwrite an existing item
item := elem.Value.(*Item[K, V])
item.update(value, ttl)
if touch {
c.updateExpirations(false, elem)
}
return item
}
if c.options.capacity != 0 && uint64(len(c.CacheItems.values)) >= c.options.capacity {
// delete the oldest item
c.evict(EvictionReasonCapacityReached, c.CacheItems.lru.Back())
}
// create a new item
item := newItem(key, value, ttl)
elem = c.CacheItems.lru.PushFront(item)
c.CacheItems.values[key] = elem
c.updateExpirations(true, elem)
c.events.insertion.mu.RLock()
for _, fn := range c.events.insertion.fns {
fn(item)
}
c.events.insertion.mu.RUnlock()
return item
}
// get retrieves an item from the cache and extends its expiration
// time if 'touch' is set to true.
// It returns nil if the item is not found or is expired.
// Not concurrently safe.
func (c *Cache[K, V]) get(key K, touch bool) (elem *list.Element, isExistAndExpired bool) {
elem = c.CacheItems.values[key]
if elem == nil {
return nil, false
}
item := elem.Value.(*Item[K, V])
if item.isExpiredUnsafe() {
return elem, true
}
c.CacheItems.lru.MoveToFront(elem)
if touch && item.ttl > 0 {
item.touch()
c.updateExpirations(false, elem)
}
return elem, false
}
// evict deletes items from the cache.
// If no items are provided, all currently present cache items
// are evicted.
// Not concurrently safe.
func (c *Cache[K, V]) evict(reason EvictionReason, elems ...*list.Element) {
if len(elems) > 0 {
c.events.eviction.mu.RLock()
for i := range elems {
item := elems[i].Value.(*Item[K, V])
delete(c.CacheItems.values, item.key)
c.CacheItems.lru.Remove(elems[i])
c.CacheItems.expQueue.remove(elems[i])
for _, fn := range c.events.eviction.fns {
fn(reason, item)
}
}
c.events.eviction.mu.RUnlock()
return
}
c.events.eviction.mu.RLock()
for _, elem := range c.CacheItems.values {
item := elem.Value.(*Item[K, V])
for _, fn := range c.events.eviction.fns {
fn(reason, item)
}
}
c.events.eviction.mu.RUnlock()
c.CacheItems.values = make(map[K]*list.Element)
c.CacheItems.lru.Init()
c.CacheItems.expQueue = newExpirationQueue[K, V]()
}