Code Monkey home page Code Monkey logo

gocache's Introduction

Test GoDoc GoReportCard codecov

Gocache

Guess what is Gocache? a Go cache library. This is an extendable cache library that brings you a lot of features for caching data.

Overview

Here is what it brings in detail:

  • ✅ Multiple cache stores: actually in memory, redis, or your own custom store
  • ✅ A chain cache: use multiple cache with a priority order (memory then fallback to a redis shared cache for instance)
  • ✅ A loadable cache: allow you to call a callback function to put your data back in cache
  • ✅ A metric cache to let you store metrics about your caches usage (hits, miss, set success, set error, ...)
  • ✅ A marshaler to automatically marshal/unmarshal your cache values as a struct
  • ✅ Define default values in stores and override them when setting data
  • ✅ Cache invalidation by expiration time and/or using tags
  • ✅ Use of Generics

Built-in stores

Built-in metrics providers

Installation

To begin working with the latest version of gocache, you can import the library in your project:

go get github.com/eko/gocache/lib/v4

and then, import the store(s) you want to use between all available ones:

go get github.com/eko/gocache/store/bigcache/v4
go get github.com/eko/gocache/store/freecache/v4
go get github.com/eko/gocache/store/go_cache/v4
go get github.com/eko/gocache/store/hazelcast/v4
go get github.com/eko/gocache/store/memcache/v4
go get github.com/eko/gocache/store/pegasus/v4
go get github.com/eko/gocache/store/redis/v4
go get github.com/eko/gocache/store/rediscluster/v4
go get github.com/eko/gocache/store/rueidis/v4
go get github.com/eko/gocache/store/ristretto/v4

Then, simply use the following import statements:

import (
	"github.com/eko/gocache/lib/v4/cache"
	"github.com/eko/gocache/store/redis/v4"
)

If you run into any errors, please be sure to run go mod tidy to clean your go.mod file.

Available cache features in detail

A simple cache

Here is a simple cache instantiation with Redis but you can also look at other available stores:

Memcache

memcacheStore := memcache_store.NewMemcache(
	memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212"),
	store.WithExpiration(10*time.Second),
)

cacheManager := cache.New[[]byte](memcacheStore)
err := cacheManager.Set(ctx, "my-key", []byte("my-value"),
	store.WithExpiration(15*time.Second), // Override default value of 10 seconds defined in the store
)
if err != nil {
    panic(err)
}

value := cacheManager.Get(ctx, "my-key")

cacheManager.Delete(ctx, "my-key")

cacheManager.Clear(ctx) // Clears the entire cache, in case you want to flush all cache

Memory (using Bigcache)

bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
bigcacheStore := bigcache_store.NewBigcache(bigcacheClient)

cacheManager := cache.New[[]byte](bigcacheStore)
err := cacheManager.Set(ctx, "my-key", []byte("my-value"))
if err != nil {
    panic(err)
}

value := cacheManager.Get(ctx, "my-key")

Memory (using Ristretto)

import (
	"github.com/dgraph-io/ristretto"
	"github.com/eko/gocache/lib/v4/cache"
	"github.com/eko/gocache/lib/v4/store"
	ristretto_store "github.com/eko/gocache/store/ristretto/v4"
)
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
	NumCounters: 1000,
	MaxCost: 100,
	BufferItems: 64,
})
if err != nil {
    panic(err)
}
ristrettoStore := ristretto_store.NewRistretto(ristrettoCache)

cacheManager := cache.New[string](ristrettoStore)
err := cacheManager.Set(ctx, "my-key", "my-value", store.WithCost(2))
if err != nil {
    panic(err)
}

value := cacheManager.Get(ctx, "my-key")

cacheManager.Delete(ctx, "my-key")

Memory (using Go-cache)

gocacheClient := gocache.New(5*time.Minute, 10*time.Minute)
gocacheStore := gocache_store.NewGoCache(gocacheClient)

cacheManager := cache.New[[]byte](gocacheStore)
err := cacheManager.Set(ctx, "my-key", []byte("my-value"))
if err != nil {
	panic(err)
}

value, err := cacheManager.Get(ctx, "my-key")
if err != nil {
	panic(err)
}
fmt.Printf("%s", value)

Redis

redisStore := redis_store.NewRedis(redis.NewClient(&redis.Options{
	Addr: "127.0.0.1:6379",
}))

cacheManager := cache.New[string](redisStore)
err := cacheManager.Set(ctx, "my-key", "my-value", store.WithExpiration(15*time.Second))
if err != nil {
    panic(err)
}

value, err := cacheManager.Get(ctx, "my-key")
switch err {
	case nil:
		fmt.Printf("Get the key '%s' from the redis cache. Result: %s", "my-key", value)
	case redis.Nil:
		fmt.Printf("Failed to find the key '%s' from the redis cache.", "my-key")
	default:
	    fmt.Printf("Failed to get the value from the redis cache with key '%s': %v", "my-key", err)
}
client, err := rueidis.NewClient(rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}})
if err != nil {
    panic(err)
}

cacheManager := cache.New[string](rueidis_store.NewRueidis(
    client,
    store.WithExpiration(15*time.Second),
    store.WithClientSideCaching(15*time.Second)),
)

if err = cacheManager.Set(ctx, "my-key", "my-value"); err != nil {
    panic(err)
}

value, err := cacheManager.Get(ctx, "my-key")
if err != nil {
    log.Fatalf("Failed to get the value from the redis cache with key '%s': %v", "my-key", err)
}
log.Printf("Get the key '%s' from the redis cache. Result: %s", "my-key", value)

Freecache

freecacheStore := freecache_store.NewFreecache(freecache.NewCache(1000), store.WithExpiration(10 * time.Second))

cacheManager := cache.New[[]byte](freecacheStore)
err := cacheManager.Set(ctx, "by-key", []byte("my-value"), opts)
if err != nil {
    panic(err)
}

value := cacheManager.Get(ctx, "my-key")

Pegasus

pegasusStore, err := pegasus_store.NewPegasus(&store.OptionsPegasus{
    MetaServers: []string{"127.0.0.1:34601", "127.0.0.1:34602", "127.0.0.1:34603"},
})

if err != nil {
    fmt.Println(err)
    return
}

cacheManager := cache.New[string](pegasusStore)
err = cacheManager.Set(ctx, "my-key", "my-value", store.WithExpiration(10 * time.Second))
if err != nil {
    panic(err)
}

value, _ := cacheManager.Get(ctx, "my-key")

Hazelcast

hzClient, err := hazelcast.StartNewClient(ctx)
if err != nil {
    log.Fatalf("Failed to start client: %v", err)
}

hzMapName:= "gocache"

hazelcastStore := hazelcast_store.NewHazelcast(hzClient, hzMapName)

cacheManager := cache.New[string](hazelcastStore)
err := cacheManager.Set(ctx, "my-key", "my-value", store.WithExpiration(15*time.Second))
if err != nil {
    panic(err)
}

value, err := cacheManager.Get(ctx, "my-key")
if err != nil {
    panic(err)
}
fmt.Printf("Get the key '%s' from the hazelcast cache. Result: %s", "my-key", value)

A chained cache

Here, we will chain caches in the following order: first in memory with Ristretto store, then in Redis (as a fallback):

// Initialize Ristretto cache and Redis client
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{NumCounters: 1000, MaxCost: 100, BufferItems: 64})
if err != nil {
    panic(err)
}

redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})

// Initialize stores
ristrettoStore := ristretto_store.NewRistretto(ristrettoCache)
redisStore := redis_store.NewRedis(redisClient, store.WithExpiration(5*time.Second))

// Initialize chained cache
cacheManager := cache.NewChain[any](
    cache.New[any](ristrettoStore),
    cache.New[any](redisStore),
)

// ... Then, do what you want with your cache

Chain cache also put data back in previous caches when it's found so in this case, if ristretto doesn't have the data in its cache but redis have, data will also get setted back into ristretto (memory) cache.

A loadable cache

This cache will provide a load function that acts as a callable function and will set your data back in your cache in case they are not available:

type Book struct {
	ID string
	Name string
}

// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := redis_store.NewRedis(redisClient)

// Initialize a load function that loads your data from a custom source
loadFunction := func(ctx context.Context, key any) (*Book, error) {
    // ... retrieve value from available source
    return &Book{ID: 1, Name: "My test amazing book"}, nil
}

// Initialize loadable cache
cacheManager := cache.NewLoadable[*Book](
	loadFunction,
	cache.New[*Book](redisStore),
)

// ... Then, you can get your data and your function will automatically put them in cache(s)

Of course, you can also pass a Chain cache into the Loadable one so if your data is not available in all caches, it will bring it back in all caches.

A metric cache to retrieve cache statistics

This cache will record metrics depending on the metric provider you pass to it. Here we give a Prometheus provider:

// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := redis_store.NewRedis(redisClient)

// Initializes Prometheus metrics service
promMetrics := metrics.NewPrometheus("my-test-app")

// Initialize metric cache
cacheManager := cache.NewMetric[any](
	promMetrics,
	cache.New[any](redisStore),
)

// ... Then, you can get your data and metrics will be observed by Prometheus

A marshaler wrapper

