k8s.io/client-go@v0.22.2/tools/cache/expiration_cache.go (about)

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