expiring.go 5.6 KB

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