Some caches like Redis stores and returns the value as a string so you have to marshal/unmarshal your structs if you want to cache an object. That's why we bring a marshaler service that wraps your cache and make the work for you:

// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := redis_store.NewRedis(redisClient)

// Initialize chained cache
cacheManager := cache.NewMetric[any](
	promMetrics,
	cache.New[any](redisStore),
)

// Initializes marshaler
marshal := marshaler.New(cacheManager)

key := BookQuery{Slug: "my-test-amazing-book"}
value := Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"}

err = marshal.Set(ctx, key, value)
if err != nil {
    panic(err)
}

returnedValue, err := marshal.Get(ctx, key, new(Book))
if err != nil {
    panic(err)
}

// Then, do what you want with the  value

marshal.Delete(ctx, "my-key")

The only thing you have to do is to specify the struct in which you want your value to be un-marshalled as a second argument when calling the .Get() method.

Cache invalidation using tags

You can attach some tags to items you create so you can easily invalidate some of them later.

Tags are stored using the same storage you choose for your cache.

Here is an example on how to use it:

// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := redis_store.NewRedis(redisClient)

// Initialize chained cache
cacheManager := cache.NewMetric[*Book](
	promMetrics,
	cache.New[*Book](redisStore),
)

// Initializes marshaler
marshal := marshaler.New(cacheManager)

key := BookQuery{Slug: "my-test-amazing-book"}
value := &Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"}

// Set an item in the cache and attach it a "book" tag
err = marshal.Set(ctx, key, value, store.WithTags([]string{"book"}))
if err != nil {
    panic(err)
}

// Remove all items that have the "book" tag
err := marshal.Invalidate(ctx, store.WithInvalidateTags([]string{"book"}))
if err != nil {
    panic(err)
}

returnedValue, err := marshal.Get(ctx, key, new(Book))
if err != nil {
	// Should be triggered because item has been deleted so it cannot be found.
    panic(err)
}

Mix this with expiration times on your caches to have a fine-tuned control on how your data are cached.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/eko/gocache/lib/v4/cache"
	"github.com/eko/gocache/lib/v4/store"
	"github.com/redis/go-redis/v9"
)

func main() {
	redisStore := redis_store.NewRedis(redis.NewClient(&redis.Options{
		Addr: "127.0.0.1:6379",
	}), nil)

	cacheManager := cache.New[string](redisStore)
	err := cacheManager.Set(ctx, "my-key", "my-value", store.WithExpiration(15*time.Second))
	if err != nil {
		panic(err)
	}

	key := "my-key"
	value, err := cacheManager.Get(ctx, key)
	if err != nil {
		log.Fatalf("unable to get cache key '%s' from the cache: %v", key, err)
	}

	fmt.Printf("%#+v\n", value)
}

Write your own custom cache

Cache respect the following interface so you can write your own (proprietary?) cache logic if needed by implementing the following interface:

type CacheInterface[T any] interface {
	Get(ctx context.Context, key any) (T, error)
	Set(ctx context.Context, key any, object T, options ...store.Option) error
	Delete(ctx context.Context, key any) error
	Invalidate(ctx context.Context, options ...store.InvalidateOption) error
	Clear(ctx context.Context) error
	GetType() string
}

Or, in case you use a setter cache, also implement the GetCodec() method:

type SetterCacheInterface[T any] interface {
	CacheInterface[T]
	GetWithTTL(ctx context.Context, key any) (T, time.Duration, error)

	GetCodec() codec.CodecInterface
}

As all caches available in this library implement CacheInterface, you will be able to mix your own caches with your own.

Write your own custom store

You also have the ability to write your own custom store by implementing the following interface:

type StoreInterface interface {
	Get(ctx context.Context, key any) (any, error)
	GetWithTTL(ctx context.Context, key any) (any, time.Duration, error)
	Set(ctx context.Context, key any, value any, options ...Option) error
	Delete(ctx context.Context, key any) error
	Invalidate(ctx context.Context, options ...InvalidateOption) error
	Clear(ctx context.Context) error
	GetType() string
}

Of course, I suggest you to have a look at current caches or stores to implement your own.

Custom cache key generator

You can implement the following interface in order to generate a custom cache key:

type CacheKeyGenerator interface {
	GetCacheKey() string
}

Benchmarks

Benchmarks

Run tests

To generate mocks using mockgen library, run:

$ make mocks

Test suite can be run with:

$ make test # run unit test

Community

Please feel free to contribute on this library and do not hesitate to open an issue if you want to discuss about a feature.

gocache's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gocache's Issues

support buntdb

The tidwall/buntdb is file db, also can use it in cache

it's still exists when restarted

cache type error with using generics

Hello everyone! Can you help me, i don't understand.

4.1.2 version.

my import:

	"github.com/eko/gocache/lib/v4/cache"
	"github.com/eko/gocache/lib/v4/store"
	redis_store "github.com/eko/gocache/store/redis/v4"

I have this code:

Снимок экрана 2023-01-18 в 07 48 27

How to fix it?

v4 issues - can't import

I'm trying to import the v4 but I can't, this error show up:

go: finding module for package github.com/eko/gocache/v4/lib
reallifeglobal.com/common/cache imports
	github.com/eko/gocache/v4/lib: module github.com/eko/gocache@latest found (v1.2.0), but does not contain package github.com/eko/gocache/v4/lib

To reproduce:

  • create a new project
  • try to follow installation steps

How to create a key that never expires

Please describe the a concise description and fill out the details below. It will help others efficiently understand your request and get to an answer instead of repeated back and forth. Providing a minimal, complete and verifiable example will further increase your chances that someone can help.

Steps for Reproduction

  1. Set up the following "xxxx" value in the config.yaml configuration file
  2. Launch binary with the following arguments
  3. Step Two
  4. Step Three

Expected behavior:
Create a key that does not expire
Actual behavior:
The TTL type only supports time.Duration and cannot pass in negative values
Platforms:

Include browser, operating system and respective versions

Versions:
3.1.1
Which versions are you running?

Null pointer in Loading cache when cache.Set after loading function

Loadable cache panics after loadFunc has been called and cache.Set called to set the loaded value.
See the code below to reproduce.
We use it in https://github.com/netbirdio/netbird and this issue forced us to downgrade to v2.3.1.

The reason is nil passed to Set function:

c.Set(context.Background(), item.key, item.value, nil)

The thing is that that nil is treated as a valid Option here:

opt(o)

Steps for Reproduction

add the following test to cache/loadable_test.go.
It will throw a null pointer exception.

func TestLoadableGocache(t *testing.T) {
	gocacheClient := gocache.New(5*time.Second, 5*time.Second)
	gocacheStore := store.NewGoCache(gocacheClient, store.WithExpiration(5*time.Second))

	cacheValue := "my-value"
	loadFunc := func(ctx context.Context, accountID any) (string, error) {
		return cacheValue, nil
	}

	cache := NewLoadable[string](loadFunc, New[string](gocacheStore))

	// When
	value, err := cache.Get(context.Background(), "my-key")

	// Wait for data to be processed
	for len(cache.setChannel) > 0 {
		time.Sleep(1 * time.Millisecond)
	}

	// Then
	assert.Nil(t, err)
	assert.Equal(t, cacheValue, value)
}

Expected behavior:
No panic and the loaded value is set.

Actual behavior:
Panic

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6fc77e]

Platforms:

Any

Versions:

v3.0.0

Issue Running BigCache store and ristretto

Hi,
i discovered your awesome library today and im trying to run the examples on the readme.
i get the below errors when i try to run them
1)
`
bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
bigcacheStore := store.NewBigcache(bigcacheClient, nil)
cacheManager := cache.New(bigcacheStore)
err := cacheManager.Set("my-key", "my-value", nil)
if err != nil {
panic(err)
}

value, err := cacheManager.Get("my-key")
log.Println(value, err)

`

im getting an error marked at err := cacheManager.Set("my-key", "my-value", nil)

panic: interface conversion: interface {} is string, not []uint8

goroutine 1 [running]:
github.com/eko/gocache/store.(*BigcacheStore).Set(0xc00019c660, 0x773a20, 0xc000032000, 0x773a20, 0x86e010, 0x0, 0xc00000e201, 0xc000032000)
D:/Dev/GOPATH/pkg/mod/github.com/eko/[email protected]/store/bigcache.go:62 +0x19d
github.com/eko/gocache/codec.(*Codec).Set(0xc00019c680, 0x773a20, 0xc000032000, 0x773a20, 0x86e010, 0x0, 0x875001, 0xc000012140)
D:/Dev/GOPATH/pkg/mod/github.com/eko/[email protected]/codec/codec.go:51 +0x70
github.com/eko/gocache/cache.(*Cache).Set(0xc00016bed8, 0x773a20, 0x86e000, 0x773a20, 0x86e010, 0x0, 0x875860, 0xb18120)
D:/Dev/GOPATH/pkg/mod/github.com/eko/[email protected]/cache/cache.go:39 +0xa2
main.main()
D:/Dev/GOPATH/src/gokit/examples/cache/main.go:17 +0x28e
exit status 2

