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  }