move cache2 package to cache (#14921)
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
61
services/cache/cache.go
поставляемый
61
services/cache/cache.go
поставляемый
@@ -4,57 +4,46 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache is a representation of any cache store that has keys and values
|
||||
// ErrKeyNotFound is the error when the given key is not found
|
||||
var ErrKeyNotFound = errors.New("key not found")
|
||||
|
||||
// Cache is a representation of a cache store that aims to replace cache.Cache
|
||||
type Cache interface {
|
||||
// Purge is used to completely clear the cache.
|
||||
Purge()
|
||||
Purge() error
|
||||
|
||||
// Add adds the given key and value to the store without an expiry.
|
||||
Add(key string, value interface{})
|
||||
// Set adds the given key and value to the store without an expiry. If the key already exists,
|
||||
// it will overwrite the previous value.
|
||||
Set(key string, value interface{}) error
|
||||
|
||||
// AddWithDefaultExpires adds the given key and value to the store with the default expiry.
|
||||
AddWithDefaultExpires(key string, value interface{})
|
||||
// SetWithDefaultExpiry adds the given key and value to the store with the default expiry. If
|
||||
// the key already exists, it will overwrite the previoous value
|
||||
SetWithDefaultExpiry(key string, value interface{}) error
|
||||
|
||||
// AddWithExpiresInSecs adds the given key and value to the cache with the given expiry.
|
||||
AddWithExpiresInSecs(key string, value interface{}, expireAtSecs int64)
|
||||
// SetWithExpiry adds the given key and value to the cache with the given expiry. If the key
|
||||
// already exists, it will overwrite the previoous value
|
||||
SetWithExpiry(key string, value interface{}, ttl time.Duration) error
|
||||
|
||||
// Get returns the value stored in the cache for a key, or nil if no value is present. The ok result indicates whether value was found in the cache.
|
||||
Get(key string) (value interface{}, ok bool)
|
||||
// Get the content stored in the cache for the given key, and decode it into the value interface.
|
||||
// Return ErrKeyNotFound if the key is missing from the cache
|
||||
Get(key string, value interface{}) error
|
||||
|
||||
// GetOrAdd returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.
|
||||
// This API intentionally deviates from the Add-only variants above for simplicity. We should simplify the entire API in the future.
|
||||
GetOrAdd(key string, value interface{}, ttl time.Duration) (actual interface{}, loaded bool)
|
||||
|
||||
// Remove deletes the value for a key.
|
||||
Remove(key string)
|
||||
// Remove deletes the value for a given key.
|
||||
Remove(key string) error
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
Keys() []string
|
||||
Keys() ([]string, error)
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
Len() int
|
||||
|
||||
// Name identifies this cache instance among others in the system.
|
||||
Name() string
|
||||
Len() (int, error)
|
||||
|
||||
// GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
|
||||
GetInvalidateClusterEvent() string
|
||||
}
|
||||
|
||||
// Provider defines how to create new caches
|
||||
type Provider interface {
|
||||
// Connect opens a new connection to the cache using specific provider parameters.
|
||||
Connect()
|
||||
|
||||
// NewCache creates a new cache with given size.
|
||||
NewCache(size int) Cache
|
||||
|
||||
// NewCacheWithParams creates a new cache with the given parameters.
|
||||
NewCacheWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) Cache
|
||||
|
||||
// Close releases any resources used by the cache provider.
|
||||
Close()
|
||||
|
||||
// Name returns the name of the cache
|
||||
Name() string
|
||||
}
|
||||
|
||||
197
services/cache/lru.go
поставляемый
Обычный файл
197
services/cache/lru.go
поставляемый
Обычный файл
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
// LRU is a thread-safe fixed size LRU cache.
|
||||
type LRU struct {
|
||||
name string
|
||||
size int
|
||||
evictList *list.List
|
||||
items map[string]*list.Element
|
||||
lock sync.RWMutex
|
||||
defaultExpiry time.Duration
|
||||
invalidateClusterEvent string
|
||||
currentGeneration int64
|
||||
len int
|
||||
}
|
||||
|
||||
// LRUOptions contains options for initializing LRU cache
|
||||
type LRUOptions struct {
|
||||
Name string
|
||||
Size int
|
||||
DefaultExpiry time.Duration
|
||||
InvalidateClusterEvent string
|
||||
}
|
||||
|
||||
// entry is used to hold a value in the evictList.
|
||||
type entry struct {
|
||||
key string
|
||||
value []byte
|
||||
expires time.Time
|
||||
generation int64
|
||||
}
|
||||
|
||||
// NewLRU creates an LRU of the given size.
|
||||
func NewLRU(opts *LRUOptions) Cache {
|
||||
return &LRU{
|
||||
name: opts.Name,
|
||||
size: opts.Size,
|
||||
evictList: list.New(),
|
||||
items: make(map[string]*list.Element, opts.Size),
|
||||
defaultExpiry: opts.DefaultExpiry,
|
||||
invalidateClusterEvent: opts.InvalidateClusterEvent,
|
||||
}
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (l *LRU) Purge() error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
l.len = 0
|
||||
l.currentGeneration++
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set adds the given key and value to the store without an expiry. If the key already exists,
|
||||
// it will overwrite the previous value.
|
||||
func (l *LRU) Set(key string, value interface{}) error {
|
||||
return l.SetWithExpiry(key, value, 0)
|
||||
}
|
||||
|
||||
// SetWithDefaultExpiry adds the given key and value to the store with the default expiry. If
|
||||
// the key already exists, it will overwrite the previoous value
|
||||
func (l *LRU) SetWithDefaultExpiry(key string, value interface{}) error {
|
||||
return l.SetWithExpiry(key, value, l.defaultExpiry)
|
||||
}
|
||||
|
||||
// SetWithExpiry adds the given key and value to the cache with the given expiry. If the key
|
||||
// already exists, it will overwrite the previoous value
|
||||
func (l *LRU) SetWithExpiry(key string, value interface{}, ttl time.Duration) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
return l.set(key, value, ttl)
|
||||
}
|
||||
|
||||
// Get the content stored in the cache for the given key, and decode it into the value interface.
|
||||
// return ErrKeyNotFound if the key is missing from the cache
|
||||
func (l *LRU) Get(key string, value interface{}) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
return l.get(key, value)
|
||||
}
|
||||
|
||||
// Remove deletes the value for a key.
|
||||
func (l *LRU) Remove(key string) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
if ent, ok := l.items[key]; ok {
|
||||
l.removeElement(ent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
func (l *LRU) Keys() ([]string, error) {
|
||||
l.lock.RLock()
|
||||
defer l.lock.RUnlock()
|
||||
|
||||
keys := make([]string, l.len)
|
||||
i := 0
|
||||
for ent := l.evictList.Back(); ent != nil; ent = ent.Prev() {
|
||||
e := ent.Value.(*entry)
|
||||
if e.generation == l.currentGeneration {
|
||||
keys[i] = e.key
|
||||
i++
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (l *LRU) Len() (int, error) {
|
||||
l.lock.RLock()
|
||||
defer l.lock.RUnlock()
|
||||
return l.len, nil
|
||||
}
|
||||
|
||||
// GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
|
||||
func (l *LRU) GetInvalidateClusterEvent() string {
|
||||
return l.invalidateClusterEvent
|
||||
}
|
||||
|
||||
// Name returns the name of the cache
|
||||
func (l *LRU) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *LRU) set(key string, value interface{}, ttl time.Duration) error {
|
||||
var expires time.Time
|
||||
if ttl > 0 {
|
||||
expires = time.Now().Add(ttl)
|
||||
}
|
||||
|
||||
buf, err := msgpack.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for existing item, ignoring expiry since we'd update anyway.
|
||||
if ent, ok := l.items[key]; ok {
|
||||
l.evictList.MoveToFront(ent)
|
||||
e := ent.Value.(*entry)
|
||||
e.value = buf
|
||||
e.expires = expires
|
||||
if e.generation != l.currentGeneration {
|
||||
e.generation = l.currentGeneration
|
||||
l.len++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add new item
|
||||
ent := &entry{key, buf, expires, l.currentGeneration}
|
||||
entry := l.evictList.PushFront(ent)
|
||||
l.items[key] = entry
|
||||
l.len++
|
||||
|
||||
if l.evictList.Len() > l.size {
|
||||
l.removeElement(l.evictList.Back())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LRU) get(key string, value interface{}) error {
|
||||
if ent, ok := l.items[key]; ok {
|
||||
e := ent.Value.(*entry)
|
||||
|
||||
if e.generation != l.currentGeneration || (!e.expires.IsZero() && time.Now().After(e.expires)) {
|
||||
l.removeElement(ent)
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
|
||||
l.evictList.MoveToFront(ent)
|
||||
|
||||
return msgpack.Unmarshal(e.value, value)
|
||||
}
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
|
||||
func (l *LRU) removeElement(e *list.Element) {
|
||||
l.evictList.Remove(e)
|
||||
kv := e.Value.(*entry)
|
||||
if kv.generation == l.currentGeneration {
|
||||
l.len--
|
||||
}
|
||||
delete(l.items, kv.key)
|
||||
}
|
||||
232
services/cache/lru/lru.go
поставляемый
232
services/cache/lru/lru.go
поставляемый
@@ -1,232 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// This files was copied/modified from https://github.com/hashicorp/golang-lru
|
||||
// which was (see below)
|
||||
|
||||
// This package provides a simple LRU cache. It is based on the
|
||||
// LRU implementation in groupcache:
|
||||
// https://github.com/golang/groupcache/tree/master/lru
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/services/cache"
|
||||
)
|
||||
|
||||
// Cache is a thread-safe fixed size LRU cache.
|
||||
type Cache struct {
|
||||
size int
|
||||
evictList *list.List
|
||||
items map[string]*list.Element
|
||||
lock sync.RWMutex
|
||||
name string
|
||||
defaultExpiry int64
|
||||
invalidateClusterEvent string
|
||||
currentGeneration int64
|
||||
len int
|
||||
}
|
||||
|
||||
// CacheProvider is an implementation of cache.Provider to create a new Lru Cache
|
||||
type CacheProvider struct{}
|
||||
|
||||
// NewCache creates a new lru.Cache with given size.
|
||||
func (c *CacheProvider) NewCache(size int) cache.Cache {
|
||||
return New(size)
|
||||
}
|
||||
|
||||
// NewCacheWithParams creates a new lru.Cache with the given parameters.
|
||||
func (c *CacheProvider) NewCacheWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) cache.Cache {
|
||||
return NewWithParams(size, name, defaultExpiry, invalidateClusterEvent)
|
||||
}
|
||||
|
||||
// Connect opens a new connection to the cache using specific provider parameters.
|
||||
func (c *CacheProvider) Connect() {
|
||||
|
||||
}
|
||||
|
||||
// Close releases any resources used by the cache provider.
|
||||
func (c *CacheProvider) Close() {
|
||||
|
||||
}
|
||||
|
||||
// entry is used to hold a value in the evictList.
|
||||
type entry struct {
|
||||
key string
|
||||
value interface{}
|
||||
expires time.Time
|
||||
generation int64
|
||||
}
|
||||
|
||||
// New creates an LRU of the given size.
|
||||
func New(size int) *Cache {
|
||||
return &Cache{
|
||||
size: size,
|
||||
evictList: list.New(),
|
||||
items: make(map[string]*list.Element, size),
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithParams creates an LRU with the given parameters.
|
||||
func NewWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
|
||||
lru := New(size)
|
||||
lru.name = name
|
||||
lru.defaultExpiry = defaultExpiry
|
||||
lru.invalidateClusterEvent = invalidateClusterEvent
|
||||
return lru
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *Cache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.len = 0
|
||||
c.currentGeneration++
|
||||
}
|
||||
|
||||
// Add adds the given key and value to the store without an expiry.
|
||||
func (c *Cache) Add(key string, value interface{}) {
|
||||
c.AddWithExpiresInSecs(key, value, 0)
|
||||
}
|
||||
|
||||
// AddWithDefaultExpires adds the given key and value to the store with the default expiry.
|
||||
func (c *Cache) AddWithDefaultExpires(key string, value interface{}) {
|
||||
c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
|
||||
}
|
||||
|
||||
// AddWithExpiresInSecs adds the given key and value to the cache with the given expiry.
|
||||
func (c *Cache) AddWithExpiresInSecs(key string, value interface{}, expireAtSecs int64) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.add(key, value, time.Duration(expireAtSecs)*time.Second)
|
||||
}
|
||||
|
||||
func (c *Cache) add(key string, value interface{}, ttl time.Duration) {
|
||||
var expires time.Time
|
||||
if ttl > 0 {
|
||||
expires = time.Now().Add(ttl)
|
||||
}
|
||||
|
||||
// Check for existing item, ignoring expiry since we'd update anyway.
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.evictList.MoveToFront(ent)
|
||||
e := ent.Value.(*entry)
|
||||
e.value = value
|
||||
e.expires = expires
|
||||
if e.generation != c.currentGeneration {
|
||||
e.generation = c.currentGeneration
|
||||
c.len++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add new item
|
||||
ent := &entry{key, value, expires, c.currentGeneration}
|
||||
entry := c.evictList.PushFront(ent)
|
||||
c.items[key] = entry
|
||||
c.len++
|
||||
|
||||
if c.evictList.Len() > c.size {
|
||||
c.removeElement(c.evictList.Back())
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value stored in the cache for a key, or nil if no value is present. The ok result indicates whether value was found in the cache.
|
||||
func (c *Cache) Get(key string) (value interface{}, ok bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
return c.getValue(key)
|
||||
}
|
||||
|
||||
func (c *Cache) getValue(key string) (value interface{}, ok bool) {
|
||||
if ent, ok := c.items[key]; ok {
|
||||
e := ent.Value.(*entry)
|
||||
|
||||
if e.generation != c.currentGeneration || (!e.expires.IsZero() && time.Now().After(e.expires)) {
|
||||
c.removeElement(ent)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
c.evictList.MoveToFront(ent)
|
||||
return ent.Value.(*entry).value, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetOrAdd returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.
|
||||
// This API intentionally deviates from the Add-only variants above for simplicity. We should simplify the entire API in the future.
|
||||
func (c *Cache) GetOrAdd(key string, value interface{}, ttl time.Duration) (actual interface{}, loaded bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Check for existing item
|
||||
if actualValue, ok := c.getValue(key); ok {
|
||||
return actualValue, true
|
||||
}
|
||||
|
||||
c.add(key, value, ttl)
|
||||
|
||||
return value, false
|
||||
}
|
||||
|
||||
// Remove deletes the value for a key.
|
||||
func (c *Cache) Remove(key string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.removeElement(ent)
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *Cache) Keys() []string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
keys := make([]string, c.len)
|
||||
i := 0
|
||||
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
|
||||
e := ent.Value.(*entry)
|
||||
if e.generation == c.currentGeneration {
|
||||
keys[i] = e.key
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.len
|
||||
}
|
||||
|
||||
// Name identifies this cache instance among others in the system.
|
||||
func (c *Cache) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
|
||||
func (c *Cache) GetInvalidateClusterEvent() string {
|
||||
return c.invalidateClusterEvent
|
||||
}
|
||||
|
||||
func (c *Cache) removeElement(e *list.Element) {
|
||||
c.evictList.Remove(e)
|
||||
kv := e.Value.(*entry)
|
||||
if kv.generation == c.currentGeneration {
|
||||
c.len--
|
||||
}
|
||||
delete(c.items, kv.key)
|
||||
}
|
||||
125
services/cache/lru/lru_test.go
поставляемый
125
services/cache/lru/lru_test.go
поставляемый
@@ -1,125 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// This files was copied/modified from https://github.com/hashicorp/golang-lru
|
||||
// which was (see below)
|
||||
|
||||
// This package provides a simple LRU cache. It is based on the
|
||||
// LRU implementation in groupcache:
|
||||
// https://github.com/golang/groupcache/tree/master/lru
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLRU(t *testing.T) {
|
||||
l := New(128)
|
||||
|
||||
for i := 0; i < 256; i++ {
|
||||
l.Add(fmt.Sprintf("%d", i), i)
|
||||
}
|
||||
require.Equalf(t, l.Len(), 128, "bad len: %v", l.Len())
|
||||
|
||||
for i, k := range l.Keys() {
|
||||
v, ok := l.Get(k)
|
||||
require.True(t, ok, "bad key: %v", k)
|
||||
require.Equalf(t, fmt.Sprintf("%d", v), k, "bad key: %v", k)
|
||||
require.Equalf(t, i+128, v, "bad key: %v", k)
|
||||
}
|
||||
for i := 0; i < 128; i++ {
|
||||
_, ok := l.Get(fmt.Sprintf("%d", i))
|
||||
require.False(t, ok, "should be evicted")
|
||||
}
|
||||
for i := 128; i < 256; i++ {
|
||||
_, ok := l.Get(fmt.Sprintf("%d", i))
|
||||
require.True(t, ok, "should not be evicted")
|
||||
}
|
||||
for i := 128; i < 192; i++ {
|
||||
l.Remove(fmt.Sprintf("%d", i))
|
||||
_, ok := l.Get(fmt.Sprintf("%d", i))
|
||||
require.False(t, ok, "should be deleted")
|
||||
}
|
||||
|
||||
l.Get("192") // expect 192 to be last key in l.Keys()
|
||||
|
||||
for i, k := range l.Keys() {
|
||||
require.Falsef(t, i < 63 && k != fmt.Sprintf("%d", i+193), "out of order key: %v", k)
|
||||
require.Falsef(t, i == 63 && k != "192", "out of order key: %v", k)
|
||||
}
|
||||
|
||||
l.Purge()
|
||||
require.Equalf(t, l.Len(), 0, "bad len: %v", l.Len())
|
||||
_, ok := l.Get("200")
|
||||
require.False(t, ok, "should contain nothing")
|
||||
}
|
||||
|
||||
func TestLRUExpire(t *testing.T) {
|
||||
l := New(128)
|
||||
|
||||
l.AddWithExpiresInSecs("1", 1, 1)
|
||||
l.AddWithExpiresInSecs("2", 2, 1)
|
||||
l.AddWithExpiresInSecs("3", 3, 0)
|
||||
|
||||
time.Sleep(time.Millisecond * 2100)
|
||||
|
||||
r1, ok := l.Get("1")
|
||||
require.False(t, ok, r1)
|
||||
|
||||
_, ok2 := l.Get("3")
|
||||
require.True(t, ok2, "should exist")
|
||||
}
|
||||
|
||||
func TestLRUGetOrAdd(t *testing.T) {
|
||||
l := New(128)
|
||||
|
||||
// First GetOrAdd should save
|
||||
value, loaded := l.GetOrAdd("1", 1, 0)
|
||||
assert.Equal(t, 1, value)
|
||||
assert.False(t, loaded)
|
||||
|
||||
// Second GetOrAdd should load original value, ignoring new value
|
||||
value, loaded = l.GetOrAdd("1", 10, 0)
|
||||
assert.Equal(t, 1, value)
|
||||
assert.True(t, loaded)
|
||||
|
||||
// Third GetOrAdd should still load original value
|
||||
value, loaded = l.GetOrAdd("1", 1, 0)
|
||||
assert.Equal(t, 1, value)
|
||||
assert.True(t, loaded)
|
||||
|
||||
// First GetOrAdd on a new key should save
|
||||
value, loaded = l.GetOrAdd("2", 2, 0)
|
||||
assert.Equal(t, 2, value)
|
||||
assert.False(t, loaded)
|
||||
|
||||
l.Remove("1")
|
||||
|
||||
// GetOrAdd after a remove should save
|
||||
value, loaded = l.GetOrAdd("1", 10, 0)
|
||||
assert.Equal(t, 10, value)
|
||||
assert.False(t, loaded)
|
||||
|
||||
// GetOrAdd after another key was removed should load original value for key
|
||||
value, loaded = l.GetOrAdd("2", 2, 0)
|
||||
assert.Equal(t, 2, value)
|
||||
assert.True(t, loaded)
|
||||
|
||||
// GetOrAdd should expire
|
||||
value, loaded = l.GetOrAdd("3", 3, 500*time.Millisecond)
|
||||
assert.Equal(t, 3, value)
|
||||
assert.False(t, loaded)
|
||||
value, loaded = l.GetOrAdd("3", 4, 500*time.Millisecond)
|
||||
assert.Equal(t, 3, value)
|
||||
assert.True(t, loaded)
|
||||
time.Sleep(1 * time.Second)
|
||||
value, loaded = l.GetOrAdd("3", 5, 500*time.Millisecond)
|
||||
assert.Equal(t, 5, value)
|
||||
assert.False(t, loaded)
|
||||
}
|
||||
466
services/cache/lru_test.go
поставляемый
Обычный файл
466
services/cache/lru_test.go
поставляемый
Обычный файл
@@ -0,0 +1,466 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLRU(t *testing.T) {
|
||||
l := NewLRU(&LRUOptions{
|
||||
Size: 128,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
|
||||
for i := 0; i < 256; i++ {
|
||||
err := l.Set(fmt.Sprintf("%d", i), i)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
size, err := l.Len()
|
||||
require.Nil(t, err)
|
||||
require.Equalf(t, size, 128, "bad len: %v", size)
|
||||
|
||||
keys, err := l.Keys()
|
||||
require.Nil(t, err)
|
||||
for i, k := range keys {
|
||||
var v int
|
||||
err = l.Get(k, &v)
|
||||
require.Nil(t, err, "bad key: %v", k)
|
||||
require.Equalf(t, fmt.Sprintf("%d", v), k, "bad key: %v", k)
|
||||
require.Equalf(t, i+128, v, "bad value: %v", k)
|
||||
}
|
||||
for i := 0; i < 128; i++ {
|
||||
var v int
|
||||
err = l.Get(fmt.Sprintf("%d", i), &v)
|
||||
require.Equal(t, ErrKeyNotFound, err, "should be evicted %v: %v", i, err)
|
||||
}
|
||||
for i := 128; i < 256; i++ {
|
||||
var v int
|
||||
err = l.Get(fmt.Sprintf("%d", i), &v)
|
||||
require.Nil(t, err, "should not be evicted %v: %v", i, err)
|
||||
}
|
||||
for i := 128; i < 192; i++ {
|
||||
l.Remove(fmt.Sprintf("%d", i))
|
||||
var v int
|
||||
err = l.Get(fmt.Sprintf("%d", i), &v)
|
||||
require.Equal(t, ErrKeyNotFound, err, "should be deleted %v: %v", i, err)
|
||||
}
|
||||
|
||||
var v int
|
||||
err = l.Get("192", &v) // expect 192 to be last key in l.Keys()
|
||||
require.Nil(t, err, "should exist")
|
||||
require.Equalf(t, 192, v, "bad value: %v", v)
|
||||
|
||||
keys, err = l.Keys()
|
||||
require.Nil(t, err)
|
||||
for i, k := range keys {
|
||||
require.Falsef(t, i < 63 && k != fmt.Sprintf("%d", i+193), "out of order key: %v", k)
|
||||
require.Falsef(t, i == 63 && k != "192", "out of order key: %v", k)
|
||||
}
|
||||
|
||||
l.Purge()
|
||||
size, err = l.Len()
|
||||
require.Nil(t, err)
|
||||
require.Equalf(t, size, 0, "bad len: %v", size)
|
||||
err = l.Get("200", &v)
|
||||
require.Equal(t, err, ErrKeyNotFound, "should contain nothing")
|
||||
|
||||
err = l.Set("201", 301)
|
||||
require.Nil(t, err)
|
||||
err = l.Get("201", &v)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 301, v)
|
||||
|
||||
}
|
||||
|
||||
func TestLRUExpire(t *testing.T) {
|
||||
l := NewLRU(&LRUOptions{
|
||||
Size: 128,
|
||||
DefaultExpiry: 1 * time.Second,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
|
||||
l.SetWithDefaultExpiry("1", 1)
|
||||
l.SetWithExpiry("3", 3, 0*time.Second)
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
var r1 int
|
||||
err := l.Get("1", &r1)
|
||||
require.Equal(t, err, ErrKeyNotFound, "should not exist")
|
||||
|
||||
var r2 int
|
||||
err2 := l.Get("3", &r2)
|
||||
require.Nil(t, err2, "should exist")
|
||||
require.Equal(t, 3, r2)
|
||||
}
|
||||
|
||||
func TestLRUMarshalUnMarshal(t *testing.T) {
|
||||
l := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
|
||||
value1 := map[string]interface{}{
|
||||
"key1": 1,
|
||||
"key2": "value2",
|
||||
}
|
||||
err := l.Set("test", value1)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
var value2 map[string]interface{}
|
||||
err = l.Get("test", &value2)
|
||||
require.Nil(t, err)
|
||||
|
||||
v1, ok := value2["key1"].(int64)
|
||||
require.True(t, ok, "unable to cast value")
|
||||
assert.Equal(t, int64(1), v1)
|
||||
|
||||
v2, ok := value2["key2"].(string)
|
||||
require.True(t, ok, "unable to cast value")
|
||||
assert.Equal(t, "value2", v2)
|
||||
|
||||
post := model.Post{
|
||||
Id: "id",
|
||||
CreateAt: 11111,
|
||||
UpdateAt: 11111,
|
||||
DeleteAt: 11111,
|
||||
EditAt: 111111,
|
||||
IsPinned: true,
|
||||
UserId: "UserId",
|
||||
ChannelId: "ChannelId",
|
||||
RootId: "RootId",
|
||||
ParentId: "ParentId",
|
||||
OriginalId: "OriginalId",
|
||||
Message: "OriginalId",
|
||||
MessageSource: "MessageSource",
|
||||
Type: "Type",
|
||||
Props: map[string]interface{}{
|
||||
"key": "val",
|
||||
},
|
||||
Hashtags: "Hashtags",
|
||||
Filenames: []string{"item1", "item2"},
|
||||
FileIds: []string{"item1", "item2"},
|
||||
PendingPostId: "PendingPostId",
|
||||
HasReactions: true,
|
||||
ReplyCount: 11111,
|
||||
Metadata: &model.PostMetadata{
|
||||
Embeds: []*model.PostEmbed{
|
||||
{
|
||||
Type: "Type",
|
||||
URL: "URL",
|
||||
Data: "some data",
|
||||
},
|
||||
{
|
||||
Type: "Type 2",
|
||||
URL: "URL 2",
|
||||
Data: "some data 2",
|
||||
},
|
||||
},
|
||||
Emojis: []*model.Emoji{
|
||||
{
|
||||
Id: "id",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
Files: nil,
|
||||
Images: map[string]*model.PostImage{
|
||||
"key": {
|
||||
Width: 1,
|
||||
Height: 1,
|
||||
Format: "format",
|
||||
FrameCount: 1,
|
||||
},
|
||||
"key2": {
|
||||
Width: 999,
|
||||
Height: 888,
|
||||
Format: "format 2",
|
||||
FrameCount: 1000,
|
||||
},
|
||||
},
|
||||
Reactions: []*model.Reaction{
|
||||
{
|
||||
UserId: "user_id",
|
||||
PostId: "post_id",
|
||||
EmojiName: "emoji_name",
|
||||
CreateAt: 111,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = l.Set("post", post.Clone())
|
||||
require.Nil(t, err)
|
||||
|
||||
var p model.Post
|
||||
err = l.Get("post", &p)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, post.Clone(), p.Clone())
|
||||
}
|
||||
|
||||
func BenchmarkLRU(b *testing.B) {
|
||||
|
||||
value1 := "simplestring"
|
||||
|
||||
b.Run("simple=new", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l2 := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
err := l2.Set("test", value1)
|
||||
require.Nil(b, err)
|
||||
|
||||
var val string
|
||||
err = l2.Get("test", &val)
|
||||
require.Nil(b, err)
|
||||
}
|
||||
})
|
||||
|
||||
type obj struct {
|
||||
Field1 int
|
||||
Field2 string
|
||||
Field3 struct {
|
||||
Field4 int
|
||||
Field5 string
|
||||
}
|
||||
Field6 map[string]string
|
||||
}
|
||||
|
||||
value2 := obj{
|
||||
1,
|
||||
"field2",
|
||||
struct {
|
||||
Field4 int
|
||||
Field5 string
|
||||
}{
|
||||
6,
|
||||
"field5 is a looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string",
|
||||
},
|
||||
map[string]string{
|
||||
"key0": "value0",
|
||||
"key1": "value value1",
|
||||
"key2": "value value value2",
|
||||
"key3": "value value value value3",
|
||||
"key4": "value value value value value4",
|
||||
"key5": "value value value value value value5",
|
||||
"key6": "value value value value value value value6",
|
||||
"key7": "value value value value value value value value7",
|
||||
"key8": "value value value value value value value value value8",
|
||||
"key9": "value value value value value value value value value value9",
|
||||
},
|
||||
}
|
||||
|
||||
b.Run("complex=new", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l2 := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
err := l2.Set("test", value2)
|
||||
require.Nil(b, err)
|
||||
|
||||
var val obj
|
||||
err = l2.Get("test", &val)
|
||||
require.Nil(b, err)
|
||||
}
|
||||
})
|
||||
|
||||
user := &model.User{
|
||||
Id: "id",
|
||||
CreateAt: 11111,
|
||||
UpdateAt: 11111,
|
||||
DeleteAt: 11111,
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
AuthService: "AuthService",
|
||||
AuthData: nil,
|
||||
Email: "Email",
|
||||
EmailVerified: true,
|
||||
Nickname: "Nickname",
|
||||
FirstName: "FirstName",
|
||||
LastName: "LastName",
|
||||
Position: "Position",
|
||||
Roles: "Roles",
|
||||
AllowMarketing: true,
|
||||
Props: map[string]string{
|
||||
"key0": "value0",
|
||||
"key1": "value value1",
|
||||
"key2": "value value value2",
|
||||
"key3": "value value value value3",
|
||||
"key4": "value value value value value4",
|
||||
"key5": "value value value value value value5",
|
||||
"key6": "value value value value value value value6",
|
||||
"key7": "value value value value value value value value7",
|
||||
"key8": "value value value value value value value value value8",
|
||||
"key9": "value value value value value value value value value value9",
|
||||
},
|
||||
NotifyProps: map[string]string{
|
||||
"key0": "value0",
|
||||
"key1": "value value1",
|
||||
"key2": "value value value2",
|
||||
"key3": "value value value value3",
|
||||
"key4": "value value value value value4",
|
||||
"key5": "value value value value value value5",
|
||||
"key6": "value value value value value value value6",
|
||||
"key7": "value value value value value value value value7",
|
||||
"key8": "value value value value value value value value value8",
|
||||
"key9": "value value value value value value value value value value9",
|
||||
},
|
||||
LastPasswordUpdate: 111111,
|
||||
LastPictureUpdate: 111111,
|
||||
FailedAttempts: 111111,
|
||||
Locale: "Locale",
|
||||
Timezone: map[string]string{
|
||||
"key0": "value0",
|
||||
"key1": "value value1",
|
||||
"key2": "value value value2",
|
||||
"key3": "value value value value3",
|
||||
"key4": "value value value value value4",
|
||||
"key5": "value value value value value value5",
|
||||
"key6": "value value value value value value value6",
|
||||
"key7": "value value value value value value value value7",
|
||||
"key8": "value value value value value value value value value8",
|
||||
"key9": "value value value value value value value value value value9",
|
||||
},
|
||||
MfaActive: true,
|
||||
MfaSecret: "MfaSecret",
|
||||
LastActivityAt: 111111,
|
||||
IsBot: true,
|
||||
BotDescription: "field5 is a looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string",
|
||||
BotLastIconUpdate: 111111,
|
||||
TermsOfServiceId: "TermsOfServiceId",
|
||||
TermsOfServiceCreateAt: 111111,
|
||||
}
|
||||
|
||||
b.Run("User=new", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l2 := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
err := l2.Set("test", user)
|
||||
require.Nil(b, err)
|
||||
|
||||
var val model.User
|
||||
err = l2.Get("test", &val)
|
||||
require.Nil(b, err)
|
||||
}
|
||||
})
|
||||
|
||||
post := &model.Post{
|
||||
Id: "id",
|
||||
CreateAt: 11111,
|
||||
UpdateAt: 11111,
|
||||
DeleteAt: 11111,
|
||||
EditAt: 111111,
|
||||
IsPinned: true,
|
||||
UserId: "UserId",
|
||||
ChannelId: "ChannelId",
|
||||
RootId: "RootId",
|
||||
ParentId: "ParentId",
|
||||
OriginalId: "OriginalId",
|
||||
Message: "OriginalId",
|
||||
MessageSource: "MessageSource",
|
||||
Type: "Type",
|
||||
Props: map[string]interface{}{
|
||||
"key": "val",
|
||||
},
|
||||
Hashtags: "Hashtags",
|
||||
Filenames: []string{"item1", "item2"},
|
||||
FileIds: []string{"item1", "item2"},
|
||||
PendingPostId: "PendingPostId",
|
||||
HasReactions: true,
|
||||
|
||||
// Transient data populated before sending a post to the client
|
||||
ReplyCount: 11111,
|
||||
Metadata: &model.PostMetadata{
|
||||
Embeds: []*model.PostEmbed{
|
||||
{
|
||||
Type: "Type",
|
||||
URL: "URL",
|
||||
Data: "some data",
|
||||
},
|
||||
{
|
||||
Type: "Type 2",
|
||||
URL: "URL 2",
|
||||
Data: "some data 2",
|
||||
},
|
||||
},
|
||||
Emojis: []*model.Emoji{
|
||||
{
|
||||
Id: "id",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
Files: nil,
|
||||
Images: map[string]*model.PostImage{
|
||||
"key": {
|
||||
Width: 1,
|
||||
Height: 1,
|
||||
Format: "format",
|
||||
FrameCount: 1,
|
||||
},
|
||||
"key2": {
|
||||
Width: 999,
|
||||
Height: 888,
|
||||
Format: "format 2",
|
||||
FrameCount: 1000,
|
||||
},
|
||||
},
|
||||
Reactions: []*model.Reaction{},
|
||||
},
|
||||
}
|
||||
|
||||
b.Run("Post=new", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l2 := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
err := l2.Set("test", post)
|
||||
require.Nil(b, err)
|
||||
|
||||
var val model.Post
|
||||
err = l2.Get("test", &val)
|
||||
require.Nil(b, err)
|
||||
}
|
||||
})
|
||||
|
||||
status := model.Status{
|
||||
UserId: "UserId",
|
||||
Status: "Status",
|
||||
Manual: true,
|
||||
LastActivityAt: 111111,
|
||||
ActiveChannel: "ActiveChannel",
|
||||
}
|
||||
|
||||
b.Run("Status=new", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l2 := NewLRU(&LRUOptions{
|
||||
Size: 1,
|
||||
DefaultExpiry: 0,
|
||||
InvalidateClusterEvent: "",
|
||||
})
|
||||
err := l2.Set("test", status)
|
||||
require.Nil(b, err)
|
||||
|
||||
var val model.Status
|
||||
err = l2.Get("test", &val)
|
||||
require.Nil(b, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
167
services/cache/mocks/Cache.go
поставляемый
Обычный файл
167
services/cache/mocks/Cache.go
поставляемый
Обычный файл
@@ -0,0 +1,167 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import time "time"
|
||||
|
||||
// Cache is an autogenerated mock type for the Cache type
|
||||
type Cache struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: key, value
|
||||
func (_m *Cache) Get(key string, value interface{}) error {
|
||||
ret := _m.Called(key, value)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}) error); ok {
|
||||
r0 = rf(key, value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetInvalidateClusterEvent provides a mock function with given fields:
|
||||
func (_m *Cache) GetInvalidateClusterEvent() string {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Keys provides a mock function with given fields:
|
||||
func (_m *Cache) Keys() ([]string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 []string
|
||||
if rf, ok := ret.Get(0).(func() []string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Len provides a mock function with given fields:
|
||||
func (_m *Cache) Len() (int, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func() int); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Name provides a mock function with given fields:
|
||||
func (_m *Cache) Name() string {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Purge provides a mock function with given fields:
|
||||
func (_m *Cache) Purge() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Remove provides a mock function with given fields: key
|
||||
func (_m *Cache) Remove(key string) error {
|
||||
ret := _m.Called(key)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(key)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Set provides a mock function with given fields: key, value
|
||||
func (_m *Cache) Set(key string, value interface{}) error {
|
||||
ret := _m.Called(key, value)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}) error); ok {
|
||||
r0 = rf(key, value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetWithDefaultExpiry provides a mock function with given fields: key, value
|
||||
func (_m *Cache) SetWithDefaultExpiry(key string, value interface{}) error {
|
||||
ret := _m.Called(key, value)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}) error); ok {
|
||||
r0 = rf(key, value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetWithExpiry provides a mock function with given fields: key, value, ttl
|
||||
func (_m *Cache) SetWithExpiry(key string, value interface{}, ttl time.Duration) error {
|
||||
ret := _m.Called(key, value, ttl)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}, time.Duration) error); ok {
|
||||
r0 = rf(key, value, ttl)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
57
services/cache/mocks/Provider.go
поставляемый
Обычный файл
57
services/cache/mocks/Provider.go
поставляемый
Обычный файл
@@ -0,0 +1,57 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
cache "github.com/mattermost/mattermost-server/v5/services/cache"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Provider is an autogenerated mock type for the Provider type
|
||||
type Provider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Close provides a mock function with given fields:
|
||||
func (_m *Provider) Close() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Connect provides a mock function with given fields:
|
||||
func (_m *Provider) Connect() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewCache provides a mock function with given fields: opts
|
||||
func (_m *Provider) NewCache(opts *cache.CacheOptions) cache.Cache {
|
||||
ret := _m.Called(opts)
|
||||
|
||||
var r0 cache.Cache
|
||||
if rf, ok := ret.Get(0).(func(*cache.CacheOptions) cache.Cache); ok {
|
||||
r0 = rf(opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(cache.Cache)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
52
services/cache/provider.go
поставляемый
Обычный файл
52
services/cache/provider.go
поставляемый
Обычный файл
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package cache
|
||||
|
||||
import "time"
|
||||
|
||||
// CacheOptions contains options for initializaing a cache
|
||||
type CacheOptions struct {
|
||||
Size int
|
||||
DefaultExpiry time.Duration
|
||||
Name string
|
||||
InvalidateClusterEvent string
|
||||
}
|
||||
|
||||
// Provider is a provider for Cache
|
||||
type Provider interface {
|
||||
// NewCache creates a new cache with given options.
|
||||
NewCache(opts *CacheOptions) Cache
|
||||
// Connect opens a new connection to the cache using specific provider parameters.
|
||||
Connect() error
|
||||
// Close releases any resources used by the cache provider.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type cacheProvider struct {
|
||||
}
|
||||
|
||||
// NewProvider creates a new CacheProvider
|
||||
func NewProvider() Provider {
|
||||
return &cacheProvider{}
|
||||
}
|
||||
|
||||
// NewCache creates a new cache with given opts
|
||||
func (c *cacheProvider) NewCache(opts *CacheOptions) Cache {
|
||||
return NewLRU(&LRUOptions{
|
||||
Name: opts.Name,
|
||||
Size: opts.Size,
|
||||
DefaultExpiry: opts.DefaultExpiry,
|
||||
InvalidateClusterEvent: opts.InvalidateClusterEvent,
|
||||
})
|
||||
}
|
||||
|
||||
// Connect opens a new connection to the cache using specific provider parameters.
|
||||
func (c *cacheProvider) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close releases any resources used by the cache provider.
|
||||
func (c *cacheProvider) Close() error {
|
||||
return nil
|
||||
}
|
||||
72
services/cache/provider_test.go
поставляемый
Обычный файл
72
services/cache/provider_test.go
поставляемый
Обычный файл
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewCache(t *testing.T) {
|
||||
t.Run("with only size option given", func(t *testing.T) {
|
||||
p := NewProvider()
|
||||
|
||||
size := 1
|
||||
c := p.NewCache(&CacheOptions{
|
||||
Size: size,
|
||||
})
|
||||
|
||||
err := c.Set("key1", "val1")
|
||||
require.Nil(t, err)
|
||||
err = c.Set("key2", "val2")
|
||||
require.Nil(t, err)
|
||||
l, err := c.Len()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, size, l)
|
||||
})
|
||||
|
||||
t.Run("with all options specified", func(t *testing.T) {
|
||||
p := NewProvider()
|
||||
|
||||
size := 1
|
||||
expiry := 1 * time.Second
|
||||
event := "clusterEvent"
|
||||
c := p.NewCache(&CacheOptions{
|
||||
Size: size,
|
||||
Name: "name",
|
||||
DefaultExpiry: expiry,
|
||||
InvalidateClusterEvent: event,
|
||||
})
|
||||
|
||||
require.Equal(t, event, c.GetInvalidateClusterEvent())
|
||||
|
||||
err := c.SetWithDefaultExpiry("key1", "val1")
|
||||
require.Nil(t, err)
|
||||
err = c.SetWithDefaultExpiry("key2", "val2")
|
||||
require.Nil(t, err)
|
||||
l, err := c.Len()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, size, l)
|
||||
|
||||
time.Sleep(expiry + 1*time.Second)
|
||||
|
||||
var v string
|
||||
err = c.Get("key1", &v)
|
||||
require.Equal(t, ErrKeyNotFound, err)
|
||||
err = c.Get("key2", &v)
|
||||
require.Equal(t, ErrKeyNotFound, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnectClose(t *testing.T) {
|
||||
p := NewProvider()
|
||||
|
||||
err := p.Connect()
|
||||
require.Nil(t, err)
|
||||
|
||||
err = p.Close()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user