Alternatively when im using the example in the benchmark directly on the store, it works
`
client, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
store := store.NewBigcache(client, nil)

	key := "test"
	value := []byte("value")

	store.Set(key, value, nil)

	val, err := store.Get(key)
	ba := []byte{}
	for _, b := range val.([]uint8) {
		ba = append(ba, byte(b))
	}
	log.Println(string(ba), err)

`

`
import (
"log"

"github.com/dgraph-io/ristretto"
"github.com/eko/gocache/cache"
"github.com/eko/gocache/store"

)

func main() {
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1000,
MaxCost: 100,
BufferItems: 64,
})
if err != nil {
panic(err)
}
ristrettoStore := store.NewRistretto(ristrettoCache, nil)

cacheManager := cache.New(ristrettoStore)
err = cacheManager.Set("my-key", "my-value", &cache.Options{Cost: 2})
if err != nil {
	panic(err)
}

value, err := cacheManager.Get("my-key")
log.Println(value, err)

cacheManager.Delete("my-key")

}
`
err:
.\main.go:23:48: undefined: cache.Options

i noticed options struct is in store package which is imported.

Could you please help

Get method doesn't work correctly for bigcache with string type.

Description
Hi,

Thank you for making the awesome library first.

The type casting code is wrong for bigcache with string type. Because bigcache always returns []byte type(refer here) even though we can store data as string type.

I think it is better if type casting is failed, it should return not only zero value but also error.

Steps for Reproduction

  1. Set up Bigcache with string type
func fn(config Config) {
  bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(config.ExpiredTime))
  bigcacheStore := store.NewBigcache(bigcacheClient)

  memoryCache = cache.New[string](bigcacheStore)
}
  1. Set data
val := "some string data"
memoryCache.Set(ctx, key, val) // There is no error because it will be convert to byte array
  1. Get data
val, _ := memoryCache.Get(ctx, key) // There is no error but data is blank string.

Expected behavior:
It should be return stored string data.

Actual behavior:
It returned blank string.

Platforms:
Ubuntu

Versions:
v3.1.1

go mod unable to get latest version v2.2.0

go mod tidy

go: finding module for package github.com/eko/gocache
github.com/eko/gocache: module github.com/eko/gocache@latest found (v1.2.0), but does not contain package github.com/eko/gocache

import (
	"github.com/eko/gocache"
)

func main() {
   //some code
}

Common not found error or different `get()` signature

What are your thoughts on a common gocache.NotFound error that wraps the store not found error classes if there is one. Right now its a bit of a leaky abstraction when using multiple cache types as they have different not found errors ie bigcache.ErrEntryNotFound vs redis.Nil. Alternatively we could add a found bool get() signature. entry, isFound, nil := cache.Get(...). Willing to submit a pr.

Data Races when running tests with race detector

There are several data races when running the tests with the race detector enabled.

Some may simply be due to mocking, however I think a couple may be symptoms of actual bugs

Steps for Reproduction

  1. go test -v -race ./...

Expected behavior:

Tests pass with no data races detected

Actual behavior:

go test -v -race ./...

=== RUN   TestNew
--- PASS: TestNew (0.00s)
=== RUN   TestCacheSet
--- PASS: TestCacheSet (0.00s)
=== RUN   TestCacheSetWhenErrorOccurs
--- PASS: TestCacheSetWhenErrorOccurs (0.00s)
=== RUN   TestCacheGet
--- PASS: TestCacheGet (0.00s)
=== RUN   TestCacheGetWhenNotFound
--- PASS: TestCacheGetWhenNotFound (0.00s)
=== RUN   TestCacheGetCodec
--- PASS: TestCacheGetCodec (0.00s)
=== RUN   TestCacheGetType
--- PASS: TestCacheGetType (0.00s)
=== RUN   TestCacheDelete
--- PASS: TestCacheDelete (0.00s)
=== RUN   TestCacheInvalidate
--- PASS: TestCacheInvalidate (0.00s)
=== RUN   TestCacheInvalidateWhenError
--- PASS: TestCacheInvalidateWhenError (0.00s)
=== RUN   TestCacheClear
--- PASS: TestCacheClear (0.00s)
=== RUN   TestCacheClearWhenError
--- PASS: TestCacheClearWhenError (0.00s)
=== RUN   TestCacheDeleteWhenError
--- PASS: TestCacheDeleteWhenError (0.00s)
=== RUN   TestNewChain
--- PASS: TestNewChain (0.00s)
=== RUN   TestChainGetCaches
--- PASS: TestChainGetCaches (0.00s)
=== RUN   TestChainGetWhenAvailableInFirstCache
--- PASS: TestChainGetWhenAvailableInFirstCache (0.00s)
=== RUN   TestChainGetWhenAvailableInSecondCache
2019/12/02 15:17:35 Unable to retrieve item from cache with store 'store1': Unable to find in cache 1
--- PASS: TestChainGetWhenAvailableInSecondCache (0.00s)
=== RUN   TestChainGetWhenNotAvailableInAnyCache
2019/12/02 15:17:35 Unable to retrieve item from cache with store 'store1': Unable to find in cache 1
2019/12/02 15:17:35 Unable to retrieve item from cache with store 'store2': Unable to find in cache 2
--- PASS: TestChainGetWhenNotAvailableInAnyCache (0.00s)
=== RUN   TestChainSet
--- PASS: TestChainSet (0.00s)
=== RUN   TestChainSetWhenErrorOnSetting
--- PASS: TestChainSetWhenErrorOnSetting (0.00s)
=== RUN   TestChainDelete
--- PASS: TestChainDelete (0.00s)
=== RUN   TestChainDeleteWhenError
--- PASS: TestChainDeleteWhenError (0.00s)
=== RUN   TestChainInvalidate
--- PASS: TestChainInvalidate (0.00s)
=== RUN   TestChainInvalidateWhenError
--- PASS: TestChainInvalidateWhenError (0.00s)
=== RUN   TestChainClear
--- PASS: TestChainClear (0.00s)
=== RUN   TestChainClearWhenError
--- PASS: TestChainClearWhenError (0.00s)
=== RUN   TestChainGetType
--- PASS: TestChainGetType (0.00s)
=== RUN   TestCacheChecksum
--- PASS: TestCacheChecksum (0.00s)
=== RUN   TestNewLoadable
--- PASS: TestNewLoadable (0.00s)
=== RUN   TestLoadableGetWhenAlreadyInCache
--- PASS: TestLoadableGetWhenAlreadyInCache (0.00s)
=== RUN   TestLoadableGetWhenNotAvailableInLoadFunc
2019/12/02 15:17:35 An error has occurred while trying to load item from load function: An error has occurred while loading data from custom source
--- PASS: TestLoadableGetWhenNotAvailableInLoadFunc (0.00s)
=== RUN   TestLoadableGetWhenAvailableInLoadFunc
--- PASS: TestLoadableGetWhenAvailableInLoadFunc (0.00s)
=== RUN   TestLoadableDelete
--- PASS: TestLoadableDelete (0.00s)
=== RUN   TestLoadableDeleteWhenError
--- PASS: TestLoadableDeleteWhenError (0.00s)
=== RUN   TestLoadableInvalidate
--- PASS: TestLoadableInvalidate (0.00s)
=== RUN   TestLoadableInvalidateWhenError
--- PASS: TestLoadableInvalidateWhenError (0.00s)
=== RUN   TestLoadableClear
--- PASS: TestLoadableClear (0.00s)
=== RUN   TestLoadableClearWhenError
--- PASS: TestLoadableClearWhenError (0.00s)
=== RUN   TestLoadableGetType
--- PASS: TestLoadableGetType (0.00s)
=== RUN   TestNewMetric
--- PASS: TestNewMetric (0.00s)
=== RUN   TestMetricGet
--- PASS: TestMetricGet (0.00s)
=== RUN   TestMetricGetWhenChainCache
--- PASS: TestMetricGetWhenChainCache (0.00s)
=== RUN   TestMetricSet
==================
WARNING: DATA RACE
Read at 0x00c00014a9c8 by goroutine 58:
  reflect.typedmemmove()
      /usr/local/Cellar/go/1.13.4/libexec/src/runtime/mbarrier.go:177 +0x0
  reflect.packEface()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:119 +0x103
  reflect.valueInterface()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1033 +0x16f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1003 +0x38f7
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:880 +0x25da
  fmt.(*pp).printArg()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:716 +0x2ee
  fmt.(*pp).doPrintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:1126 +0x912
  fmt.Sprintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:219 +0x73
  github.com/stretchr/testify/mock.Arguments.Diff()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:687 +0xf1a
  github.com/stretchr/testify/mock.(*Mock).findExpectedCall()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:269 +0x16a
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:348 +0xb3
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/metrics.(*MetricsInterface).RecordFromCodec()
      /Users/markphelps/workspace/gocache/test/mocks/metrics/metrics_interface.go:21 +0xaa

Previous write at 0x00c00014a9c8 by goroutine 57:
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:385 +0x7b6
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/codec.(*CodecInterface).GetStore()
      /Users/markphelps/workspace/gocache/test/mocks/codec/codec_interface.go:83 +0x63
  github.com/eko/gocache/cache.(*ChainCache).setUntil()
      /Users/markphelps/workspace/gocache/cache/chain.go:90 +0x13b

Goroutine 58 (running) created at:
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:65 +0x1bd
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:61 +0xff
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:31 +0xcb
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199

