github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/cache/lru.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package cache
     5  
     6  import (
     7  	"container/list"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/tinylib/msgp/msgp"
    12  	"github.com/vmihailenco/msgpack/v5"
    13  
    14  	"github.com/masterhung0112/hk_server/v5/model"
    15  )
    16  
    17  // LRU is a thread-safe fixed size LRU cache.
    18  type LRU struct {
    19  	lock                   sync.RWMutex
    20  	size                   int
    21  	len                    int
    22  	currentGeneration      int64
    23  	evictList              *list.List
    24  	items                  map[string]*list.Element
    25  	defaultExpiry          time.Duration
    26  	name                   string
    27  	invalidateClusterEvent string
    28  }
    29  
    30  // LRUOptions contains options for initializing LRU cache
    31  type LRUOptions struct {
    32  	Name                   string
    33  	Size                   int
    34  	DefaultExpiry          time.Duration
    35  	InvalidateClusterEvent string
    36  	// StripedBuckets is used only by LRUStriped and shouldn't be greater than the number
    37  	// of CPUs available on the machine running this cache.
    38  	StripedBuckets int
    39  }
    40  
    41  // entry is used to hold a value in the evictList.
    42  type entry struct {
    43  	key        string
    44  	value      []byte
    45  	expires    time.Time
    46  	generation int64
    47  }
    48  
    49  // NewLRU creates an LRU of the given size.
    50  func NewLRU(opts LRUOptions) Cache {
    51  	return &LRU{
    52  		name:                   opts.Name,
    53  		size:                   opts.Size,
    54  		evictList:              list.New(),
    55  		items:                  make(map[string]*list.Element, opts.Size),
    56  		defaultExpiry:          opts.DefaultExpiry,
    57  		invalidateClusterEvent: opts.InvalidateClusterEvent,
    58  	}
    59  }
    60  
    61  // Purge is used to completely clear the cache.
    62  func (l *LRU) Purge() error {
    63  	l.lock.Lock()
    64  	defer l.lock.Unlock()
    65  
    66  	l.len = 0
    67  	l.currentGeneration++
    68  	return nil
    69  }
    70  
    71  // Set adds the given key and value to the store without an expiry. If the key already exists,
    72  // it will overwrite the previous value.
    73  func (l *LRU) Set(key string, value interface{}) error {
    74  	return l.SetWithExpiry(key, value, 0)
    75  }
    76  
    77  // SetWithDefaultExpiry adds the given key and value to the store with the default expiry. If
    78  // the key already exists, it will overwrite the previoous value
    79  func (l *LRU) SetWithDefaultExpiry(key string, value interface{}) error {
    80  	return l.SetWithExpiry(key, value, l.defaultExpiry)
    81  }
    82  
    83  // SetWithExpiry adds the given key and value to the cache with the given expiry. If the key
    84  // already exists, it will overwrite the previoous value
    85  func (l *LRU) SetWithExpiry(key string, value interface{}, ttl time.Duration) error {
    86  	return l.set(key, value, ttl)
    87  }
    88  
    89  // Get the content stored in the cache for the given key, and decode it into the value interface.
    90  // return ErrKeyNotFound if the key is missing from the cache
    91  func (l *LRU) Get(key string, value interface{}) error {
    92  	return l.get(key, value)
    93  }
    94  
    95  // Remove deletes the value for a key.
    96  func (l *LRU) Remove(key string) error {
    97  	l.lock.Lock()
    98  	defer l.lock.Unlock()
    99  
   100  	if ent, ok := l.items[key]; ok {
   101  		l.removeElement(ent)
   102  	}
   103  	return nil
   104  }
   105  
   106  // Keys returns a slice of the keys in the cache.
   107  func (l *LRU) Keys() ([]string, error) {
   108  	l.lock.RLock()
   109  	defer l.lock.RUnlock()
   110  
   111  	keys := make([]string, l.len)
   112  	i := 0
   113  	for ent := l.evictList.Back(); ent != nil; ent = ent.Prev() {
   114  		e := ent.Value.(*entry)
   115  		if e.generation == l.currentGeneration {
   116  			keys[i] = e.key
   117  			i++
   118  		}
   119  	}
   120  	return keys, nil
   121  }
   122  
   123  // Len returns the number of items in the cache.
   124  func (l *LRU) Len() (int, error) {
   125  	l.lock.RLock()
   126  	defer l.lock.RUnlock()
   127  	return l.len, nil
   128  }
   129  
   130  // GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
   131  func (l *LRU) GetInvalidateClusterEvent() string {
   132  	return l.invalidateClusterEvent
   133  }
   134  
   135  // Name returns the name of the cache
   136  func (l *LRU) Name() string {
   137  	return l.name
   138  }
   139  
   140  func (l *LRU) set(key string, value interface{}, ttl time.Duration) error {
   141  	var expires time.Time
   142  	if ttl > 0 {
   143  		expires = time.Now().Add(ttl)
   144  	}
   145  
   146  	var buf []byte
   147  	var err error
   148  	// We use a fast path for hot structs.
   149  	if msgpVal, ok := value.(msgp.Marshaler); ok {
   150  		buf, err = msgpVal.MarshalMsg(nil)
   151  	} else {
   152  		// Slow path for other structs.
   153  		buf, err = msgpack.Marshal(value)
   154  	}
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	l.lock.Lock()
   160  	defer l.lock.Unlock()
   161  
   162  	// Check for existing item, ignoring expiry since we'd update anyway.
   163  	if ent, ok := l.items[key]; ok {
   164  		l.evictList.MoveToFront(ent)
   165  		e := ent.Value.(*entry)
   166  		e.value = buf
   167  		e.expires = expires
   168  		if e.generation != l.currentGeneration {
   169  			e.generation = l.currentGeneration
   170  			l.len++
   171  		}
   172  		return nil
   173  	}
   174  
   175  	// Add new item
   176  	ent := &entry{key, buf, expires, l.currentGeneration}
   177  	entry := l.evictList.PushFront(ent)
   178  	l.items[key] = entry
   179  	l.len++
   180  
   181  	if l.evictList.Len() > l.size {
   182  		l.removeElement(l.evictList.Back())
   183  	}
   184  	return nil
   185  }
   186  
   187  func (l *LRU) get(key string, value interface{}) error {
   188  	val, err := l.getItem(key)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	// We use a fast path for hot structs.
   194  	if msgpVal, ok := value.(msgp.Unmarshaler); ok {
   195  		_, err := msgpVal.UnmarshalMsg(val)
   196  		return err
   197  	}
   198  
   199  	// This is ugly and makes the cache package aware of the model package.
   200  	// But this is due to 2 things.
   201  	// 1. The msgp package works on methods on structs rather than functions.
   202  	// 2. Our cache interface passes pointers to empty pointers, and not pointers
   203  	// to values. This is mainly how all our model structs are passed around.
   204  	// It might be technically possible to use values _just_ for hot structs
   205  	// like these and then return a pointer while returning from the cache function,
   206  	// but it will make the codebase inconsistent, and has some edge-cases to take care of.
   207  	switch v := value.(type) {
   208  	case **model.User:
   209  		var u model.User
   210  		_, err := u.UnmarshalMsg(val)
   211  		*v = &u
   212  		return err
   213  	case *map[string]*model.User:
   214  		var u model.UserMap
   215  		_, err := u.UnmarshalMsg(val)
   216  		*v = u
   217  		return err
   218  	}
   219  
   220  	// Slow path for other structs.
   221  	return msgpack.Unmarshal(val, value)
   222  }
   223  
   224  func (l *LRU) getItem(key string) ([]byte, error) {
   225  	l.lock.Lock()
   226  	defer l.lock.Unlock()
   227  
   228  	ent, ok := l.items[key]
   229  	if !ok {
   230  		return nil, ErrKeyNotFound
   231  	}
   232  	e := ent.Value.(*entry)
   233  	if e.generation != l.currentGeneration || (!e.expires.IsZero() && time.Now().After(e.expires)) {
   234  		l.removeElement(ent)
   235  		return nil, ErrKeyNotFound
   236  	}
   237  	l.evictList.MoveToFront(ent)
   238  	return e.value, nil
   239  }
   240  
   241  func (l *LRU) removeElement(e *list.Element) {
   242  	l.evictList.Remove(e)
   243  	kv := e.Value.(*entry)
   244  	if kv.generation == l.currentGeneration {
   245  		l.len--
   246  	}
   247  	delete(l.items, kv.key)
   248  }