github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/multiwatcher/store.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package multiwatcher 5 6 import ( 7 "container/list" 8 "reflect" 9 "sync" 10 11 "github.com/kr/pretty" 12 ) 13 14 // Store stores the current entities to use as a basis for the multiwatcher 15 // notifications. 16 type Store interface { 17 All() []EntityInfo 18 // ChangesSince takes revno. A zero implies that this is the first call for changes. 19 // A slice of changes is returned along with the latest revno that the store has seen. 20 ChangesSince(revno int64) ([]Delta, int64) 21 22 // AddReference and DecReference are used for internal reference counting for the 23 // watchers that have been notified. 24 // TODO: determine if this is actually useful, and whether this is the right place for it. 25 AddReference(revno int64) 26 DecReference(revno int64) 27 28 Get(id EntityID) EntityInfo 29 Update(info EntityInfo) 30 Remove(id EntityID) 31 32 // Size returns the internal size of the store's list. 33 // Used only for tests and metrics. 34 Size() int 35 } 36 37 // entityEntry holds an entry in the linked list of all entities known 38 // to a params. 39 type entityEntry struct { 40 // The revno holds the local idea of the latest change to the 41 // given entity. It is not the same as the transaction revno - 42 // this means we can unconditionally move a newly fetched entity 43 // to the front of the list without worrying if the revno has 44 // changed since the watcher reported it. 45 revno int64 46 47 // creationRevno holds the revision number when the 48 // entity was created. 49 creationRevno int64 50 51 // removed marks whether the entity has been removed. 52 removed bool 53 54 // refCount holds a count of the number of watchers that 55 // have seen this entity. When the entity is marked as removed, 56 // the ref count is decremented whenever a Multiwatcher that 57 // has previously seen the entry now sees that it has been removed; 58 // the entry will be deleted when all such Multiwatchers have 59 // been notified. 60 refCount int 61 62 // info holds the actual information on the entity. 63 info EntityInfo 64 } 65 66 // store holds a list of all known entities. 67 type store struct { 68 mu sync.Mutex 69 latestRevno int64 70 entities map[interface{}]*list.Element 71 list *list.List 72 logger Logger 73 } 74 75 // Logger describes the logging methods used in this package by the worker. 76 type Logger interface { 77 IsTraceEnabled() bool 78 Tracef(string, ...interface{}) 79 Errorf(string, ...interface{}) 80 Criticalf(string, ...interface{}) 81 } 82 83 // NewStore returns an Store instance holding information about the 84 // current state of all entities in the model. 85 // It is only exposed here for testing purposes. 86 func NewStore(logger Logger) Store { 87 return newStore(logger) 88 } 89 90 func newStore(logger Logger) *store { 91 return &store{ 92 entities: make(map[interface{}]*list.Element), 93 list: list.New(), 94 logger: logger, 95 } 96 } 97 98 // Size returns the length of the internal list. 99 func (a *store) Size() int { 100 a.mu.Lock() 101 defer a.mu.Unlock() 102 103 return a.list.Len() 104 } 105 106 // All returns all the entities stored in the Store, 107 // oldest first. 108 func (a *store) All() []EntityInfo { 109 a.mu.Lock() 110 defer a.mu.Unlock() 111 112 entities := make([]EntityInfo, 0, a.list.Len()) 113 for e := a.list.Front(); e != nil; e = e.Next() { 114 entry := e.Value.(*entityEntry) 115 if entry.removed { 116 continue 117 } 118 entities = append(entities, entry.info.Clone()) 119 } 120 return entities 121 } 122 123 // add adds a new entity with the given id and associated 124 // information to the list. 125 func (a *store) add(id interface{}, info EntityInfo) { 126 if _, ok := a.entities[id]; ok { 127 a.logger.Criticalf("programming error: adding new entry with duplicate id %q", id) 128 return 129 } 130 a.latestRevno++ 131 entry := &entityEntry{ 132 info: info, 133 revno: a.latestRevno, 134 creationRevno: a.latestRevno, 135 } 136 a.entities[id] = a.list.PushFront(entry) 137 } 138 139 // decRef decrements the reference count of an entry within the list, 140 // deleting it if it becomes zero and the entry is removed. 141 func (a *store) decRef(entry *entityEntry) { 142 if entry.refCount--; entry.refCount > 0 { 143 return 144 } 145 if entry.refCount < 0 { 146 a.logger.Criticalf("programming error: negative reference count\n%s", pretty.Sprint(entry)) 147 return 148 } 149 if !entry.removed { 150 return 151 } 152 id := entry.info.EntityID() 153 elem, ok := a.entities[id] 154 if !ok { 155 a.logger.Criticalf("programming error: delete of non-existent entry\n%s", pretty.Sprint(entry)) 156 return 157 } 158 delete(a.entities, id) 159 a.list.Remove(elem) 160 } 161 162 // delete deletes the entry with the given info id. 163 func (a *store) delete(id EntityID) { 164 elem, ok := a.entities[id] 165 if !ok { 166 return 167 } 168 delete(a.entities, id) 169 a.list.Remove(elem) 170 } 171 172 // Remove marks that the entity with the given id has 173 // been removed from the backing. If nothing has seen the 174 // entity, then we delete it immediately. 175 func (a *store) Remove(id EntityID) { 176 a.mu.Lock() 177 defer a.mu.Unlock() 178 179 if elem := a.entities[id]; elem != nil { 180 entry := elem.Value.(*entityEntry) 181 if entry.removed { 182 return 183 } 184 a.latestRevno++ 185 if entry.refCount == 0 { 186 a.delete(id) 187 return 188 } 189 entry.revno = a.latestRevno 190 entry.removed = true 191 a.list.MoveToFront(elem) 192 } 193 } 194 195 // Update updates the information for the given entity. 196 func (a *store) Update(info EntityInfo) { 197 a.mu.Lock() 198 defer a.mu.Unlock() 199 200 id := info.EntityID() 201 elem, ok := a.entities[id] 202 if !ok { 203 a.add(id, info) 204 return 205 } 206 entry := elem.Value.(*entityEntry) 207 // Nothing has changed, so change nothing. 208 // TODO(rog) do the comparison more efficiently. 209 if reflect.DeepEqual(info, entry.info) { 210 return 211 } 212 // We already know about the entity; update its doc. 213 a.latestRevno++ 214 entry.revno = a.latestRevno 215 entry.info = info 216 // The app might have been removed and re-added. 217 entry.removed = false 218 a.list.MoveToFront(elem) 219 } 220 221 // Get returns the stored entity with the given id, or nil if none was found. 222 // The contents of the returned entity MUST not be changed. 223 func (a *store) Get(id EntityID) EntityInfo { 224 a.mu.Lock() 225 defer a.mu.Unlock() 226 227 e, ok := a.entities[id] 228 if !ok { 229 return nil 230 } 231 ei := e.Value.(*entityEntry).info 232 if ei == nil { 233 return nil 234 } 235 // Always clone to prevent data races/mutating internal store state which will miss 236 // sending changes. 237 return ei.Clone() 238 } 239 240 // ChangesSince returns any changes that have occurred since 241 // the given revno, oldest first. 242 func (a *store) ChangesSince(revno int64) ([]Delta, int64) { 243 a.mu.Lock() 244 defer a.mu.Unlock() 245 246 e := a.list.Front() 247 n := 0 248 for ; e != nil; e = e.Next() { 249 entry := e.Value.(*entityEntry) 250 if entry.revno <= revno { 251 break 252 } 253 n++ 254 } 255 if e != nil { 256 // We've found an element that we've already seen. 257 e = e.Prev() 258 } else { 259 // We haven't seen any elements, so we want all of them. 260 e = a.list.Back() 261 n++ 262 } 263 changes := make([]Delta, 0, n) 264 for ; e != nil; e = e.Prev() { 265 entry := e.Value.(*entityEntry) 266 if entry.removed && entry.creationRevno > revno { 267 // Don't include entries that have been created 268 // and removed since the revno. 269 continue 270 } 271 // Use clone to make a copy to avoid races. 272 changes = append(changes, Delta{ 273 Removed: entry.removed, 274 Entity: entry.info.Clone(), 275 }) 276 } 277 return changes, a.latestRevno 278 } 279 280 // AddReference states that a Multiwatcher has just been given information about 281 // all entities newer than the given revno. We assume it has already seen all 282 // the older entities. 283 func (a *store) AddReference(revno int64) { 284 a.mu.Lock() 285 defer a.mu.Unlock() 286 287 for e := a.list.Front(); e != nil; { 288 next := e.Next() 289 entry := e.Value.(*entityEntry) 290 if entry.revno <= revno { 291 break 292 } 293 if entry.creationRevno > revno { 294 if !entry.removed { 295 // This is a new entity that hasn't been seen yet, 296 // so increment the entry's refCount. 297 entry.refCount++ 298 } 299 } else if entry.removed { 300 // This is an entity that we previously saw, but 301 // has now been removed, so decrement its refCount, removing 302 // the entity if nothing else is waiting to be notified that it's 303 // gone. 304 a.decRef(entry) 305 } 306 e = next 307 } 308 } 309 310 // DecReference is called when a watcher leaves. It decrements the reference 311 // counts of any entities that have been seen by the watcher. 312 func (a *store) DecReference(revno int64) { 313 a.mu.Lock() 314 defer a.mu.Unlock() 315 316 for e := a.list.Front(); e != nil; { 317 next := e.Next() 318 entry := e.Value.(*entityEntry) 319 if entry.creationRevno <= revno { 320 // The watcher has seen this entry. 321 if entry.removed && entry.revno <= revno { 322 // The entity has been removed and the 323 // watcher has already been informed of that, 324 // so its refcount has already been decremented. 325 e = next 326 continue 327 } 328 a.decRef(entry) 329 } 330 e = next 331 } 332 }