Goroutine 57 (finished) created at:
  github.com/eko/gocache/cache.(*ChainCache).Get()
      /Users/markphelps/workspace/gocache/cache/chain.go:37 +0x383
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:29 +0x75
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199
==================
==================
WARNING: DATA RACE
Read at 0x00c00014a9f8 by goroutine 58:
  reflect.typedmemmove()
      /usr/local/Cellar/go/1.13.4/libexec/src/runtime/mbarrier.go:177 +0x0
--- FAIL: TestMetricSet (0.00s)
  reflect.packEface()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:119 +0x103
    testing.go:853: race detected during execution of test
  reflect.valueInterface()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1033 +0x16f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1003 +0x38f7
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:880 +0x25da
  fmt.(*pp).printArg()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:716 +0x2ee
  fmt.(*pp).doPrintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:1126 +0x912
  fmt.Sprintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:219 +0x73
  github.com/stretchr/testify/mock.Arguments.Diff()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:687 +0xf1a
  github.com/stretchr/testify/mock.(*Mock).findExpectedCall()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:269 +0x16a
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:348 +0xb3
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/metrics.(*MetricsInterface).RecordFromCodec()
      /Users/markphelps/workspace/gocache/test/mocks/metrics/metrics_interface.go:21 +0xaa

Previous write at 0x00c00014a9f8 by goroutine 57:
  sync/atomic.AddInt32()
      /usr/local/Cellar/go/1.13.4/libexec/src/runtime/race_amd64.s:269 +0xb
  sync.(*Mutex).Unlock()
      /usr/local/Cellar/go/1.13.4/libexec/src/sync/mutex.go:186 +0x51
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:405 +0x921
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/codec.(*CodecInterface).GetStore()
      /Users/markphelps/workspace/gocache/test/mocks/codec/codec_interface.go:83 +0x63
  github.com/eko/gocache/cache.(*ChainCache).setUntil()
      /Users/markphelps/workspace/gocache/cache/chain.go:90 +0x13b

Goroutine 58 (running) created at:
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:65 +0x1bd
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:61 +0xff
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:31 +0xcb
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199
=== RUN   TestMetricDelete

Goroutine 57 (finished) created at:
  github.com/eko/gocache/cache.(*ChainCache).Get()
      /Users/markphelps/workspace/gocache/cache/chain.go:37 +0x383
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:29 +0x75
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199
==================
==================
WARNING: DATA RACE
Read at 0x00c0001f6870 by goroutine 58:
  reflect.typedmemmove()
      /usr/local/Cellar/go/1.13.4/libexec/src/runtime/mbarrier.go:177 +0x0
  reflect.packEface()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:119 +0x103
  reflect.valueInterface()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1033 +0x16f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1003 +0x38f7
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:869 +0xec7
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:810 +0x283f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:810 +0x283f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:880 +0x25da
  fmt.(*pp).printArg()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:716 +0x2ee
  fmt.(*pp).doPrintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:1126 +0x912
  fmt.Sprintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:219 +0x73
  github.com/stretchr/testify/mock.Arguments.Diff()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:687 +0xf1a
  github.com/stretchr/testify/mock.(*Mock).findExpectedCall()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:269 +0x16a
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:348 +0xb3
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/metrics.(*MetricsInterface).RecordFromCodec()
      /Users/markphelps/workspace/gocache/test/mocks/metrics/metrics_interface.go:21 +0xaa

Previous write at 0x00c0001f6870 by goroutine 57:
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:385 +0x775
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/codec.(*CodecInterface).GetStore()
      /Users/markphelps/workspace/gocache/test/mocks/codec/codec_interface.go:83 +0x63
  github.com/eko/gocache/cache.(*ChainCache).setUntil()
      /Users/markphelps/workspace/gocache/cache/chain.go:90 +0x13b

Goroutine 58 (running) created at:
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:65 +0x1bd
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:61 +0xff
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:31 +0xcb
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199

Goroutine 57 (finished) created at:
  github.com/eko/gocache/cache.(*ChainCache).Get()
      /Users/markphelps/workspace/gocache/cache/chain.go:37 +0x383
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:29 +0x75
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199
==================
--- FAIL: TestMetricDelete (0.00s)
    testing.go:853: race detected during execution of test
=== RUN   TestMetricDeleteWhenError
--- PASS: TestMetricDeleteWhenError (0.00s)
=== RUN   TestMetricInvalidate
==================
WARNING: DATA RACE
Read at 0x00c0002dcba0 by goroutine 58:
  reflect.Value.String()
      /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:1845 +0x5d
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:761 +0x3156
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:869 +0xec7
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:810 +0x283f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:869 +0xec7
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:810 +0x283f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:810 +0x283f
  fmt.(*pp).printValue()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:880 +0x25da
  fmt.(*pp).printArg()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:716 +0x2ee
  fmt.(*pp).doPrintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:1126 +0x912
  fmt.Sprintf()
      /usr/local/Cellar/go/1.13.4/libexec/src/fmt/print.go:219 +0x73
  github.com/stretchr/testify/mock.Arguments.Diff()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:687 +0xf1a
  github.com/stretchr/testify/mock.(*Mock).findExpectedCall()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:269 +0x16a
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:348 +0xb3
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/metrics.(*MetricsInterface).RecordFromCodec()
      /Users/markphelps/workspace/gocache/test/mocks/metrics/metrics_interface.go:21 +0xaa

Previous write at 0x00c0002dcba0 by goroutine 57:
  github.com/stretchr/testify/assert.CallerInfo()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/assert/assertions.go:146 +0x3c3
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:385 +0x57e
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/markphelps/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:338 +0x1e4
  github.com/eko/gocache/test/mocks/codec.(*CodecInterface).GetStore()
      /Users/markphelps/workspace/gocache/test/mocks/codec/codec_interface.go:83 +0x63
  github.com/eko/gocache/cache.(*ChainCache).setUntil()
      /Users/markphelps/workspace/gocache/cache/chain.go:90 +0x13b

Goroutine 58 (running) created at:
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:65 +0x1bd
  github.com/eko/gocache/cache.(*MetricCache).updateMetrics()
      /Users/markphelps/workspace/gocache/cache/metric.go:61 +0xff
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:31 +0xcb
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199

Goroutine 57 (finished) created at:
  github.com/eko/gocache/cache.(*ChainCache).Get()
      /Users/markphelps/workspace/gocache/cache/chain.go:37 +0x383
  github.com/eko/gocache/cache.(*MetricCache).Get()
      /Users/markphelps/workspace/gocache/cache/metric.go:29 +0x75
  github.com/eko/gocache/cache.TestMetricGetWhenChainCache()
      /Users/markphelps/workspace/gocache/cache/metric_test.go:83 +0x714
  testing.tRunner()
      /usr/local/Cellar/go/1.13.4/libexec/src/testing/testing.go:909 +0x199
==================
--- FAIL: TestMetricInvalidate (0.00s)
    testing.go:853: race detected during execution of test
