github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/cachedmap/cachedmap.go (about)

     1  // Copyright 2024 The Inspektor Gadget authors
     2  //
     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  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package cachedmap provides a CachedMap which functions as a map with a TTL
    16  // for old entries. So after an entry is "removed" from the map its value is still
    17  // available for a certain amount of time.
    18  package cachedmap
    19  
    20  import (
    21  	"sync"
    22  	"time"
    23  )
    24  
    25  type oldResource[T any] struct {
    26  	deletionTimestamp time.Time
    27  	obj               T
    28  }
    29  
    30  type CachedMap[Key comparable, T any] interface {
    31  	Clear()
    32  	Add(key Key, obj T)
    33  	Remove(key Key)
    34  	Keys() []Key
    35  	Values() []T
    36  	Get(key Key) (T, bool)
    37  	GetCmp(cmp func(T) bool) (T, bool)
    38  	// Close stops the background goroutine that prunes old entries
    39  	Close()
    40  }
    41  
    42  type cachedMap[Key comparable, T any] struct {
    43  	sync.RWMutex
    44  	current     map[Key]T
    45  	old         map[Key]oldResource[T]
    46  	oldEntryTTL time.Duration
    47  	exit        chan struct{}
    48  }
    49  
    50  // NewCachedMap creates a new CachedMap with the given oldEntryTTL
    51  // The old entries will be deleted between oldEntryTTL and 2*oldEntryTTL
    52  func NewCachedMap[Key comparable, T any](oldEntryTTL time.Duration) CachedMap[Key, T] {
    53  	cm := &cachedMap[Key, T]{
    54  		current:     make(map[Key]T),
    55  		old:         make(map[Key]oldResource[T]),
    56  		oldEntryTTL: oldEntryTTL,
    57  		exit:        make(chan struct{}),
    58  	}
    59  	go cm.pruneLoop()
    60  	return cm
    61  }
    62  
    63  func (c *cachedMap[Key, T]) Close() {
    64  	close(c.exit)
    65  }
    66  
    67  func (c *cachedMap[Key, T]) Clear() {
    68  	c.Lock()
    69  	defer c.Unlock()
    70  	c.current = make(map[Key]T)
    71  	c.old = make(map[Key]oldResource[T])
    72  }
    73  
    74  func (c *cachedMap[Key, T]) Add(key Key, obj T) {
    75  	c.Lock()
    76  	defer c.Unlock()
    77  	c.current[key] = obj
    78  	delete(c.old, key)
    79  }
    80  
    81  func (c *cachedMap[Key, T]) Remove(key Key) {
    82  	c.Lock()
    83  	defer c.Unlock()
    84  	oldObj, ok := c.current[key]
    85  	if ok {
    86  		delete(c.current, key)
    87  		c.old[key] = oldResource[T]{deletionTimestamp: time.Now(), obj: oldObj}
    88  	}
    89  }
    90  
    91  func (c *cachedMap[Key, T]) pruneLoop() {
    92  	ticker := time.NewTicker(c.oldEntryTTL)
    93  	defer ticker.Stop()
    94  	for {
    95  		select {
    96  		case <-ticker.C:
    97  			c.pruneOldObjects()
    98  		case <-c.exit:
    99  			return
   100  		}
   101  	}
   102  }
   103  
   104  func (c *cachedMap[Key, T]) pruneOldObjects() {
   105  	c.Lock()
   106  	defer c.Unlock()
   107  	now := time.Now()
   108  	for key, oldObj := range c.old {
   109  		if now.Sub(oldObj.deletionTimestamp) > c.oldEntryTTL {
   110  			delete(c.old, key)
   111  		}
   112  	}
   113  }
   114  
   115  func (c *cachedMap[Key, T]) Keys() []Key {
   116  	c.RLock()
   117  	defer c.RUnlock()
   118  
   119  	keys := make([]Key, 0, len(c.current)+len(c.old))
   120  	for key := range c.current {
   121  		keys = append(keys, key)
   122  	}
   123  	for oldKey := range c.old {
   124  		if _, ok := c.current[oldKey]; !ok {
   125  			keys = append(keys, oldKey)
   126  		}
   127  	}
   128  	return keys
   129  }
   130  
   131  func (c *cachedMap[Key, T]) Values() []T {
   132  	c.RLock()
   133  	defer c.RUnlock()
   134  
   135  	objs := make([]T, 0, len(c.current)+len(c.old))
   136  	for _, obj := range c.current {
   137  		objs = append(objs, obj)
   138  	}
   139  	for oldKey, oldObj := range c.old {
   140  		if _, ok := c.current[oldKey]; !ok {
   141  			objs = append(objs, oldObj.obj)
   142  		}
   143  	}
   144  	return objs
   145  }
   146  
   147  func (c *cachedMap[Key, T]) Get(key Key) (T, bool) {
   148  	c.RLock()
   149  	defer c.RUnlock()
   150  
   151  	if obj, ok := c.current[key]; ok {
   152  		return obj, true
   153  	}
   154  	if oldObj, ok := c.old[key]; ok {
   155  		return oldObj.obj, true
   156  	}
   157  
   158  	var zeroValue T
   159  	return zeroValue, false
   160  }
   161  
   162  func (c *cachedMap[Key, T]) GetCmp(cmp func(T) bool) (T, bool) {
   163  	c.RLock()
   164  	defer c.RUnlock()
   165  
   166  	for _, obj := range c.current {
   167  		if cmp(obj) {
   168  			return obj, true
   169  		}
   170  	}
   171  	for _, oldObj := range c.old {
   172  		if cmp(oldObj.obj) {
   173  			return oldObj.obj, true
   174  		}
   175  	}
   176  	var zeroValue T
   177  	return zeroValue, false
   178  }