1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
// Package memstore offers an in-memory store implementation for throttled.
package memstore // import "github.com/throttled/throttled/store/memstore"
import (
"sync"
"sync/atomic"
"time"
"github.com/hashicorp/golang-lru"
)
// MemStore is an in-memory store implementation for throttled. It
// supports evicting the least recently used keys to control memory
// usage. It is stored in memory in the current process and thus
// doesn't share state with other rate limiters.
type MemStore struct {
sync.RWMutex
keys *lru.Cache
m map[string]*int64
}
// New initializes a Store. If maxKeys > 0, the number of different
// keys is restricted to the specified amount. In this case, it uses
// an LRU algorithm to evict older keys to make room for newer
// ones. If maxKeys <= 0, there is no limit on the number of keys,
// which may use an unbounded amount of memory.
func New(maxKeys int) (*MemStore, error) {
var m *MemStore
if maxKeys > 0 {
keys, err := lru.New(maxKeys)
if err != nil {
return nil, err
}
m = &MemStore{
keys: keys,
}
} else {
m = &MemStore{
m: make(map[string]*int64),
}
}
return m, nil
}
// GetWithTime returns the value of the key if it is in the store or
// -1 if it does not exist. It also returns the current local time on
// the machine.
func (ms *MemStore) GetWithTime(key string) (int64, time.Time, error) {
now := time.Now()
valP, ok := ms.get(key, false)
if !ok {
return -1, now, nil
}
return atomic.LoadInt64(valP), now, nil
}
// SetIfNotExistsWithTTL sets the value of key only if it is not
// already set in the store it returns whether a new value was set. It
// ignores the ttl.
func (ms *MemStore) SetIfNotExistsWithTTL(key string, value int64, _ time.Duration) (bool, error) {
_, ok := ms.get(key, false)
if ok {
return false, nil
}
ms.Lock()
defer ms.Unlock()
_, ok = ms.get(key, true)
if ok {
return false, nil
}
// Store a pointer to a new instance so that the caller
// can't mutate the value after setting
v := value
if ms.keys != nil {
ms.keys.Add(key, &v)
} else {
ms.m[key] = &v
}
return true, nil
}
// CompareAndSwapWithTTL atomically compares the value at key to the
// old value. If it matches, it sets it to the new value and returns
// true. Otherwise, it returns false. If the key does not exist in the
// store, it returns false with no error. It ignores the ttl.
func (ms *MemStore) CompareAndSwapWithTTL(key string, old, new int64, _ time.Duration) (bool, error) {
valP, ok := ms.get(key, false)
if !ok {
return false, nil
}
return atomic.CompareAndSwapInt64(valP, old, new), nil
}
func (ms *MemStore) get(key string, locked bool) (*int64, bool) {
var valP *int64
var ok bool
if ms.keys != nil {
var valI interface{}
valI, ok = ms.keys.Get(key)
if ok {
valP = valI.(*int64)
}
} else {
if !locked {
ms.RLock()
defer ms.RUnlock()
}
valP, ok = ms.m[key]
}
return valP, ok
}
|