expiration_cache.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /*
  2. Copyright 2014 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. "sync"
  16. "time"
  17. "k8s.io/apimachinery/pkg/util/clock"
  18. "k8s.io/klog"
  19. )
  20. // ExpirationCache implements the store interface
  21. // 1. All entries are automatically time stamped on insert
  22. // a. The key is computed based off the original item/keyFunc
  23. // b. The value inserted under that key is the timestamped item
  24. // 2. Expiration happens lazily on read based on the expiration policy
  25. // a. No item can be inserted into the store while we're expiring
  26. // *any* item in the cache.
  27. // 3. Time-stamps are stripped off unexpired entries before return
  28. // Note that the ExpirationCache is inherently slower than a normal
  29. // threadSafeStore because it takes a write lock every time it checks if
  30. // an item has expired.
  31. type ExpirationCache struct {
  32. cacheStorage ThreadSafeStore
  33. keyFunc KeyFunc
  34. clock clock.Clock
  35. expirationPolicy ExpirationPolicy
  36. // expirationLock is a write lock used to guarantee that we don't clobber
  37. // newly inserted objects because of a stale expiration timestamp comparison
  38. expirationLock sync.Mutex
  39. }
  40. // ExpirationPolicy dictates when an object expires. Currently only abstracted out
  41. // so unittests don't rely on the system clock.
  42. type ExpirationPolicy interface {
  43. IsExpired(obj *timestampedEntry) bool
  44. }
  45. // TTLPolicy implements a ttl based ExpirationPolicy.
  46. type TTLPolicy struct {
  47. // >0: Expire entries with an age > ttl
  48. // <=0: Don't expire any entry
  49. Ttl time.Duration
  50. // Clock used to calculate ttl expiration
  51. Clock clock.Clock
  52. }
  53. // IsExpired returns true if the given object is older than the ttl, or it can't
  54. // determine its age.
  55. func (p *TTLPolicy) IsExpired(obj *timestampedEntry) bool {
  56. return p.Ttl > 0 && p.Clock.Since(obj.timestamp) > p.Ttl
  57. }
  58. // timestampedEntry is the only type allowed in a ExpirationCache.
  59. type timestampedEntry struct {
  60. obj interface{}
  61. timestamp time.Time
  62. }
  63. // getTimestampedEntry returns the timestampedEntry stored under the given key.
  64. func (c *ExpirationCache) getTimestampedEntry(key string) (*timestampedEntry, bool) {
  65. item, _ := c.cacheStorage.Get(key)
  66. if tsEntry, ok := item.(*timestampedEntry); ok {
  67. return tsEntry, true
  68. }
  69. return nil, false
  70. }
  71. // getOrExpire retrieves the object from the timestampedEntry if and only if it hasn't
  72. // already expired. It holds a write lock across deletion.
  73. func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
  74. // Prevent all inserts from the time we deem an item as "expired" to when we
  75. // delete it, so an un-expired item doesn't sneak in under the same key, just
  76. // before the Delete.
  77. c.expirationLock.Lock()
  78. defer c.expirationLock.Unlock()
  79. timestampedItem, exists := c.getTimestampedEntry(key)
  80. if !exists {
  81. return nil, false
  82. }
  83. if c.expirationPolicy.IsExpired(timestampedItem) {
  84. klog.V(4).Infof("Entry %v: %+v has expired", key, timestampedItem.obj)
  85. c.cacheStorage.Delete(key)
  86. return nil, false
  87. }
  88. return timestampedItem.obj, true
  89. }
  90. // GetByKey returns the item stored under the key, or sets exists=false.
  91. func (c *ExpirationCache) GetByKey(key string) (interface{}, bool, error) {
  92. obj, exists := c.getOrExpire(key)
  93. return obj, exists, nil
  94. }
  95. // Get returns unexpired items. It purges the cache of expired items in the
  96. // process.
  97. func (c *ExpirationCache) Get(obj interface{}) (interface{}, bool, error) {
  98. key, err := c.keyFunc(obj)
  99. if err != nil {
  100. return nil, false, KeyError{obj, err}
  101. }
  102. obj, exists := c.getOrExpire(key)
  103. return obj, exists, nil
  104. }
  105. // List retrieves a list of unexpired items. It purges the cache of expired
  106. // items in the process.
  107. func (c *ExpirationCache) List() []interface{} {
  108. items := c.cacheStorage.List()
  109. list := make([]interface{}, 0, len(items))
  110. for _, item := range items {
  111. obj := item.(*timestampedEntry).obj
  112. if key, err := c.keyFunc(obj); err != nil {
  113. list = append(list, obj)
  114. } else if obj, exists := c.getOrExpire(key); exists {
  115. list = append(list, obj)
  116. }
  117. }
  118. return list
  119. }
  120. // ListKeys returns a list of all keys in the expiration cache.
  121. func (c *ExpirationCache) ListKeys() []string {
  122. return c.cacheStorage.ListKeys()
  123. }
  124. // Add timestamps an item and inserts it into the cache, overwriting entries
  125. // that might exist under the same key.
  126. func (c *ExpirationCache) Add(obj interface{}) error {
  127. c.expirationLock.Lock()
  128. defer c.expirationLock.Unlock()
  129. key, err := c.keyFunc(obj)
  130. if err != nil {
  131. return KeyError{obj, err}
  132. }
  133. c.cacheStorage.Add(key, &timestampedEntry{obj, c.clock.Now()})
  134. return nil
  135. }
  136. // Update has not been implemented yet for lack of a use case, so this method
  137. // simply calls `Add`. This effectively refreshes the timestamp.
  138. func (c *ExpirationCache) Update(obj interface{}) error {
  139. return c.Add(obj)
  140. }
  141. // Delete removes an item from the cache.
  142. func (c *ExpirationCache) Delete(obj interface{}) error {
  143. c.expirationLock.Lock()
  144. defer c.expirationLock.Unlock()
  145. key, err := c.keyFunc(obj)
  146. if err != nil {
  147. return KeyError{obj, err}
  148. }
  149. c.cacheStorage.Delete(key)
  150. return nil
  151. }
  152. // Replace will convert all items in the given list to TimestampedEntries
  153. // before attempting the replace operation. The replace operation will
  154. // delete the contents of the ExpirationCache `c`.
  155. func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) error {
  156. c.expirationLock.Lock()
  157. defer c.expirationLock.Unlock()
  158. items := make(map[string]interface{}, len(list))
  159. ts := c.clock.Now()
  160. for _, item := range list {
  161. key, err := c.keyFunc(item)
  162. if err != nil {
  163. return KeyError{item, err}
  164. }
  165. items[key] = &timestampedEntry{item, ts}
  166. }
  167. c.cacheStorage.Replace(items, resourceVersion)
  168. return nil
  169. }
  170. // Resync will touch all objects to put them into the processing queue
  171. func (c *ExpirationCache) Resync() error {
  172. return c.cacheStorage.Resync()
  173. }
  174. // NewTTLStore creates and returns a ExpirationCache with a TTLPolicy
  175. func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
  176. return &ExpirationCache{
  177. cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
  178. keyFunc: keyFunc,
  179. clock: clock.RealClock{},
  180. expirationPolicy: &TTLPolicy{ttl, clock.RealClock{}},
  181. }
  182. }