github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/cache/expiring.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cache 18 19 import ( 20 "container/heap" 21 "sync" 22 "time" 23 24 "k8s.io/utils/clock" 25 ) 26 27 // NewExpiring returns an initialized expiring cache. 28 func NewExpiring() *Expiring { 29 return NewExpiringWithClock(clock.RealClock{}) 30 } 31 32 // NewExpiringWithClock is like NewExpiring but allows passing in a custom 33 // clock for testing. 34 func NewExpiringWithClock(clock clock.Clock) *Expiring { 35 return &Expiring{ 36 clock: clock, 37 cache: make(map[interface{}]entry), 38 } 39 } 40 41 // Expiring is a map whose entries expire after a per-entry timeout. 42 type Expiring struct { 43 clock clock.Clock 44 45 // mu protects the below fields 46 mu sync.RWMutex 47 // cache is the internal map that backs the cache. 48 cache map[interface{}]entry 49 // generation is used as a cheap resource version for cache entries. Cleanups 50 // are scheduled with a key and generation. When the cleanup runs, it first 51 // compares its generation with the current generation of the entry. It 52 // deletes the entry iff the generation matches. This prevents cleanups 53 // scheduled for earlier versions of an entry from deleting later versions of 54 // an entry when Set() is called multiple times with the same key. 55 // 56 // The integer value of the generation of an entry is meaningless. 57 generation uint64 58 59 heap expiringHeap 60 } 61 62 type entry struct { 63 val interface{} 64 expiry time.Time 65 generation uint64 66 } 67 68 // Get looks up an entry in the cache. 69 func (c *Expiring) Get(key interface{}) (val interface{}, ok bool) { 70 c.mu.RLock() 71 defer c.mu.RUnlock() 72 e, ok := c.cache[key] 73 if !ok || !c.clock.Now().Before(e.expiry) { 74 return nil, false 75 } 76 return e.val, true 77 } 78 79 // Set sets a key/value/expiry entry in the map, overwriting any previous entry 80 // with the same key. The entry expires at the given expiry time, but its TTL 81 // may be lengthened or shortened by additional calls to Set(). Garbage 82 // collection of expired entries occurs during calls to Set(), however calls to 83 // Get() will not return expired entries that have not yet been garbage 84 // collected. 85 func (c *Expiring) Set(key interface{}, val interface{}, ttl time.Duration) { 86 now := c.clock.Now() 87 expiry := now.Add(ttl) 88 89 c.mu.Lock() 90 defer c.mu.Unlock() 91 92 c.generation++ 93 94 c.cache[key] = entry{ 95 val: val, 96 expiry: expiry, 97 generation: c.generation, 98 } 99 100 // Run GC inline before pushing the new entry. 101 c.gc(now) 102 103 heap.Push(&c.heap, &expiringHeapEntry{ 104 key: key, 105 expiry: expiry, 106 generation: c.generation, 107 }) 108 } 109 110 // Delete deletes an entry in the map. 111 func (c *Expiring) Delete(key interface{}) { 112 c.mu.Lock() 113 defer c.mu.Unlock() 114 c.del(key, 0) 115 } 116 117 // del deletes the entry for the given key. The generation argument is the 118 // generation of the entry that should be deleted. If the generation has been 119 // changed (e.g. if a set has occurred on an existing element but the old 120 // cleanup still runs), this is a noop. If the generation argument is 0, the 121 // entry's generation is ignored and the entry is deleted. 122 // 123 // del must be called under the write lock. 124 func (c *Expiring) del(key interface{}, generation uint64) { 125 e, ok := c.cache[key] 126 if !ok { 127 return 128 } 129 if generation != 0 && generation != e.generation { 130 return 131 } 132 delete(c.cache, key) 133 } 134 135 // Len returns the number of items in the cache. 136 func (c *Expiring) Len() int { 137 c.mu.RLock() 138 defer c.mu.RUnlock() 139 return len(c.cache) 140 } 141 142 func (c *Expiring) gc(now time.Time) { 143 for { 144 // Return from gc if the heap is empty or the next element is not yet 145 // expired. 146 // 147 // heap[0] is a peek at the next element in the heap, which is not obvious 148 // from looking at the (*expiringHeap).Pop() implementation below. 149 // heap.Pop() swaps the first entry with the last entry of the heap, then 150 // calls (*expiringHeap).Pop() which returns the last element. 151 if len(c.heap) == 0 || now.Before(c.heap[0].expiry) { 152 return 153 } 154 cleanup := heap.Pop(&c.heap).(*expiringHeapEntry) 155 c.del(cleanup.key, cleanup.generation) 156 } 157 } 158 159 type expiringHeapEntry struct { 160 key interface{} 161 expiry time.Time 162 generation uint64 163 } 164 165 // expiringHeap is a min-heap ordered by expiration time of its entries. The 166 // expiring cache uses this as a priority queue to efficiently organize entries 167 // which will be garbage collected once they expire. 168 type expiringHeap []*expiringHeapEntry 169 170 var _ heap.Interface = &expiringHeap{} 171 172 func (cq expiringHeap) Len() int { 173 return len(cq) 174 } 175 176 func (cq expiringHeap) Less(i, j int) bool { 177 return cq[i].expiry.Before(cq[j].expiry) 178 } 179 180 func (cq expiringHeap) Swap(i, j int) { 181 cq[i], cq[j] = cq[j], cq[i] 182 } 183 184 func (cq *expiringHeap) Push(c interface{}) { 185 *cq = append(*cq, c.(*expiringHeapEntry)) 186 } 187 188 func (cq *expiringHeap) Pop() interface{} { 189 c := (*cq)[cq.Len()-1] 190 *cq = (*cq)[:cq.Len()-1] 191 return c 192 }