=== RUN   TestMetricInvalidateWhenError
--- PASS: TestMetricInvalidateWhenError (0.00s)
=== RUN   TestMetricClear
--- PASS: TestMetricClear (0.00s)
=== RUN   TestMetricClearWhenError
--- PASS: TestMetricClearWhenError (0.00s)
=== RUN   TestMetricGetType
--- PASS: TestMetricGetType (0.00s)
FAIL
FAIL	github.com/eko/gocache/cache	0.095s
=== RUN   TestNew
--- PASS: TestNew (0.00s)
=== RUN   TestGetWhenHit
--- PASS: TestGetWhenHit (0.00s)
=== RUN   TestGetWhenMiss
--- PASS: TestGetWhenMiss (0.00s)
=== RUN   TestSetWhenSuccess
--- PASS: TestSetWhenSuccess (0.00s)
=== RUN   TestSetWhenError
--- PASS: TestSetWhenError (0.00s)
=== RUN   TestDeleteWhenSuccess
--- PASS: TestDeleteWhenSuccess (0.00s)
=== RUN   TestInvalidateWhenSuccess
--- PASS: TestInvalidateWhenSuccess (0.00s)
=== RUN   TestInvalidateWhenError
--- PASS: TestInvalidateWhenError (0.00s)
=== RUN   TestClearWhenSuccess
--- PASS: TestClearWhenSuccess (0.00s)
=== RUN   TestClearWhenError
--- PASS: TestClearWhenError (0.00s)
=== RUN   TestGetStore
--- PASS: TestGetStore (0.00s)
=== RUN   TestGetStats
--- PASS: TestGetStats (0.00s)
PASS
ok  	github.com/eko/gocache/codec	(cached)
=== RUN   TestNew
--- PASS: TestNew (0.00s)
=== RUN   TestGetWhenStoreReturnsSliceOfBytes
--- PASS: TestGetWhenStoreReturnsSliceOfBytes (0.00s)
=== RUN   TestGetWhenStoreReturnsString
--- PASS: TestGetWhenStoreReturnsString (0.00s)
=== RUN   TestGetWhenUnmarshalingError
--- PASS: TestGetWhenUnmarshalingError (0.00s)
=== RUN   TestGetWhenNotFoundInStore
--- PASS: TestGetWhenNotFoundInStore (0.00s)
=== RUN   TestSetWhenStruct
--- PASS: TestSetWhenStruct (0.00s)
=== RUN   TestSetWhenString
--- PASS: TestSetWhenString (0.00s)
=== RUN   TestSetWhenError
--- PASS: TestSetWhenError (0.00s)
=== RUN   TestDelete
--- PASS: TestDelete (0.00s)
=== RUN   TestDeleteWhenError
--- PASS: TestDeleteWhenError (0.00s)
=== RUN   TestInvalidate
--- PASS: TestInvalidate (0.00s)
=== RUN   TestInvalidatingWhenError
--- PASS: TestInvalidatingWhenError (0.00s)
=== RUN   TestClear
--- PASS: TestClear (0.00s)
=== RUN   TestClearWhenError
--- PASS: TestClearWhenError (0.00s)
PASS
ok  	github.com/eko/gocache/marshaler	(cached)
=== RUN   TestNewPrometheus
--- PASS: TestNewPrometheus (0.00s)
=== RUN   TestRecord
--- PASS: TestRecord (0.00s)
=== RUN   TestRecordFromCodec
--- PASS: TestRecordFromCodec (0.00s)
PASS
ok  	github.com/eko/gocache/metrics	(cached)
=== RUN   TestNewBigcache
--- PASS: TestNewBigcache (0.00s)
=== RUN   TestBigcacheGet
--- PASS: TestBigcacheGet (0.00s)
=== RUN   TestBigcacheGetWhenError
--- PASS: TestBigcacheGetWhenError (0.00s)
=== RUN   TestBigcacheSet
--- PASS: TestBigcacheSet (0.00s)
=== RUN   TestBigcacheSetWhenError
--- PASS: TestBigcacheSetWhenError (0.00s)
=== RUN   TestBigcacheSetWithTags
--- PASS: TestBigcacheSetWithTags (0.00s)
=== RUN   TestBigcacheSetWithTagsWhenAlreadyInserted
--- PASS: TestBigcacheSetWithTagsWhenAlreadyInserted (0.00s)
=== RUN   TestBigcacheDelete
--- PASS: TestBigcacheDelete (0.00s)
=== RUN   TestBigcacheDeleteWhenError
--- PASS: TestBigcacheDeleteWhenError (0.00s)
=== RUN   TestBigcacheInvalidate
--- PASS: TestBigcacheInvalidate (0.00s)
=== RUN   TestBigcacheInvalidateWhenError
--- PASS: TestBigcacheInvalidateWhenError (0.00s)
=== RUN   TestBigcacheClear
--- PASS: TestBigcacheClear (0.00s)
=== RUN   TestBigcacheClearWhenError
--- PASS: TestBigcacheClearWhenError (0.00s)
=== RUN   TestBigcacheGetType
--- PASS: TestBigcacheGetType (0.00s)
=== RUN   TestInvalidateOptionsTagsValue
--- PASS: TestInvalidateOptionsTagsValue (0.00s)
=== RUN   TestNewMemcache
--- PASS: TestNewMemcache (0.00s)
=== RUN   TestMemcacheGet
--- PASS: TestMemcacheGet (0.00s)
=== RUN   TestMemcacheGetWhenError
--- PASS: TestMemcacheGetWhenError (0.00s)
=== RUN   TestMemcacheSet
--- PASS: TestMemcacheSet (0.00s)
=== RUN   TestMemcacheSetWhenNoOptionsGiven
--- PASS: TestMemcacheSetWhenNoOptionsGiven (0.00s)
=== RUN   TestMemcacheSetWhenError
--- PASS: TestMemcacheSetWhenError (0.00s)
=== RUN   TestMemcacheSetWithTags
--- PASS: TestMemcacheSetWithTags (0.00s)
=== RUN   TestMemcacheSetWithTagsWhenAlreadyInserted
--- PASS: TestMemcacheSetWithTagsWhenAlreadyInserted (0.00s)
=== RUN   TestMemcacheDelete
--- PASS: TestMemcacheDelete (0.00s)
=== RUN   TestMemcacheDeleteWhenError
--- PASS: TestMemcacheDeleteWhenError (0.00s)
=== RUN   TestMemcacheInvalidate
--- PASS: TestMemcacheInvalidate (0.00s)
=== RUN   TestMemcacheInvalidateWhenError
--- PASS: TestMemcacheInvalidateWhenError (0.00s)
=== RUN   TestMemcacheClear
--- PASS: TestMemcacheClear (0.00s)
=== RUN   TestMemcacheClearWhenError
--- PASS: TestMemcacheClearWhenError (0.00s)
=== RUN   TestMemcacheGetType
--- PASS: TestMemcacheGetType (0.00s)
=== RUN   TestOptionsCostValue
--- PASS: TestOptionsCostValue (0.00s)
=== RUN   TestOptionsExpirationValue
--- PASS: TestOptionsExpirationValue (0.00s)
=== RUN   TestOptionsTagsValue
--- PASS: TestOptionsTagsValue (0.00s)
=== RUN   TestNewRedis
--- PASS: TestNewRedis (0.00s)
=== RUN   TestRedisGet
--- PASS: TestRedisGet (0.00s)
=== RUN   TestRedisSet
--- PASS: TestRedisSet (0.00s)
=== RUN   TestRedisSetWhenNoOptionsGiven
--- PASS: TestRedisSetWhenNoOptionsGiven (0.00s)
=== RUN   TestRedisSetWithTags
--- PASS: TestRedisSetWithTags (0.00s)
=== RUN   TestRedisDelete
--- PASS: TestRedisDelete (0.00s)
=== RUN   TestRedisInvalidate
--- PASS: TestRedisInvalidate (0.00s)
=== RUN   TestRedisClear
--- PASS: TestRedisClear (0.00s)
=== RUN   TestRedisGetType
--- PASS: TestRedisGetType (0.00s)
=== RUN   TestNewRistretto
--- PASS: TestNewRistretto (0.00s)
=== RUN   TestRistrettoGet
--- PASS: TestRistrettoGet (0.00s)
=== RUN   TestRistrettoGetWhenError
--- PASS: TestRistrettoGetWhenError (0.00s)
=== RUN   TestRistrettoSet
--- PASS: TestRistrettoSet (0.00s)
=== RUN   TestRistrettoSetWhenNoOptionsGiven
--- PASS: TestRistrettoSetWhenNoOptionsGiven (0.00s)
=== RUN   TestRistrettoSetWhenError
--- PASS: TestRistrettoSetWhenError (0.00s)
=== RUN   TestRistrettoSetWithTags
--- PASS: TestRistrettoSetWithTags (0.00s)
=== RUN   TestRistrettoSetWithTagsWhenAlreadyInserted
--- PASS: TestRistrettoSetWithTagsWhenAlreadyInserted (0.00s)
=== RUN   TestRistrettoDelete
--- PASS: TestRistrettoDelete (0.00s)
=== RUN   TestRistrettoInvalidate
--- PASS: TestRistrettoInvalidate (0.00s)
=== RUN   TestRistrettoInvalidateWhenError
--- PASS: TestRistrettoInvalidateWhenError (0.00s)
=== RUN   TestRistrettoClear
--- PASS: TestRistrettoClear (0.00s)
=== RUN   TestRistrettoGetType
--- PASS: TestRistrettoGetType (0.00s)
PASS
ok  	github.com/eko/gocache/store	(cached)
?   	github.com/eko/gocache/test/mocks/cache	[no test files]
?   	github.com/eko/gocache/test/mocks/codec	[no test files]
?   	github.com/eko/gocache/test/mocks/metrics	[no test files]
?   	github.com/eko/gocache/test/mocks/store	[no test files]
?   	github.com/eko/gocache/test/mocks/store/clients	[no test files]
FAIL

Platforms:

Mac OS 10.14.6
Go 1.13.4

Versions:

master @ 1b6c810

Get all keys

Hi! Is there some simple way to get all keys in store(by pattern or just all keys) and iterate by received items?

Error while building go application, issue with github.com/XiaoMi/pegasus-go-client

Unable to build go application on go version 1.18.2 on MacOS and Ubuntu
My guess is that problem is in this library: https://github.com/XiaoMi/pegasus-go-client which is used in github.com/eko/gocache/v3/store

image

Steps for Reproduction

  1. Use store.WithExpiration(5*time.Minute) in code
    Error is there even if using any other time value

Expected behavior:
App should build

Actual behavior:
Error while building application

Error Log:

