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  }