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 }