good morning!!!!

Skip to content
Commits on Source (2)
# goutil
a collection of things that we commonly use, so we don't need to keep redefining them for each project
a collection of things that we commonly use, so we don't need to keep redefining them for each project
note that the api is NOT STABLE across versions, however i WILL NOT remove old tags
that means that ANY update for goutil is not neccesarily safe. I will try to keep them mostly compatible, but things like removing unneeded will be done
most likely this repo will be renamed and restructured if that guarantee is change
package bitmap
import (
"math/big"
)
type U256 struct {
i *big.Int
}
func NewU256FromBig(b *big.Int) *U256 {
out := &U256{
i: b,
}
return out
}
func (u *U256) ReadBit(idx int) bool {
return u.i.Bit(idx) > 0
}
func (u *U256) ReadInt(idx int, size int) int {
output := 0
offset := 0
for i := idx; i < idx+size; i++ {
if u.ReadBit(i) {
output = output + 1<<offset
}
offset = offset + 1
}
return output
}
package bitmap
import (
"log"
"math/big"
"testing"
)
func TestUint256(t *testing.T) {
i, _ := new(big.Int).SetString("36893853548754508715888", 0)
u2 := NewU256FromBig(i)
ri := u2.ReadInt(0, 16)
log.Println(ri)
}
module gfx.cafe/open/goutil
go 1.18
require (
github.com/dboslee/lru v0.0.1
github.com/stretchr/testify v1.8.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dboslee/lru v0.0.1 h1:PMT+59nkGSkf9Tcb4YMw5B08ilpGgNSmRjEyNK2JVoE=
github.com/dboslee/lru v0.0.1/go.mod h1:vDIFJHUqr1vdYKAdG9x3r+zFWP0i9uJqQWpB6nSuHxM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package lru
type LRU[K comparable, V any] interface {
Get(K) (V, bool)
Set(K, V)
Delete(K) bool
Peek(K) (V, bool)
Flush()
Len() int
}
package lru
// Element is an element in a linked list.
type Element[T any] struct {
prev, next *Element[T]
list *List[T]
Value T
}
// Next returns the next item in the list.
func (e *Element[T]) Next() *Element[T] {
if e.list == nil || e.next == &e.list.root {
return nil
}
return e.next
}
// Prev returns the previous item in the list.
func (e *Element[T]) Prev() *Element[T] {
if e.list == nil || e.prev == &e.list.root {
return nil
}
return e.prev
}
// List implements a generic linked list based off of container/list. This
// contains the minimimum functionally required for an lru cache.
type List[T any] struct {
root Element[T]
len int
}
// NewList creates a new linked list.
func NewList[T any]() *List[T] {
l := &List[T]{}
l.Init()
return l
}
// Init intializes the list with no elements.
func (l *List[T]) Init() {
l.root = Element[T]{}
l.root.prev = &l.root
l.root.next = &l.root
l.len = 0
}
// Len is the number of elements in the list.
func (l *List[T]) Len() int {
return l.len
}
// MoveToFront moves the given element to the front of the list.
func (l *List[T]) MoveToFront(e *Element[T]) {
if e.list != l || l.root.next == e {
return
}
l.move(e, &l.root)
}
func (l *List[T]) move(e, at *Element[T]) *Element[T] {
if e == at {
return e
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
return e
}
// Remove removes the given element from the list.
func (l *List[T]) Remove(e *Element[T]) T {
e.prev.next = e.next
e.next.prev = e.prev
e.list.len--
e.next = nil
e.prev = nil
e.list = nil
return e.Value
}
// PushFront adds a new value to the front of the list.
func (l *List[T]) PushFront(value T) *Element[T] {
return l.insert(&Element[T]{Value: value}, &l.root)
}
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
// Back returns the last element in the list.
func (l *List[T]) Back() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}
package lru
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestPushRemove(t *testing.T) {
ll := NewList[int]()
length := 10
for i := 1; i <= length; i++ {
ll.PushFront(i)
require.Equal(t, ll.Len(), i)
}
for i := length; i <= 1; i++ {
ll.Remove(ll.Back())
require.Equal(t, ll.Len(), i)
}
}
func TestMoveToFront(t *testing.T) {
ll := NewList[int]()
e := ll.PushFront(0)
ll.PushFront(1)
require.Equal(t, e, ll.Back())
ll.MoveToFront(e)
require.NotEqual(t, e, ll.Back())
}
func TestInit(t *testing.T) {
ll := NewList[int]()
ll.PushFront(1)
require.Equal(t, ll.Len(), 1)
ll.Init()
require.Equal(t, ll.Len(), 0)
}
package lru
import (
"container/list"
"errors"
"time"
)
// EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback func(key interface{}, value interface{})
// LRU implements a non-thread safe fixed size LRU cache
type LRU struct {
size int
evictList *list.List
items map[interface{}]*list.Element
expire time.Duration
onEvict EvictCallback
}
var _ LRU[int, int] = &Cache[int, int]{}
// entry is used to hold a value in the evictList
type entry struct {
key interface{}
value interface{}
expire *time.Time
// Cache is a lru cache. It automatically removes elements as new elements are
// added if the capacity is reached. Items are removes based on how recently
// they were used where the oldest items are removed first.
type Cache[K comparable, V any] struct {
ll *List[entry[K, V]]
items map[K]*Element[entry[K, V]]
options *options
}
func (e *entry) IsExpired() bool {
if e.expire == nil {
return false
}
return time.Now().After(*e.expire)
type entry[K comparable, V any] struct {
key K
value V
}
// NewLRU constructs an LRU of the given size
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
if size <= 0 {
return nil, errors.New("Must provide a positive size")
}
c := &LRU{
size: size,
evictList: list.New(),
items: make(map[interface{}]*list.Element),
expire: 0,
onEvict: onEvict,
// New initializes a new lru cache with the given capacity.
func New[K comparable, V any](cacheOptions ...CacheOption) *Cache[K, V] {
c := &Cache[K, V]{
ll: NewList[entry[K, V]](),
items: make(map[K]*Element[entry[K, V]]),
options: defaultOptions(),
}
return c, nil
}
// NewLRUWithExpire contrusts an LRU of the given size and expire time
func NewLRUWithExpire(size int, expire time.Duration, onEvict EvictCallback) (*LRU, error) {
if size <= 0 {
return nil, errors.New("Must provide a positive size")
}
c := &LRU{
size: size,
evictList: list.New(),
items: make(map[interface{}]*list.Element),
expire: expire,
onEvict: onEvict,
for _, option := range cacheOptions {
option.apply(c.options)
}
return c, nil
}
// Purge is used to completely clear the cache
func (c *LRU) Purge() {
for k, v := range c.items {
if c.onEvict != nil {
c.onEvict(k, v.Value.(*entry).value)
}
delete(c.items, k)
}
c.evictList.Init()
return c
}
// Add adds a value to the cache. Returns true if an eviction occurred.
func (c *LRU) Add(key, value interface{}) bool {
return c.AddEx(key, value, 0)
// Len is the number of key value pairs in the cache.
func (c *Cache[K, V]) Len() int {
return c.ll.Len()
}
// AddEx adds a value to the cache with expire. Returns true if an eviction occurred.
func (c *LRU) AddEx(key, value interface{}, expire time.Duration) bool {
var ex *time.Time = nil
if expire > 0 {
expire := time.Now().Add(expire)
ex = &expire
} else if c.expire > 0 {
expire := time.Now().Add(c.expire)
ex = &expire
}
// Check for existing item
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
ent.Value.(*entry).value = value
ent.Value.(*entry).expire = ex
return false
}
// Add new item
ent := &entry{key: key, value: value, expire: ex}
entry := c.evictList.PushFront(ent)
c.items[key] = entry
evict := c.evictList.Len() > c.size
// Verify size not exceeded
if evict {
c.removeOldest()
// Set the given key value pair.
// This operation updates the recent usage of the item.
func (c *Cache[K, V]) Set(key K, value V) {
if element, ok := c.items[key]; ok {
element.Value.value = value
c.ll.MoveToFront(element)
return
}
return evict
}
// Get looks up a key's value from the cache.
func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
if ent, ok := c.items[key]; ok {
if ent.Value.(*entry).IsExpired() {
return nil, false
}
c.evictList.MoveToFront(ent)
return ent.Value.(*entry).value, true
entry := entry[K, V]{
key: key,
value: value,
}
return
}
// Check if a key is in the cache, without updating the recent-ness
// or deleting it for being stale.
func (c *LRU) Contains(key interface{}) (ok bool) {
if ent, ok := c.items[key]; ok {
if ent.Value.(*entry).IsExpired() {
return false
}
return ok
e := c.ll.PushFront(entry)
if c.ll.Len() > c.options.capacity {
c.deleteElement(c.ll.Back())
}
return
c.items[key] = e
}
// Returns the key value (or undefined if not found) without updating
// the "recently used"-ness of the key.
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
if ent, ok := c.items[key]; ok {
if ent.Value.(*entry).IsExpired() {
return nil, false
}
return ent.Value.(*entry).value, true
// Get an item from the cache.
// This operation updates recent usage of the item.
func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
e, ok := c.items[key]
if !ok {
return
}
return nil, ok
}
// Remove removes the provided key from the cache, returning if the
// key was contained.
func (c *LRU) Remove(key interface{}) bool {
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
return true
}
return false
c.ll.MoveToFront(e)
return e.Value.value, true
}
// RemoveOldest removes the oldest item from the cache.
func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
kv := ent.Value.(*entry)
return kv.key, kv.value, true
// Peek gets an item from the cache without updating the recent usage.
func (c *Cache[K, V]) Peek(key K) (value V, ok bool) {
e, ok := c.items[key]
if !ok {
return
}
return nil, nil, false
}
// GetOldest returns the oldest entry
func (c *LRU) GetOldest() (interface{}, interface{}, bool) {
ent := c.evictList.Back()
if ent != nil {
kv := ent.Value.(*entry)
return kv.key, kv.value, true
}
return nil, nil, false
return e.Value.value, true
}
// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *LRU) Keys() []interface{} {
keys := make([]interface{}, len(c.items))
i := 0
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
keys[i] = ent.Value.(*entry).key
i++
// Delete an item from the cache.
func (c *Cache[K, V]) Delete(key K) bool {
e, ok := c.items[key]
if !ok {
return false
}
return keys
}
// Len returns the number of items in the cache.
func (c *LRU) Len() int {
return c.evictList.Len()
}
c.deleteElement(e)
// Resize changes the cache size.
func (c *LRU) Resize(size int) (evicted int) {
diff := c.Len() - size
if diff < 0 {
diff = 0
}
for i := 0; i < diff; i++ {
c.removeOldest()
}
c.size = size
return diff
return true
}
// removeOldest removes the oldest item from the cache.
func (c *LRU) removeOldest() {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
}
func (c *Cache[K, V]) deleteElement(e *Element[entry[K, V]]) {
delete(c.items, e.Value.key)
c.ll.Remove(e)
}
// removeElement is used to remove a given list element from the cache
func (c *LRU) removeElement(e *list.Element) {
c.evictList.Remove(e)
kv := e.Value.(*entry)
delete(c.items, kv.key)
if c.onEvict != nil {
c.onEvict(kv.key, kv.value)
}
// Flush deletes all items from the cache.
func (c *Cache[K, V]) Flush() {
c.ll.Init()
c.items = make(map[K]*Element[entry[K, V]])
}
package lru
package lru_test
import (
"testing"
"time"
)
func TestLRU(t *testing.T) {
evictCounter := 0
onEvicted := func(k interface{}, v interface{}) {
if k != v {
t.Fatalf("Evict values not equal (%v!=%v)", k, v)
}
evictCounter += 1
}
l, err := NewLRU(128, onEvicted)
if err != nil {
t.Fatalf("err: %v", err)
}
for i := 0; i < 256; i++ {
l.Add(i, i)
}
if l.Len() != 128 {
t.Fatalf("bad len: %v", l.Len())
}
"github.com/dboslee/lru"
"github.com/stretchr/testify/require"
)
if evictCounter != 128 {
t.Fatalf("bad evict count: %v", evictCounter)
func TestCapacity(t *testing.T) {
tests := []struct {
capacity int
}{
{1},
{10},
{100},
}
for i, k := range l.Keys() {
if v, ok := l.Get(k); !ok || v != k || v != i+128 {
t.Fatalf("bad key: %v", k)
}
}
for i := 0; i < 128; i++ {
_, ok := l.Get(i)
if ok {
t.Fatalf("should be evicted")
}
}
for i := 128; i < 256; i++ {
_, ok := l.Get(i)
if !ok {
t.Fatalf("should not be evicted")
}
}
for i := 128; i < 192; i++ {
ok := l.Remove(i)
if !ok {
t.Fatalf("should be contained")
}
ok = l.Remove(i)
if ok {
t.Fatalf("should not be contained")
}
_, ok = l.Get(i)
if ok {
t.Fatalf("should be deleted")
lru.New[int, int](lru.WithCapacity(10))
for _, tc := range tests {
lru := lru.New[int, int](lru.WithCapacity(tc.capacity))
for i := 0; i < tc.capacity+1; i++ {
lru.Set(i, i)
}
}
l.Get(192) // expect 192 to be last key in l.Keys()
for i, k := range l.Keys() {
if (i < 63 && k != i+193) || (i == 63 && k != 192) {
t.Fatalf("out of order key: %v", k)
}
}
l.Purge()
if l.Len() != 0 {
t.Fatalf("bad len: %v", l.Len())
}
if _, ok := l.Get(200); ok {
t.Fatalf("should contain nothing")
}
}
require.Equal(t, tc.capacity, lru.Len(), "expected capacity to be full")
func TestLRU_GetOldest_RemoveOldest(t *testing.T) {
l, err := NewLRU(128, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
for i := 0; i < 256; i++ {
l.Add(i, i)
}
k, _, ok := l.GetOldest()
if !ok {
t.Fatalf("missing")
}
if k.(int) != 128 {
t.Fatalf("bad: %v", k)
}
k, _, ok = l.RemoveOldest()
if !ok {
t.Fatalf("missing")
}
if k.(int) != 128 {
t.Fatalf("bad: %v", k)
}
_, ok := lru.Get(0)
require.False(t, ok, "expected key to be evicted")
k, _, ok = l.RemoveOldest()
if !ok {
t.Fatalf("missing")
}
if k.(int) != 129 {
t.Fatalf("bad: %v", k)
_, ok = lru.Get(1)
require.True(t, ok, "expected key to exist")
}
}
// Test that Add returns true/false if an eviction occurred
func TestLRU_Add(t *testing.T) {
evictCounter := 0
onEvicted := func(k interface{}, v interface{}) {
evictCounter += 1
}
l, err := NewLRU(1, onEvicted)
if err != nil {
t.Fatalf("err: %v", err)
}
if l.Add(1, 1) == true || evictCounter != 0 {
t.Errorf("should not have an eviction")
}
if l.Add(2, 2) == false || evictCounter != 1 {
t.Errorf("should have an eviction")
}
func TestGetMissing(t *testing.T) {
lru := lru.New[int, int]()
_, ok := lru.Get(0)
require.False(t, ok, "expected not ok")
}
// Test that Contains doesn't update recent-ness
func TestLRU_Contains(t *testing.T) {
l, err := NewLRU(2, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
func TestSetGet(t *testing.T) {
lru := lru.New[int, int]()
value := 100
l.Add(1, 1)
l.Add(2, 2)
if !l.Contains(1) {
t.Errorf("1 should be contained")
}
lru.Set(1, value)
value, ok := lru.Get(1)
l.Add(3, 3)
if l.Contains(1) {
t.Errorf("Contains should not have updated recent-ness of 1")
}
require.True(t, ok, "expected ok")
require.Equal(t, value, value, "expected set value %s", value)
}
// Test that Peek doesn't update recent-ness
func TestLRU_Peek(t *testing.T) {
l, err := NewLRU(2, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
func TestDelete(t *testing.T) {
lru := lru.New[int, int]()
l.Add(1, 1)
l.Add(2, 2)
if v, ok := l.Peek(1); !ok || v != 1 {
t.Errorf("1 should be set to 1: %v, %v", v, ok)
}
key, value := 1, 100
lru.Set(key, value)
require.Equal(t, lru.Len(), 1)
l.Add(3, 3)
if l.Contains(1) {
t.Errorf("should not have updated recent-ness of 1")
}
ok := lru.Delete(key)
require.True(t, ok, "expected ok")
}
// Test that expire feature
func TestLRU_Expire(t *testing.T) {
l, err := NewLRUWithExpire(2, 2*time.Second, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
l.Add(1, 1)
if !l.Contains(1) {
t.Errorf("1 should be contained")
}
time.Sleep(2 * time.Second)
if l.Contains(1) {
t.Errorf("1 should not be contained")
}
l.AddEx(1, 1, 1*time.Second)
if !l.Contains(1) {
t.Errorf("1 should be contained")
}
time.Sleep(1 * time.Second)
if l.Contains(1) {
t.Errorf("1 should not be contained")
}
func TestDeleteMissing(t *testing.T) {
lru := lru.New[int, int]()
key := 100
ok := lru.Delete(key)
require.False(t, ok, "expected not ok")
}
// Test that Resize can upsize and downsize
func TestLRU_Resize(t *testing.T) {
onEvictCounter := 0
onEvicted := func(k interface{}, v interface{}) {
onEvictCounter++
}
l, err := NewLRU(2, onEvicted)
if err != nil {
t.Fatalf("err: %v", err)
}
func TestFlush(t *testing.T) {
lru := lru.New[int, int]()
key, value := 1, 100
lru.Set(key, value)
require.Equal(t, lru.Len(), 1)
// Downsize
l.Add(1, 1)
l.Add(2, 2)
evicted := l.Resize(1)
if evicted != 1 {
t.Errorf("1 element should have been evicted: %v", evicted)
}
if onEvictCounter != 1 {
t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
}
l.Add(3, 3)
if l.Contains(1) {
t.Errorf("Element 1 should have been evicted")
}
lru.Flush()
require.Equal(t, lru.Len(), 0)
// Upsize
evicted = l.Resize(2)
if evicted != 0 {
t.Errorf("0 elements should have been evicted: %v", evicted)
}
l.Add(4, 4)
if !l.Contains(3) || !l.Contains(4) {
t.Errorf("Cache should have contained 2 elements")
}
_, ok := lru.Get(key)
require.False(t, ok, "expected not ok")
}
package lru
const (
// DefaultCacity is the default cache capacity
DefaultCapacity = 10000
)
// CacheOption configurs a lru cache.
type CacheOption interface {
apply(*options)
}
// funcCacheOption wraps a function to implement the CacheOption interface.
type funcCacheOption func(o *options)
func (f funcCacheOption) apply(o *options) {
f(o)
}
// WithCapacity configures how many items can be stored before old items begin
// to be deleted.
func WithCapacity(capacity int) CacheOption {
return funcCacheOption(func(o *options) {
o.capacity = capacity
})
}
// options for a cache instance.
type options struct {
capacity int
}
// defaultOptions returns options with default values set.
func defaultOptions() *options {
return &options{
capacity: DefaultCapacity,
}
}
package lru
import (
"sync"
)
var _ LRU[int, int] = &SyncCache[int, int]{}
// SyncCache is a threadsafe lru cache.
type SyncCache[K comparable, V any] struct {
cache *Cache[K, V]
mu sync.RWMutex
}
// New initializes a new lru cache with the given capacity.
func NewSync[K comparable, V any](options ...CacheOption) *SyncCache[K, V] {
return &SyncCache[K, V]{
cache: New[K, V](options...),
}
}
// Len is the number of key value pairs in the cache.
func (c *SyncCache[K, V]) Len() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.cache.Len()
}
// Set the given key value pair.
// This operation updates the recent usage of the item.
func (c *SyncCache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.cache.Set(key, value)
}
// Get an item from the cache.
// This operation updates recent usage of the item.
func (c *SyncCache[K, V]) Get(key K) (value V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Get(key)
}
// Peek gets an item from the cache without updating the recent usage.
func (c *SyncCache[K, V]) Peek(key K) (value V, ok bool) {
c.mu.RLock()
defer c.mu.RUnlock()
return c.cache.Peek(key)
}
// Delete an item from the cache.
func (c *SyncCache[K, V]) Delete(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Delete(key)
}
// Flush deletes all items from the cache.
func (c *SyncCache[K, V]) Flush() {
c.mu.Lock()
defer c.mu.Unlock()
c.cache.Flush()
}
package lru
import (
"container/list"
"sync"
"time"
)
type ConcurrentLRU struct {
u *LRU
sync.RWMutex
}
// NewConcurrentLRU constructs an ConcurrentLRU of the given size
func NewConcurrentLRU(size int, onEvict EvictCallback) (*ConcurrentLRU, error) {
c := &ConcurrentLRU{}
var err error
c.u, err = NewLRU(size, onEvict)
return c, err
}
// NewConcurrentLRUWithExpire contrusts an ConcurrentLRU of the given size and expire time
func NewConcurrentLRUWithExpire(size int, expire time.Duration, onEvict EvictCallback) (*ConcurrentLRU, error) {
c := &ConcurrentLRU{}
var err error
c.u, err = NewLRUWithExpire(size, expire, onEvict)
return c, err
}
// Purge is used to completely clear the cache
func (c *ConcurrentLRU) Purge() {
c.Lock()
defer c.Unlock()
c.u.Purge()
}
// Add adds a value to the cache. Returns true if an eviction occurred.
func (c *ConcurrentLRU) Add(key, value interface{}) bool {
c.Lock()
defer c.Unlock()
return c.u.Add(key, value)
}
// AddEx adds a value to the cache with expire. Returns true if an eviction occurred.
func (c *ConcurrentLRU) AddEx(key, value interface{}, expire time.Duration) bool {
c.Lock()
defer c.Unlock()
return c.u.AddEx(key, value, expire)
}
// Get looks up a key's value from the cache.
func (c *ConcurrentLRU) Get(key interface{}) (value interface{}, ok bool) {
c.Lock()
defer c.Unlock()
return c.u.Get(key)
}
// Check if a key is in the cache, without updating the recent-ness
// or deleting it for being stale.
func (c *ConcurrentLRU) Contains(key interface{}) (ok bool) {
c.Lock()
defer c.Unlock()
return c.u.Contains(key)
}
// Returns the key value (or undefined if not found) without updating
// the "recently used"-ness of the key.
func (c *ConcurrentLRU) Peek(key interface{}) (value interface{}, ok bool) {
c.Lock()
defer c.Unlock()
return c.u.Peek(key)
}
// Remove removes the provided key from the cache, returning if the
// key was contained.
func (c *ConcurrentLRU) Remove(key interface{}) bool {
c.Lock()
defer c.Unlock()
return c.u.Remove(key)
}
// RemoveOldest removes the oldest item from the cache.
func (c *ConcurrentLRU) RemoveOldest() (interface{}, interface{}, bool) {
c.Lock()
defer c.Unlock()
return c.u.RemoveOldest()
}
// GetOldest returns the oldest entry
func (c *ConcurrentLRU) GetOldest() (interface{}, interface{}, bool) {
c.Lock()
defer c.Unlock()
return c.u.GetOldest()
}
// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *ConcurrentLRU) Keys() []interface{} {
c.Lock()
defer c.Unlock()
return c.u.Keys()
}
// Len returns the number of items in the cache.
func (c *ConcurrentLRU) Len() int {
c.Lock()
defer c.Unlock()
return c.u.Len()
}
// Resize changes the cache size.
func (c *ConcurrentLRU) Resize(size int) (evicted int) {
c.Lock()
defer c.Unlock()
return c.u.Resize(size)
}
// removeOldest removes the oldest item from the cache.
func (c *ConcurrentLRU) removeOldest() {
c.Lock()
defer c.Unlock()
c.u.removeOldest()
}
// removeElement is used to remove a given list element from the cache
func (c *ConcurrentLRU) removeElement(e *list.Element) {
c.Lock()
defer c.Unlock()
c.u.removeElement(e)
}
package lru
import (
"testing"
"time"
)
func TestConcurrentLRU(t *testing.T) {
evictCounter := 0
onEvicted := func(k interface{}, v interface{}) {
if k != v {
t.Fatalf("Evict values not equal (%v!=%v)", k, v)
}
evictCounter += 1
}
l, err := NewConcurrentLRU(128, onEvicted)
if err != nil {
t.Fatalf("err: %v", err)
}
for i := 0; i < 256; i++ {
l.Add(i, i)
}
if l.Len() != 128 {
t.Fatalf("bad len: %v", l.Len())
}
if evictCounter != 128 {
t.Fatalf("bad evict count: %v", evictCounter)
}
for i, k := range l.Keys() {
if v, ok := l.Get(k); !ok || v != k || v != i+128 {
t.Fatalf("bad key: %v", k)
}
}
for i := 0; i < 128; i++ {
_, ok := l.Get(i)
if ok {
t.Fatalf("should be evicted")
}
}
for i := 128; i < 256; i++ {
_, ok := l.Get(i)
if !ok {
t.Fatalf("should not be evicted")
}
}
for i := 128; i < 192; i++ {
ok := l.Remove(i)
if !ok {
t.Fatalf("should be contained")
}
ok = l.Remove(i)
if ok {
t.Fatalf("should not be contained")
}
_, ok = l.Get(i)
if ok {
t.Fatalf("should be deleted")
}
}
l.Get(192) // expect 192 to be last key in l.Keys()
for i, k := range l.Keys() {
if (i < 63 && k != i+193) || (i == 63 && k != 192) {
t.Fatalf("out of order key: %v", k)
}
}
l.Purge()
if l.Len() != 0 {
t.Fatalf("bad len: %v", l.Len())
}
if _, ok := l.Get(200); ok {
t.Fatalf("should contain nothing")
}
}
func TestConcurrentLRU_GetOldest_RemoveOldest(t *testing.T) {
l, err := NewConcurrentLRU(128, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
for i := 0; i < 256; i++ {
l.Add(i, i)
}
k, _, ok := l.GetOldest()
if !ok {
t.Fatalf("missing")
}
if k.(int) != 128 {
t.Fatalf("bad: %v", k)
}
k, _, ok = l.RemoveOldest()
if !ok {
t.Fatalf("missing")
}
if k.(int) != 128 {
t.Fatalf("bad: %v", k)
}
k, _, ok = l.RemoveOldest()
if !ok {
t.Fatalf("missing")
}
if k.(int) != 129 {
t.Fatalf("bad: %v", k)
}
}
// Test that Add returns true/false if an eviction occurred
func TestConcurrentLRU_Add(t *testing.T) {
evictCounter := 0
onEvicted := func(k interface{}, v interface{}) {
evictCounter += 1
}
l, err := NewConcurrentLRU(1, onEvicted)
if err != nil {
t.Fatalf("err: %v", err)
}
if l.Add(1, 1) == true || evictCounter != 0 {
t.Errorf("should not have an eviction")
}
if l.Add(2, 2) == false || evictCounter != 1 {
t.Errorf("should have an eviction")
}
}
// Test that Contains doesn't update recent-ness
func TestConcurrentLRU_Contains(t *testing.T) {
l, err := NewConcurrentLRU(2, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
l.Add(1, 1)
l.Add(2, 2)
if !l.Contains(1) {
t.Errorf("1 should be contained")
}
l.Add(3, 3)
if l.Contains(1) {
t.Errorf("Contains should not have updated recent-ness of 1")
}
}
// Test that Peek doesn't update recent-ness
func TestConcurrentLRU_Peek(t *testing.T) {
l, err := NewConcurrentLRU(2, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
l.Add(1, 1)
l.Add(2, 2)
if v, ok := l.Peek(1); !ok || v != 1 {
t.Errorf("1 should be set to 1: %v, %v", v, ok)
}
l.Add(3, 3)
if l.Contains(1) {
t.Errorf("should not have updated recent-ness of 1")
}
}
// Test that expire feature
func TestConcurrentLRU_Expire(t *testing.T) {
l, err := NewConcurrentLRUWithExpire(2, 2*time.Second, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
l.Add(1, 1)
if !l.Contains(1) {
t.Errorf("1 should be contained")
}
time.Sleep(2 * time.Second)
if l.Contains(1) {
t.Errorf("1 should not be contained")
}
l.AddEx(1, 1, 1*time.Second)
if !l.Contains(1) {
t.Errorf("1 should be contained")
}
time.Sleep(1 * time.Second)
if l.Contains(1) {
t.Errorf("1 should not be contained")
}
}
// Test that Resize can upsize and downsize
func TestConcurrentLRU_Resize(t *testing.T) {
onEvictCounter := 0
onEvicted := func(k interface{}, v interface{}) {
onEvictCounter++
}
l, err := NewConcurrentLRU(2, onEvicted)
if err != nil {
t.Fatalf("err: %v", err)
}
// Downsize
l.Add(1, 1)
l.Add(2, 2)
evicted := l.Resize(1)
if evicted != 1 {
t.Errorf("1 element should have been evicted: %v", evicted)
}
if onEvictCounter != 1 {
t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
}
l.Add(3, 3)
if l.Contains(1) {
t.Errorf("Element 1 should have been evicted")
}
// Upsize
evicted = l.Resize(2)
if evicted != 0 {
t.Errorf("0 elements should have been evicted: %v", evicted)
}
l.Add(4, 4)
if !l.Contains(3) || !l.Contains(4) {
t.Errorf("Cache should have contained 2 elements")
}
}