github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/reply_cache.go (about) 1 package common 2 3 import ( 4 //"log" 5 "sync" 6 "sync/atomic" 7 ) 8 9 // ReplyCache is an interface which spits out replies from a fast cache rather than the database, whether from memory or from an application like Redis. Replies may not be present in the cache but may be in the database 10 type ReplyCache interface { 11 Get(id int) (*Reply, error) 12 GetUnsafe(id int) (*Reply, error) 13 BulkGet(ids []int) (list []*Reply) 14 Set(item *Reply) error 15 Add(item *Reply) error 16 AddUnsafe(item *Reply) error 17 Remove(id int) error 18 RemoveUnsafe(id int) error 19 Flush() 20 Length() int 21 SetCapacity(cap int) 22 GetCapacity() int 23 } 24 25 // MemoryReplyCache stores and pulls replies out of the current process' memory 26 type MemoryReplyCache struct { 27 items map[int]*Reply 28 length int64 // sync/atomic only lets us operate on int32s and int64s 29 capacity int 30 31 sync.RWMutex 32 } 33 34 // NewMemoryReplyCache gives you a new instance of MemoryReplyCache 35 func NewMemoryReplyCache(cap int) *MemoryReplyCache { 36 return &MemoryReplyCache{ 37 items: make(map[int]*Reply), 38 capacity: cap, 39 } 40 } 41 42 // Get fetches a reply by ID. Returns ErrNoRows if not present. 43 func (s *MemoryReplyCache) Get(id int) (*Reply, 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 reply by ID. Returns ErrNoRows if not present. THIS METHOD IS NOT THREAD-SAFE. 54 func (s *MemoryReplyCache) GetUnsafe(id int) (*Reply, 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 replies by their IDs. Indices without replies 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 *MemoryReplyCache) BulkGet(ids []int) (list []*Reply) { 64 list = make([]*Reply, 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 reply in the cache, whether it's present or not. May return a capacity overflow error. 74 func (s *MemoryReplyCache) Set(item *Reply) error { 75 s.Lock() 76 _, ok := s.items[item.ID] 77 if ok { 78 s.items[item.ID] = item 79 } else if int(s.length) >= s.capacity { 80 s.Unlock() 81 return ErrStoreCapacityOverflow 82 } else { 83 s.items[item.ID] = item 84 atomic.AddInt64(&s.length, 1) 85 } 86 s.Unlock() 87 return nil 88 } 89 90 // Add adds a reply 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 *MemoryReplyCache) Add(item *Reply) error { 93 //log.Print("MemoryReplyCache.Add") 94 s.Lock() 95 if int(s.length) >= s.capacity { 96 s.Unlock() 97 return ErrStoreCapacityOverflow 98 } 99 s.items[item.ID] = item 100 s.Unlock() 101 atomic.AddInt64(&s.length, 1) 102 return nil 103 } 104 105 // AddUnsafe is the unsafe version of Add. May return a capacity overflow error. THIS METHOD IS NOT THREAD-SAFE. 106 func (s *MemoryReplyCache) AddUnsafe(item *Reply) error { 107 if int(s.length) >= s.capacity { 108 return ErrStoreCapacityOverflow 109 } 110 s.items[item.ID] = item 111 s.length = int64(len(s.items)) 112 return nil 113 } 114 115 // Remove removes a reply from the cache by ID, if they exist. Returns ErrNoRows if no items exist. 116 func (s *MemoryReplyCache) Remove(id int) error { 117 s.Lock() 118 _, ok := s.items[id] 119 if !ok { 120 s.Unlock() 121 return ErrNoRows 122 } 123 delete(s.items, id) 124 s.Unlock() 125 atomic.AddInt64(&s.length, -1) 126 return nil 127 } 128 129 // RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE. 130 func (s *MemoryReplyCache) RemoveUnsafe(id int) error { 131 _, ok := s.items[id] 132 if !ok { 133 return ErrNoRows 134 } 135 delete(s.items, id) 136 atomic.AddInt64(&s.length, -1) 137 return nil 138 } 139 140 // Flush removes all the replies from the cache, useful for tests. 141 func (s *MemoryReplyCache) Flush() { 142 s.Lock() 143 s.items = make(map[int]*Reply) 144 s.length = 0 145 s.Unlock() 146 } 147 148 // ! Is this concurrent? 149 // Length returns the number of replies in the memory cache 150 func (s *MemoryReplyCache) Length() int { 151 return int(s.length) 152 } 153 154 // SetCapacity sets the maximum number of replies which this cache can hold 155 func (s *MemoryReplyCache) SetCapacity(cap int) { 156 // Ints are moved in a single instruction, so this should be thread-safe 157 s.capacity = cap 158 } 159 160 // GetCapacity returns the maximum number of replies this cache can hold 161 func (s *MemoryReplyCache) GetCapacity() int { 162 return s.capacity 163 }