Skip to content

Commit

Permalink
add v2 with generics
Browse files Browse the repository at this point in the history
  • Loading branch information
paskal committed Feb 18, 2024
1 parent 7babaa0 commit 4375d49
Show file tree
Hide file tree
Showing 25 changed files with 3,576 additions and 13 deletions.
19 changes: 18 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,28 @@ jobs:
TZ: "America/Chicago"
ENABLE_REDIS_TESTS: "true"

- name: build and test for v2
run: |
go get -v
go test -timeout=60s -race -covermode=atomic -coverprofile=$GITHUB_WORKSPACE/profile.cov_tmp
# combine the coverage files
cat $GITHUB_WORKSPACE/profile.cov_tmp | grep -v "_mock.go" | grep -v "mode:" >> $GITHUB_WORKSPACE/profile.cov
go build -race
env:
TZ: "America/Chicago"
ENABLE_REDIS_TESTS: "true"
working-directory: v2

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
version: latest

- name: golangci-lint for v2
uses: golangci/golangci-lint-action@v4
with:
version: latest
working-directory: v2

- name: submit coverage
run: |
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Loading Cache Wrapper [![Build Status](https://github.com/go-pkgz/lcw/workflows/build/badge.svg)](https://github.com/go-pkgz/lcw/actions) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/lcw/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/lcw?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/lcw?status.svg)](https://godoc.org/github.com/go-pkgz/lcw)
# Loading Cache Wrapper [![Build Status](https://github.com/go-pkgz/lcw/workflows/build/badge.svg)](https://github.com/go-pkgz/lcw/actions) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/lcw/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/lcw?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/lcw?status.svg)](https://godoc.org/github.com/go-pkgz/lcw/v2)

The library adds a thin layer on top of [lru cache](https://github.com/hashicorp/golang-lru) and internal implementation
of expirable cache.
Expand All @@ -25,34 +25,35 @@ Main features:

## Install and update

`go get -u github.com/go-pkgz/lcw`
`go get -u github.com/go-pkgz/lcw/v2`

## Usage

```go
package main

import (
"github.com/go-pkgz/lcw"
"github.com/go-pkgz/lcw/v2"
)

func main() {
cache, err := lcw.NewLruCache(lcw.MaxKeys(500), lcw.MaxCacheSize(65536), lcw.MaxValSize(200), lcw.MaxKeySize(32))
o := lcw.NewOpts[int]()
cache, err := lcw.NewLruCache(o.MaxKeys(500), o.MaxCacheSize(65536), o.MaxValSize(200), o.MaxKeySize(32))
if err != nil {
panic("failed to create cache")
}
defer cache.Close()

val, err := cache.Get("key123", func() (interface{}, error) {
res, err := getDataFromSomeSource(params) // returns string
val, err := cache.Get("key123", func() (int, error) {
res, err := getDataFromSomeSource(params) // returns int
return res, err
})

if err != nil {
panic("failed to get data")
}

s := val.(string) // cached value
s := val // cached value
}
```

Expand Down
11 changes: 6 additions & 5 deletions redis_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lcw

import (
"context"
"errors"
"fmt"
"sync/atomic"
"time"
Expand Down Expand Up @@ -44,18 +45,18 @@ func NewRedisCache(backend *redis.Client, opts ...Option) (*RedisCache, error) {
// Get gets value by key or load with fn if not found in cache
func (c *RedisCache) Get(key string, fn func() (interface{}, error)) (data interface{}, err error) {
v, getErr := c.backend.Get(context.Background(), key).Result()
switch getErr {
switch {
// RedisClient returns nil when find a key in DB
case nil:
case getErr == nil:
atomic.AddInt64(&c.Hits, 1)
return v, nil
// RedisClient returns redis.Nil when doesn't find a key in DB
case redis.Nil:
// RedisClient returns redis.Nil when doesn't find a key in DB
case errors.Is(getErr, redis.Nil):
if data, err = fn(); err != nil {
atomic.AddInt64(&c.Errors, 1)
return data, err
}
// RedisClient returns !nil when something goes wrong while get data
// RedisClient returns !nil when something goes wrong while get data
default:
atomic.AddInt64(&c.Errors, 1)
return v, getErr
Expand Down
84 changes: 84 additions & 0 deletions v2/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Package lcw adds a thin layer on top of lru and expirable cache providing more limits and common interface.
// The primary method to get (and set) data to/from the cache is LoadingCache.Get returning stored data for a given key or
// call provided func to retrieve and store, similar to Guava loading cache.
// Limits allow max values for key size, number of keys, value size and total size of values in the cache.
// CacheStat gives general stats on cache performance.
// 3 flavors of cache provided - NoP (do-nothing cache), ExpirableCache (TTL based), and LruCache
package lcw

import (
"fmt"
)

// Sizer allows to perform size-based restrictions, optional.
// If not defined both maxValueSize and maxCacheSize checks will be ignored
type Sizer interface {
Size() int
}

// LoadingCache defines guava-like cache with Get method returning cached value ao retrieving it if not in cache
type LoadingCache[V any] interface {
Get(key string, fn func() (V, error)) (val V, err error) // load or get from cache
Peek(key string) (V, bool) // get from cache by key
Invalidate(fn func(key string) bool) // invalidate items for func(key) == true
Delete(key string) // delete by key
Purge() // clear cache
Stat() CacheStat // cache stats
Keys() []string // list of all keys
Close() error // close open connections
}

// CacheStat represent stats values
type CacheStat struct {
Hits int64
Misses int64
Keys int
Size int64
Errors int64
}

// String formats cache stats
func (s CacheStat) String() string {
ratio := 0.0
if s.Hits+s.Misses > 0 {
ratio = float64(s.Hits) / float64(s.Hits+s.Misses)
}
return fmt.Sprintf("{hits:%d, misses:%d, ratio:%.2f, keys:%d, size:%d, errors:%d}",
s.Hits, s.Misses, ratio, s.Keys, s.Size, s.Errors)
}

// Nop is do-nothing implementation of LoadingCache
type Nop[V any] struct{}

// NewNopCache makes new do-nothing cache
func NewNopCache[V any]() *Nop[V] {
return &Nop[V]{}
}

// Get calls fn without any caching
func (n *Nop[V]) Get(_ string, fn func() (V, error)) (V, error) { return fn() }

// Peek does nothing and always returns false
func (n *Nop[V]) Peek(string) (V, bool) { var emptyValue V; return emptyValue, false }

// Invalidate does nothing for nop cache
func (n *Nop[V]) Invalidate(func(key string) bool) {}

// Purge does nothing for nop cache
func (n *Nop[V]) Purge() {}

// Delete does nothing for nop cache
func (n *Nop[V]) Delete(string) {}

// Keys does nothing for nop cache
func (n *Nop[V]) Keys() []string { return nil }

// Stat always 0s for nop cache
func (n *Nop[V]) Stat() CacheStat {
return CacheStat{}
}

// Close does nothing for nop cache
func (n *Nop[V]) Close() error {
return nil
}
Loading

0 comments on commit 4375d49

Please sign in to comment.