divkix@Divs: …/AlitaGoRobot [ Beta][+3595 -17369][📦🗑️×22📝×41🛤️ ×2][🐳 desktop-linux][🐹 v1.18.2][🐏 11GiB/16GiB]
❯ go build .
# github.com/XiaoMi/pegasus-go-client/idl/cmd
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:41:15: not enough arguments in call to iprot.ReadStructBegin
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:46:35: not enough arguments in call to iprot.ReadFieldBegin
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:60:26: not enough arguments in call to iprot.Skip
        have (thrift.TType)
        want (context.Context, thrift.TType)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:70:26: not enough arguments in call to iprot.Skip
        have (thrift.TType)
        want (context.Context, thrift.TType)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:75:25: not enough arguments in call to iprot.Skip
        have (thrift.TType)
        want (context.Context, thrift.TType)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:79:13: not enough arguments in call to iprot.ReadFieldEnd
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:83:12: not enough arguments in call to iprot.ReadStructEnd
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:90:15: not enough arguments in call to iprot.ReadString
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:99:18: not enough arguments in call to iprot.ReadListBegin
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:107:16: not enough arguments in call to iprot.ReadString
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/cmd/cmd.go:107:16: too many errors
# github.com/XiaoMi/pegasus-go-client/idl/base
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/blob.go:18:15: not enough arguments in call to iprot.ReadBinary
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/blob.go:27:27: not enough arguments in call to oprot.WriteBinary
        have ([]byte)
        want (context.Context, []byte)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/error_code.go:94:18: not enough arguments in call to iprot.ReadString
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/error_code.go:99:27: not enough arguments in call to oprot.WriteString
        have (string)
        want (context.Context, string)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/gpid.go:18:12: not enough arguments in call to iprot.ReadI64
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/gpid.go:30:24: not enough arguments in call to oprot.WriteI64
        have (int64)
        want (context.Context, int64)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/rpc_address.go:26:18: not enough arguments in call to iprot.ReadI64
        have ()
        want (context.Context)
vendor/github.com/XiaoMi/pegasus-go-client/idl/base/rpc_address.go:35:24: not enough arguments in call to oprot.WriteI64
        have (int64)
        want (context.Context, int64)

Platforms:

  • MacOS Monterey Version 12.3.1 (21E258)
  • Ubuntu 20.04 LTS

Include browser, operating system and respective versions

Versions:

  • v3.0.0 (latest)

Which versions are you running?
Go Version: v1.18.2
gocache version: v3.0.0
OS: MacOS 12.3.1 and Ubuntu 20.04 LTS

bigcache GetWithTTL not supported

The bigcache implementation of GetWithTTL is just a hard coded 0 duration but with no indication in API it will not work as intended.

I suggest instead returning an error that lets the user know that the method is in fact not supported and it should not be relied upon.

Current behavior:

// GetWithTTL returns data stored from a given key and its corresponding TTL
func (s *BigcacheStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) {
	item, err := s.Get(ctx, key)
	return item, 0, err
}

Proposed change:

ErrNotImplemented = errors.New("Method not implemented for codec")

// GetWithTTL returns data stored from a given key and its corresponding TTL
func (s *BigcacheStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) {
	return nil, 0, store.ErrNotImplemented
}

Inconsistency on updating tags

In my case, I'm using the library along with Redis, and it seems I've found a bug related to how the tags are formed.

I was attempting to invalidate all messages with the same tag, but it only deleted the last one

Steps for Reproduction

  1. Set up a project using redis and with the marshaler cache;
  2. Save any kind of message, with a tag, lets say "myTag" with key "abc";
  3. On redis, there will be a key called "gocache_tag_myTag", with value "abc";
  4. Save another message, with the tag "myTag" and key "def";
  5. The key "gocache_tag_myTag" now has the value "def".

With this, if I attempt to invalidate messages with the tag "myTag", only the message with key "def" will be cleaned up.

Expected behavior:

  1. Set up a project using redis and with the marshaler cache;
  2. Save any kind of message, with a tag, lets say "myTag" with key "abc";
  3. On redis, there will be a key called "gocache_tag_myTag", with value "abc";
  4. Save another message, with the tag "myTag" and key "def";
  5. The key "gocache_tag_myTag" should have the value "abc,def"

Versions:

Which versions are you running?
v1.0.0

Performance improvement can be made for `Marshaler.Get`.

Performance improvement can be made for Marshaler.Get.

image

Here []bytes(v) can be replace with String2Bytes(v) cast funciton.

func String2Bytes(s string) []byte {
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	bh := reflect.SliceHeader{
		Data: sh.Data,
		Len:  sh.Len,
		Cap:  sh.Len,
	}
	return *(*[]byte)(unsafe.Pointer(&bh))
}

bad go module version

reference: golang/go#35732
when version go to v2, v3 and so on.
module path should be end with v2, v3

Steps for Reproduction

mkdir a
cd a
go mod init a
go get -v github.com/eko/[email protected]

Expected behavior:
get correct module

Actual behavior:
output

go get: github.com/eko/[email protected]: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

[Suggestion] Split built-in store implementations out into their own Go modules?

Having all the different built-in store implementations in the same one Go module means that their dependencies are also pulled in when using eko/gocahce.

If the built-in implementations were moved to their own sub-modules with their own go.mod files, it would enable a much smaller dependency tree narrowed to only what you're actively using.

This should avoid issues like #144 for everyone not actively using the Pegasus store, since they wouldn't be pulling in the pegasus client package as a dependency.

For me this has become a bit of an issue at the moment while trying to use gocache within a custom Traefik plugin. Traefik runs plugins via Yaegi, which means you cannot have anything in the dependency tree that uses the unsafe or syscall packages.

I'm fully aware a change like this would be a major breaking change, but I'd like to maybe get the discussion started.

Also, if it would help, I'd be happy to take a stab at the separation myself and submit a PR.

about the ttl of ristretto and bigcache

I found this problem when I set the validity period for the data.

i use store.Options to set the Expiration ,and the ttl I got is 0 when it based on ristretto or bigcache, but go-cache works normally.
here is my code:

ristretto

	ctx := context.Background()
	ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
		NumCounters: 1000,
		MaxCost:     100,
		BufferItems: 64,
	})
	if err != nil {
		panic(err)
	}
	ristrettoStore := store.NewRistretto(ristrettoCache, nil)

	cacheManager := cache.New(ristrettoStore)
	err1 := cacheManager.Set(ctx, "my-key", []byte("my-value"), &store.Options{
		Expiration: time.Hour,
	})
	if err1 != nil {
		panic(err1)
	}

	_, tt, _ := cacheManager.GetWithTTL(ctx, "my-key")
	fmt.Println(tt)

bigcache

	ctx := context.Background()
	bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
	bigcacheStore := store.NewBigcache(bigcacheClient, nil) // No options provided (as second argument)

	cacheManager := cache.New(bigcacheStore)
	err1 := cacheManager.Set(ctx, "my-key", []byte("my-value"), &store.Options{
		Expiration: time.Hour,
	})
	if err1 != nil {
		panic(err1)
	}

	_, tt, _ := cacheManager.GetWithTTL(ctx, "my-key")
	fmt.Println(tt)

store.Options does not expose its props, making StoreInterface challenging to implement

Steps for Reproduction

The docs suggest that the dev can use a custom store by implementing the StoreInterface. I'm working on a custom cache to use as part of the library's chain cache but one sticking point is the Set. Here is its definition:

Set(ctx context.Context, key any, value any, options ...Option) error

Option is a function that takes and configures an Options object, both from the library package. here is how the library passes it to my cache's setter: github

Within the library's cache implementations, the configured Options object is then read to discern, for example, the expiration date of the cache entry: example

The issue I face is that Options (declared here) makes all its properties private, so when I implement my own Set(), I have no way to read it to figure out when the cached entry should expire.

How is my custom StoreInterface implementation supposed to figure out the caching options used?

Expected behavior:

Custom struct implementing StoreInterface can be used in the chain cache

Actual behavior:

Unable to implement interface correctly because the Options object passed to my implementation does not expose the options in public properties

Platforms:

All

Versions:

v3.1.1

Should Rueidis store implementation return string?

Hi @eko, @rwrz,

Thank you for adding rueidis integration. It is my honor that rueidis can be integrated into this popular cache library.

Just wondering why doesn't Rueidis store implementation return a simple string instead of a rueidis.RedisResult? Are there some considerations I missed? I thought It would be much friendly if it returns a string to users like what go-redis integration does.

For example:

func (s *RueidisStore) Get(ctx context.Context, key any) (any, error) {
	cmd := s.client.B().Get().Key(key.(string)).Cache()
	str, err := s.client.DoCache(ctx, cmd, s.options.ClientSideCacheExpiration).ToString()
	if rueidis.IsRedisNil(err) {
		err = lib_store.NotFoundWithCause(err)
	}
	return str, err
}

Also, I noticed that there are some room for improvement:

  1. Each tag operation in setTags can be manually pipelined:
func (s *RueidisStore) setTags(ctx context.Context, key any, tags []string) {
	ttl := 720 * time.Hour
	for _, tag := range tags {
		tagKey := fmt.Sprintf(RueidisTagPattern, tag)
		s.client.DoMulti(ctx,
			s.client.B().Sadd().Key(tagKey).Member(key.(string)).Build(),
			s.client.B().Expire().Key(tagKey).Seconds(int64(ttl.Seconds())).Build(),
		)
	}
}
  1. I am going to release a new CacheTTL() method this weekend. It can retrieve the remaining client-side TTL of a cached message. Then, the GetWithTTL can be just like this:
func (s *RueidisStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) {
	cmd := s.client.B().Get().Key(key.(string)).Cache()
	res := s.client.DoCache(ctx, cmd).Cache(), s.options.ClientSideCacheExpiration)
	ttl := res.CacheTTL()
	str, err := res.ToString()
	if rueidis.IsRedisNil(err) {
		err = lib_store.NotFoundWithCause(err)
	}
	return str, time.Duration(ttl) * time.Second, err
}

