expiration_cache.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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/utils/clock"
  18. )
  19. // ExpirationCache implements the store interface
  20. // 1. All entries are automatically time stamped on insert
  21. // a. The key is computed based off the original item/keyFunc
  22. // b. The value inserted under that key is the timestamped item
  23. // 2. Expiration happens lazily on read based on the expiration policy
  24. // a. No item can be inserted into the store while we're expiring
  25. // *any* item in the cache.
  26. // 3. Time-stamps are stripped off unexpired entries before return
  27. //
  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. // Keep in mind that it is not safe to share timestamps between computers.
  60. // Behavior may be inconsistent if you get a timestamp from the API Server and
  61. // use it on the client machine as part of your ExpirationCache.
  62. type TimestampedEntry struct {
  63. Obj interface{}
  64. Timestamp time.Time
  65. key string
  66. }
  67. // getTimestampedEntry returns the TimestampedEntry stored under the given key.
  68. func (c *ExpirationCache) getTimestampedEntry(key string) (*TimestampedEntry, bool) {
  69. item, _ := c.cacheStorage.Get(key)
  70. if tsEntry, ok := item.(*TimestampedEntry); ok {
  71. return tsEntry, true
  72. }
  73. return nil, false
  74. }
  75. // getOrExpire retrieves the object from the TimestampedEntry if and only if it hasn't
  76. // already expired. It holds a write lock across deletion.
  77. func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
  78. // Prevent all inserts from the time we deem an item as "expired" to when we
  79. // delete it, so an un-expired item doesn't sneak in under the same key, just
  80. // before the Delete.
  81. c.expirationLock.Lock()
  82. defer c.expirationLock.Unlock()
  83. timestampedItem, exists := c.getTimestampedEntry(key)
  84. if !exists {
  85. return nil, false
  86. }
  87. if c.expirationPolicy.IsExpired(timestampedItem) {
  88. c.cacheStorage.Delete(key)
  89. return nil, false
  90. }
  91. return timestampedItem.Obj, true
  92. }
  93. // GetByKey returns the item stored under the key, or sets exists=false.
  94. func (c *ExpirationCache) GetByKey(key string) (interface{}, bool, error) {
  95. obj, exists := c.getOrExpire(key)
  96. return obj, exists, nil
  97. }
  98. // Get returns unexpired items. It purges the cache of expired items in the
  99. // process.
  100. func (c *ExpirationCache) Get(obj interface{}) (interface{}, bool, error) {
  101. key, err := c.keyFunc(obj)
  102. if err != nil {
  103. return nil, false, KeyError{obj, err}
  104. }
  105. obj, exists := c.getOrExpire(key)
  106. return obj, exists, nil
  107. }
  108. // List retrieves a list of unexpired items. It purges the cache of expired
  109. // items in the process.
  110. func (c *ExpirationCache) List() []interface{} {
  111. items := c.cacheStorage.List()
  112. list := make([]interface{}, 0, len(items))
  113. for _, item := range items {
  114. key := item.(*TimestampedEntry).key
  115. if obj, exists := c.getOrExpire(key); exists {
  116. list = append(list, obj)
  117. }
  118. }
  119. return list
  120. }
  121. // ListKeys returns a list of all keys in the expiration cache.
  122. func (c *ExpirationCache) ListKeys() []string {
  123. return c.cacheStorage.ListKeys()
  124. }
  125. // Add timestamps an item and inserts it into the cache, overwriting entries
  126. // that might exist under the same key.
  127. func (c *ExpirationCache) Add(obj interface{}) error {
  128. key, err := c.keyFunc(obj)
  129. if err != nil {
  130. return KeyError{obj, err}
  131. }
  132. c.expirationLock.Lock()
  133. defer c.expirationLock.Unlock()
  134. c.cacheStorage.Add(key, &TimestampedEntry{obj, c.clock.Now(), key})
  135. return nil
  136. }
  137. // Update has not been implemented yet for lack of a use case, so this method
  138. // simply calls `Add`. This effectively refreshes the timestamp.
  139. func (c *ExpirationCache) Update(obj interface{}) error {
  140. return c.Add(obj)
  141. }
  142. // Delete removes an item from the cache.
  143. func (c *ExpirationCache) Delete(obj interface{}) error {
  144. key, err := c.keyFunc(obj)
  145. if err != nil {
  146. return KeyError{obj, err}
  147. }
  148. c.expirationLock.Lock()
  149. defer c.expirationLock.Unlock()
  150. c.cacheStorage.Delete(key)
  151. return nil
  152. }
  153. // Replace will convert all items in the given list to TimestampedEntries
  154. // before attempting the replace operation. The replace operation will
  155. // delete the contents of the ExpirationCache `c`.
  156. func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) error {
  157. items := make(map[string]interface{}, len(list))
  158. ts := c.clock.Now()
  159. for _, item := range list {
  160. key, err := c.keyFunc(item)
  161. if err != nil {
  162. return KeyError{item, err}
  163. }
  164. items[key] = &TimestampedEntry{item, ts, key}
  165. }
  166. c.expirationLock.Lock()
  167. defer c.expirationLock.Unlock()
  168. c.cacheStorage.Replace(items, resourceVersion)
  169. return nil
  170. }
  171. // Resync is a no-op for one of these
  172. func (c *ExpirationCache) Resync() error {
  173. return nil
  174. }
  175. // NewTTLStore creates and returns a ExpirationCache with a TTLPolicy
  176. func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
  177. return NewExpirationStore(keyFunc, &TTLPolicy{ttl, clock.RealClock{}})
  178. }
  179. // NewExpirationStore creates and returns a ExpirationCache for a given policy
  180. func NewExpirationStore(keyFunc KeyFunc, expirationPolicy ExpirationPolicy) Store {
  181. return &ExpirationCache{
  182. cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
  183. keyFunc: keyFunc,
  184. clock: clock.RealClock{},
  185. expirationPolicy: expirationPolicy,
  186. }
  187. }