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 }