What do you think? I am happy to open a PR for the above changes. 😊

Bug or misunderstanding with applyOptionsWithDefault

gocache/store/options.go

Lines 20 to 28 in 92c8ea6

func applyOptionsWithDefault(defaultOptions *Options, opts ...Option) *Options {
returnedOptions := applyOptions(opts...)
if returnedOptions.isEmpty() {
returnedOptions = defaultOptions
}
return returnedOptions
}

This is incorrect, right? If my default options are "5 minute expiration" and then I set w/ a tag, I end up with the tag only and not tag plus expiration.

It's impossible to create my own cache

The options struct is not public, so it's not really viable to implement my own cache since I can't use the v3 Option type. Option is just a func that takes private struct as input so I can't use it in my own package. I think the options struct should be public.

Any plans to make a release?

@eko A lot of things done in master during a year (like freecache implementation), but the last release was 8 Dec 2019
maybe its time to make a new one? :-)

make Marshaler and Loadable work together

I use gocache as a cachable mysql wrapper.

	bc, err := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute))
	if err != nil {
		panic(err)
	}
	return cache.NewLoadable(func(key interface{}) (interface{}, error) {
		var data DataType
		err := db.Get(&data, "select * from some_table where some_id = ?", key)
		return data, err
	}, cache.New(store.NewBigcache(bc, nil)))

but there is panic as below

goroutine 40 [running]:
github.com/eko/gocache/store.(*BigcacheStore).Set(0xc022c311d0, 0x1266220, 0xc0005b0400, 0x13bd900, 0xc0266114a0, 0x0, 0xc000084401, 0xc0005b0400)
	E:/go/pkg/mod/github.com/eko/[email protected]/store/bigcache.go:68 +0x1a5
github.com/eko/gocache/codec.(*Codec).Set(0xc022c311e8, 0x1266220, 0xc0005b0400, 0x13bd900, 0xc0266114a0, 0x0, 0x1, 0x101)
	E:/go/pkg/mod/github.com/eko/[email protected]/codec/codec.go:66 +0x70
github.com/eko/gocache/cache.(*Cache).Set(0xc0003b0030, 0x1266220, 0xc0002105a0, 0x13bd900, 0xc0266114a0, 0x0, 0x0, 0x0)
	E:/go/pkg/mod/github.com/eko/[email protected]/cache/cache.go:45 +0xb1
github.com/eko/gocache/cache.(*LoadableCache).Set(...)
	E:/go/pkg/mod/github.com/eko/[email protected]/cache/loadable.go:68
github.com/eko/gocache/cache.(*LoadableCache).setter(0xc000090020)
	E:/go/pkg/mod/github.com/eko/[email protected]/cache/loadable.go:41 +0x93
created by github.com/eko/gocache/cache.NewLoadable
	E:/go/pkg/mod/github.com/eko/[email protected]/cache/loadable.go:34 +0xbf

Loadable set value directly to bigcache which accept []byte only.

make Marshaler as CacheInterface may be ok.

	bc, err := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute))
	if err != nil {
		panic(err)
	}
	return cache.NewLoadable(func(key interface{}) (interface{}, error) {
		var data DataType
		err := db.Get(&data, "select * from some_table where some_id = ?", key)
		return data, err
	}, Marshaler.New(cache.New(store.NewBigcache(bc, nil))))

Here is some thoughts ~

for now, all cache implement CacheInterface, but some cache is using CacheInterface:
could I understand this as the Decorator pattern ?

// LoadableCache represents a cache that uses a function to load data
type LoadableCache struct {
	loadFunc   loadFunction
	cache      CacheInterface
	setChannel chan *loadableKeyValue
}

// Marshaler is the struct that marshal and unmarshal cache values
type Marshaler struct {
	cache cache.CacheInterface
}

AND StoreInterface is same to CacheInterface,

// CacheInterface represents the interface for all caches (aggregates, metric, memory, redis, ...)
type CacheInterface interface {
	Get(key interface{}) (interface{}, error)
	Set(key, object interface{}, options *store.Options) error
	Delete(key interface{}) error
	Invalidate(options store.InvalidateOptions) error
	Clear() error
	GetType() string
}

// StoreInterface is the interface for all available stores
type StoreInterface interface {
	Get(key interface{}) (interface{}, error)
	Set(key interface{}, value interface{}, options *Options) error
	Delete(key interface{}) error
	Invalidate(options InvalidateOptions) error
	Clear() error
	GetType() string
}

according to the above case, there is my thoughts:

  1. all cache implement CacheInterface, all store implement StoreInterface, this is good, but the two interface should be different (CacheInterface is an wrapper of StoreInterface, but provide extern functions like: "metrics, loadable, chain and others"), I would change CacheInterface as:
// CacheInterface represents the interface for all caches (aggregates, metric, memory, redis, ...)
type CacheInterface interface {
	Get(key interface{}) (interface{}, error)
	Set(key, object interface{}, options *store.Options) error
	Delete(key interface{}) error
	Invalidate(options store.InvalidateOptions) error
	Clear() error

        GetType() string                                     // return cache type
        GetCodec()  codec.CodecInterface{}  // get codec instance
        GetStore() store.StoreInterface{}        // get current store instance
}
  1. all cache constructor should return CacheInterface to limit usage.

  2. I also think about extern functions, Could we coding them as WithLoadFunc() WithMetrics() WithChain()

type Option struct {
    marshal mashaler.MarshalerInterface
    // ...  other fields
}

func WithMarshal(params ...string) *Option {
  // do something
}

func NewCache(store store.StoreInterface, opts ...*Options) CacheInterface {
      // to construct a Cache
}

I hope this image can help to understand me ~~~

image

Cannot write reliable tests for cache invalidation

I am testing a cache that I've written on top of eko/gocache. I need to wait for an invalidation to occur before I can move on to my next step. Currently I am waiting for the invalidation via:

	// wait for cache invalidation
	assert.Eventually(t, func() bool {
		_, err = s.localStore.Get(ctx, key)
		return err != nil
	}, 10*time.Second, 10*time.Millisecond, "timed out waiting for local store")

this loops for 10 seconds, checking every 10 milliseconds to see if the value has been ejected.

Usually this passes, but sometimes it does not pass. I cannot figure out why this sometimes fails.

I am using Invalidate and not Delete, so it is invalidating via the tag.

Local store in this case is Ristretto.

support custom prometheus registry

Steps for Reproduction

Currently, the prometheus metrics implementation for the cache only supports the use of the default registerer. This introduces global state into peoples applications and makes it difficult for people with custom registerers to use the prometheus metrics implementation.

Expected behavior:

allow people the option of using a custom metrics registry

Actual behavior:

No metrics for people using a custom prometheus metrics registry

Platforms:

Include browser, operating system and respective versions

Versions:

Which versions are you running?

Unable to install v2 using go get

I tried go get -u github.com/eko/gocache/v2/cache
And got

# github.com/XiaoMi/pegasus-go-client/idl/base
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\blob.go:18:31: not enough arguments in call to iprot.ReadBinary
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\blob.go:27:26: not enough arguments in call to oprot.WriteBinary
        have ([]byte)
        want (context.Context, []byte)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\error_code.go:105:34: not enough arguments in call to iprot.ReadString
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\error_code.go:110:26: not enough arguments in call to oprot.WriteString
        have (string)
        want (context.Context, string)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\gpid.go:18:25: not enough arguments in call to iprot.ReadI64
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\gpid.go:30:23: not enough arguments in call to oprot.WriteI64
        have (int64)
        want (context.Context, int64)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\rpc_address.go:26:31: not enough arguments in call to iprot.ReadI64
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\base\rpc_address.go:35:23: not enough arguments in call to oprot.WriteI64
        have (int64)
        want (context.Context, int64)
# github.com/XiaoMi/pegasus-go-client/idl/cmd
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:42:36: not enough arguments in call to iprot.ReadStructBegin
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:47:55: not enough arguments in call to iprot.ReadFieldBegin
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:61:25: not enough arguments in call to iprot.Skip
        have (thrift.TType)
        want (context.Context, thrift.TType)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:80:31: not enough arguments in call to iprot.ReadFieldEnd
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:84:31: not enough arguments in call to iprot.ReadStructEnd
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:91:31: not enough arguments in call to iprot.ReadString
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:100:37: not enough arguments in call to iprot.ReadListBegin
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:108:32: not enough arguments in call to iprot.ReadString
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:115:29: not enough arguments in call to iprot.ReadListEnd
        have ()
        want (context.Context)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:122:34: not enough arguments in call to oprot.WriteStructBegin
        have (string)
        want (context.Context, string)
..\..\go\pkg\mod\github.com\!xiao!mi\[email protected]\idl\cmd\cmd.go:122:34: too many errors

Version 1.2 also does not work, with the same problem.
OS: Windows 11 64 bit
Golang: Version 1.17
Any help is appreciated. Thank you.

Fallback from Redis to bigcache will panic: interface conversion: interface {} is string, not []uint8

When I chained Bigcache & Redis, the value follow below step is ok:

Set: value -> Bigcache -> Redis
Get: value <- Bigcache

But when application restarted, the flow became

Get: value <- Redis
               \
                +---> Bigcache

Now the application will panic:

