github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/topic_cache.go (about)

     1  package common
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  )
     7  
     8  // TopicCache is an interface which spits out topics from a fast cache rather than the database, whether from memory or from an application like Redis. Topics may not be present in the cache but may be in the database
     9  type TopicCache interface {
    10  	Get(id int) (*Topic, error)
    11  	GetUnsafe(id int) (*Topic, error)
    12  	BulkGet(ids []int) (list []*Topic)
    13  	Set(item *Topic) error
    14  	Add(item *Topic) error
    15  	AddUnsafe(item *Topic) error
    16  	Remove(id int) error
    17  	RemoveUnsafe(id int) error
    18  	RemoveMany(ids []int) error
    19  	Flush()
    20  	Length() int
    21  	SetCapacity(cap int)
    22  	GetCapacity() int
    23  }
    24  
    25  // MemoryTopicCache stores and pulls topics out of the current process' memory
    26  type MemoryTopicCache struct {
    27  	items    map[int]*Topic
    28  	length   int64 // sync/atomic only lets us operate on int32s and int64s
    29  	capacity int
    30  
    31  	sync.RWMutex
    32  }
    33  
    34  // NewMemoryTopicCache gives you a new instance of MemoryTopicCache
    35  func NewMemoryTopicCache(cap int) *MemoryTopicCache {
    36  	return &MemoryTopicCache{
    37  		items:    make(map[int]*Topic),
    38  		capacity: cap,
    39  	}
    40  }
    41  
    42  // Get fetches a topic by ID. Returns ErrNoRows if not present.
    43  func (s *MemoryTopicCache) Get(id int) (*Topic, error) {
    44  	s.RLock()
    45  	item, ok := s.items[id]
    46  	s.RUnlock()
    47  	if ok {
    48  		return item, nil
    49  	}
    50  	return item, ErrNoRows
    51  }
    52  
    53  // GetUnsafe fetches a topic by ID. Returns ErrNoRows if not present. THIS METHOD IS NOT THREAD-SAFE.
    54  func (s *MemoryTopicCache) GetUnsafe(id int) (*Topic, error) {
    55  	item, ok := s.items[id]
    56  	if ok {
    57  		return item, nil
    58  	}
    59  	return item, ErrNoRows
    60  }
    61  
    62  // BulkGet fetches multiple topics by their IDs. Indices without topics will be set to nil, so make sure you check for those, we might want to change this behaviour to make it less confusing.
    63  func (s *MemoryTopicCache) BulkGet(ids []int) (list []*Topic) {
    64  	list = make([]*Topic, len(ids))
    65  	s.RLock()
    66  	for i, id := range ids {
    67  		list[i] = s.items[id]
    68  	}
    69  	s.RUnlock()
    70  	return list
    71  }
    72  
    73  // Set overwrites the value of a topic in the cache, whether it's present or not. May return a capacity overflow error.
    74  func (s *MemoryTopicCache) Set(it *Topic) error {
    75  	s.Lock()
    76  	_, ok := s.items[it.ID]
    77  	if ok {
    78  		s.items[it.ID] = it
    79  	} else if int(s.length) >= s.capacity {
    80  		s.Unlock()
    81  		return ErrStoreCapacityOverflow
    82  	} else {
    83  		s.items[it.ID] = it
    84  		atomic.AddInt64(&s.length, 1)
    85  	}
    86  	s.Unlock()
    87  	return nil
    88  }
    89  
    90  // Add adds a topic to the cache, similar to Set, but it's only intended for new items. This method might be deprecated in the near future, use Set. May return a capacity overflow error.
    91  // ? Is this redundant if we have Set? Are the efficiency wins worth this? Is this even used?
    92  func (s *MemoryTopicCache) Add(item *Topic) error {
    93  	s.Lock()
    94  	if int(s.length) >= s.capacity {
    95  		s.Unlock()
    96  		return ErrStoreCapacityOverflow
    97  	}
    98  	s.items[item.ID] = item
    99  	s.Unlock()
   100  	atomic.AddInt64(&s.length, 1)
   101  	return nil
   102  }
   103  
   104  // AddUnsafe is the unsafe version of Add. May return a capacity overflow error. THIS METHOD IS NOT THREAD-SAFE.
   105  func (s *MemoryTopicCache) AddUnsafe(item *Topic) error {
   106  	if int(s.length) >= s.capacity {
   107  		return ErrStoreCapacityOverflow
   108  	}
   109  	s.items[item.ID] = item
   110  	s.length = int64(len(s.items))
   111  	return nil
   112  }
   113  
   114  // Remove removes a topic from the cache by ID, if they exist. Returns ErrNoRows if no items exist.
   115  func (s *MemoryTopicCache) Remove(id int) error {
   116  	var ok bool
   117  	s.Lock()
   118  	if _, ok = s.items[id]; !ok {
   119  		s.Unlock()
   120  		return ErrNoRows
   121  	}
   122  	delete(s.items, id)
   123  	s.Unlock()
   124  	atomic.AddInt64(&s.length, -1)
   125  	return nil
   126  }
   127  
   128  func (s *MemoryTopicCache) RemoveMany(ids []int) error {
   129  	var n int64
   130  	var ok bool
   131  	s.Lock()
   132  	for _, id := range ids {
   133  		if _, ok = s.items[id]; ok {
   134  			delete(s.items, id)
   135  			n++
   136  		}
   137  	}
   138  	atomic.AddInt64(&s.length, -n)
   139  	s.Unlock()
   140  	return nil
   141  }
   142  
   143  // RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE.
   144  func (s *MemoryTopicCache) RemoveUnsafe(id int) error {
   145  	if _, ok := s.items[id]; !ok {
   146  		return ErrNoRows
   147  	}
   148  	delete(s.items, id)
   149  	atomic.AddInt64(&s.length, -1)
   150  	return nil
   151  }
   152  
   153  // Flush removes all the topics from the cache, useful for tests.
   154  func (s *MemoryTopicCache) Flush() {
   155  	s.Lock()
   156  	s.items = make(map[int]*Topic)
   157  	s.length = 0
   158  	s.Unlock()
   159  }
   160  
   161  // ! Is this concurrent?
   162  // Length returns the number of topics in the memory cache
   163  func (s *MemoryTopicCache) Length() int {
   164  	return int(s.length)
   165  }
   166  
   167  // SetCapacity sets the maximum number of topics which this cache can hold
   168  func (s *MemoryTopicCache) SetCapacity(cap int) {
   169  	// Ints are moved in a single instruction, so this should be thread-safe
   170  	s.capacity = cap
   171  }
   172  
   173  // GetCapacity returns the maximum number of topics this cache can hold
   174  func (s *MemoryTopicCache) GetCapacity() int {
   175  	return s.capacity
   176  }