panic: interface conversion: interface {} is string, not []uint8

goroutine 13 [running]:
github.com/eko/gocache/store.(*BigcacheStore).Set(0xc0080b2500, 0x14d63a0, 0xc00009e010, 0x14d63a0, 0xc000020b30, 0xc00057a060, 0x1b33501, 0xc00009e010)
        /Users/wh/codes/go/pkg/mod/github.com/eko/[email protected]/store/bigcache.go:68 +0x196
github.com/eko/gocache/codec.(*Codec).Set(0xc0080b2540, 0x14d63a0, 0xc00009e010, 0x14d63a0, 0xc000020b30, 0xc00057a060, 0x1, 0xc00057a060)
        /Users/wh/codes/go/pkg/mod/github.com/eko/[email protected]/codec/codec.go:66 +0x69
github.com/eko/gocache/cache.(*Cache).Set(0xc000020900, 0x14d63a0, 0x162b270, 0x14d63a0, 0xc000020b30, 0xc00057a060, 0x0, 0x0)
        /Users/wh/codes/go/pkg/mod/github.com/eko/[email protected]/cache/cache.go:45 +0x9b
github.com/eko/gocache/cache.(*ChainCache).setter(0xc0080b25a0)
        /Users/wh/codes/go/pkg/mod/github.com/eko/[email protected]/cache/chain.go:48 +0xfb
created by github.com/eko/gocache/cache.NewChain
        /Users/wh/codes/go/pkg/mod/github.com/eko/[email protected]/cache/chain.go:35 +0xa7
exit status 2

Steps for Reproduction

Run below application multiple times

package main

import (
	"time"

	"github.com/allegro/bigcache"
	"github.com/eko/gocache/cache"
	"github.com/eko/gocache/marshaler"
	"github.com/eko/gocache/store"
	"github.com/go-redis/redis/v7"
)

func main() {
	// Initialize Ristretto cache and Redis client
	bigcache, err := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))
	if err != nil {
		panic(err)
	}

	redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})

	// Initialize stores
	ristrettoStore := store.NewBigcache(bigcache, nil)
	redisStore := store.NewRedis(redisClient, &store.Options{Expiration: 5 * time.Second})

	// Initialize chained cache
	cacheManager := cache.NewChain(
		cache.New(ristrettoStore),
		cache.New(redisStore),
	)
	c := marshaler.New(cacheManager)
	var s string
	c.Get("abcd", &s)
	if s == "" {
		c.Set("abcd", "abc", &store.Options{
			Expiration: 10 * time.Minute,
		})
	}
	println(s)
	time.Sleep(10 * time.Second)
}

Expected behavior:

Working ok.

Actual behavior:

panic

Platforms:

Linux & macOS.

Versions:

v1.1.1

Support for Hazelcast cache

Like other cache support for example redis , memcache . Tool should be extended to provide support for hazelcast.

Go 1.18 generics support?

Have you considered adding support for generics to remove the need for casting when using the cache? I think a change like that would have to be a new major version, but I think that it would make using the cache much nicer. I don't know the implications for the stores interface. I would have to experiment a bit to understand the implications. I think this would be a fun little project, so I'm open to making the PR :)

can use redis.UniversalClient

github.com/go-redis/redis/v8 privide UniversalClient.

There is basically no difference between the two redis stores. So i think the RedisClusterClientInterface interface can be removed.

Users should not modify the code when switching redis single node to cluster. They just need to modify the address array in the configuration.

What do you think?

Suggestion for an easier interface

I wrote and maintain pagoda, a rapid, full-stack web development starter kit. I included gocache to provide a flexible way to handle caching that wouldn't have to be changed if you opt for a different cache provider. While I think this module is great, I found the interface to be somewhat cumbersome to deal with so I wrote a wrapper on top of it that I think turned out pretty well. Since this is something I'd like available in other projects, I thought I'd see if it's something you'd be interested in bringing in.

Here is the overall explanation of how it works and what is available. And here is the client code that provides that.

For quick reference, here is an example:

err := cache.
    Set().
    Key("my-key").
    Tags("tag1", "tag2").
    Expiration(time.Hour * 2).
    Data(myData).
    Save(ctx)

Can we Ping a store?

Thanks for the great package. I just started using it and it'a exactly what I needed.

Is there an easy/idiomatic way to ping a particular cache store to ensure credentials and addresses are valid?

Many thanks.

There is a bug in the Invalidate method in the redis store

I'm not sure of the correct fix. But here is the problem

In the setTags function of RedisStore

You set the tag for the key like so

s.Set(tagKey, []byte(strings.Join(cacheKeys, ",")), &Options{
			Expiration: 720 * time.Hour,
})

Then in Invalidate function of RedisStore

You guys do the following

	result, err := s.Get(tagKey)
			if err != nil {
				fmt.Println("Didn't find tag!!")
				return nil
			}

			var cacheKeys = []string{}
			if bytes, ok := result.([]byte); ok {
				cacheKeys = strings.Split(string(bytes), ",")
			}

You type assert the result to a []byte.

But this assert fails because result is actually of type string

As you can see if you look at the implementation of RedisStore Get

func (s *RedisStore) Get(key interface{}) (interface{}, error) { return s.client.Get(key.(string)).Result() }

But the Result function returns a (string,error)

I think what you want is this

func (s *RedisStore) Get(key interface{}) (interface{}, error) { return s.client.Get(key.(string)).Bytes() }

At least this is the change I made to my local copy to get Invalidate to work.

Redis V8

Hi there! I'm about to upgrade redis lib to V8.
I wanted to know, before I start, if there is any particular reason of using v7 instead of v8? and why the upgrade isn't done today?

Any considerations I should know?

Thanks in advance.

Jose.-

[Error While Compiling] Missing go.sum for X, Y, Z after installing the library

I installed the library as documented

go get github.com/eko/gocache/v3

then I installed the in-memory store

go get github.com/patrickmn/go-cache

And I have a code without any syntax error like this

package main

import (
	"context"
	"fmt"
	"github.com/eko/gocache/v3/cache"
	"github.com/eko/gocache/v3/store"
	gocache "github.com/patrickmn/go-cache"
	"time"
)

func main() {
	cacheManager, _ := createCache()
	err := cacheManager.Set(context.Background(), "my-key", []byte("my-value"))
	if err != nil {
		panic(err)
	}

	value, err := cacheManager.Get(context.Background(), "my-key")
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s", value)

}

func createCache() (*cache.Cache[[]byte], error) {
	gocacheClient := gocache.New(5*time.Minute, 10*time.Minute)
	gocacheStore := store.NewGoCache(gocacheClient)
	cacheManager := cache.New[[]byte](gocacheStore)

	return cacheManager, nil
}

When trying to compile I get error as below (yes I tried also go mod tidy and did not help)

../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/pegasus.go:10:2: missing go.sum entry for module providing package github.com/XiaoMi/pegasus-go-client/admin (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/pegasus.go:11:2: missing go.sum entry for module providing package github.com/XiaoMi/pegasus-go-client/pegasus (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/memcache.go:12:2: missing go.sum entry for module providing package github.com/bradfitz/gomemcache/memcache (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/redis.go:8:2: missing go.sum entry for module providing package github.com/go-redis/redis/v8 (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/gin-gonic/[email protected]/binding/protobuf.go:11:2: missing go.sum entry for module providing package github.com/golang/protobuf/proto (imported by github.com/gin-gonic/gin/binding); to add:
        go get github.com/gin-gonic/gin/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/metrics/prometheus.go:5:2: missing go.sum entry for module providing package github.com/prometheus/client_golang/prometheus (imported by github.com/eko/gocache/v3/metrics); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/metrics/prometheus.go:6:2: missing go.sum entry for module providing package github.com/prometheus/client_golang/prometheus/promauto (imported by github.com/eko/gocache/v3/metrics); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/pegasus.go:12:2: missing go.sum entry for module providing package github.com/spf13/cast (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/options_test_matchers.go:7:2: missing go.sum entry for module providing package golang.org/x/exp/slices (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
../../../go/pkg/mod/github.com/eko/gocache/[email protected]/store/memcache.go:10:2: missing go.sum entry for module providing package golang.org/x/sync/errgroup (imported by github.com/eko/gocache/v3/store); to add:
        go get github.com/eko/gocache/v3/[email protected]
make: *** [build] Error 1

Wrong type for method Del when initialize Redis Store

I want to implement a chained cache with Redis and BigCache. I don't have problem to declare Bigcache store, but have problem with redis. I using redis from "github.com/go-redis/redis", gocache store and gocache cache

this is how i declare my cache:

var LocationCache *cache.ChainCache

func createCache(host string) *cache.ChainCache {
	redisClient := redis.NewClient(&redis.Options{Addr: host})
	bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(720 * time.Hour))
	bigcacheStorage := store.NewBigcache(bigcacheClient, nil)
	redisStorage := store.NewRedis(redisClient, nil)

	LocationCache = cache.NewChain(
		cache.New(bigcacheStorage),
		cache.New(redisStorage))
	return LocationCache
}

But it give me this error: cannot use redisClient (variable of type *redis.Client) as store.RedisClientInterface value in argument to store.NewRedis: wrong type for method Del"

Is anyone can help me? Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.