Skip to content

Commit

Permalink
Merge pull request #71 from hyperledger/crud-str-id
Browse files Browse the repository at this point in the history
Allow any string for ID column, and support collections without an `updated` column
  • Loading branch information
peterbroadhurst authored Jun 30, 2023
2 parents 17b0710 + 9066e70 commit da6668c
Show file tree
Hide file tree
Showing 22 changed files with 680 additions and 111 deletions.
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ linters:
disable-all: false
enable:
- bodyclose
- depguard
- dogsled
- errcheck
- goconst
Expand Down
55 changes: 46 additions & 9 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,33 @@ import (
"github.com/karlseguin/ccache"
)

type BehaviorOption int

const (
// StrictExpiry sets the behavior so that an expired item will never be returned by the cache.
//
// The default behavior is that items are returned until they have been purged from the cache by an
// synchronous reaper
// NOTE: see issue https://github.com/hyperledger/firefly-common/issues/85 on operation of the async reaper.
StrictExpiry BehaviorOption = iota

// TTLFromInitialAdd sets the behavior so that the time-to-live for a cache entry is set when
// it is added, and not extended when the item is accessed.
// This is useful if you have code that wants to rely on a cache-miss as a way to force regular
// synchronization of cached data with a remote source of truth
//
// The default behavior is that the TTL is extended each time a cache entry is accessed.
// This is useful if you're managing the cache such that the data inside it is always valid,
// and want to keep the most frequently accessed data in the cache indefinitely.
TTLFromInitialAdd
)

// Manager contains functions to manage cache instances.
// It provides functions for creating new cache instances and list all of the names of existing cache instances
// Each cache instance has unique name and its own cache size and TTL configuration.
type Manager interface {
// Get a cache by name, if a cache already exists with the same name, it will be returned as is without checking maxSize, ttl and enabled matches
GetCache(ctx context.Context, namespace, name string, maxSize int64, ttl time.Duration, enabled bool) (CInterface, error)
GetCache(ctx context.Context, namespace, name string, maxSize int64, ttl time.Duration, enabled bool, behaviorOptions ...BehaviorOption) (CInterface, error)
ListCacheNames(namespace string) []string
ResetCaches(namespace string)
IsEnabled() bool
Expand All @@ -55,12 +76,14 @@ type CInterface interface {
}

type CCache struct {
enabled bool
ctx context.Context
namespace string
name string
cache *ccache.Cache
cacheTTL time.Duration
enabled bool
ctx context.Context
namespace string
name string
cache *ccache.Cache
cacheTTL time.Duration
strictExpiry bool
ttlFromInitialAdd bool
}

func (c *CCache) Set(key string, val interface{}) {
Expand All @@ -71,7 +94,13 @@ func (c *CCache) Set(key string, val interface{}) {
func (c *CCache) Get(key string) interface{} {
if c.enabled {
if cached := c.cache.Get(key); cached != nil {
cached.Extend(c.cacheTTL)
if c.strictExpiry && cached.Expired() {
c.Delete(key)
return nil
}
if !c.ttlFromInitialAdd {
cached.Extend(c.cacheTTL)
}
return cached.Value()
}
}
Expand Down Expand Up @@ -144,7 +173,7 @@ type cacheManager struct {
configuredCaches map[string]*CCache // Cache manager maintain a list of named configured CCache, the names are unique
}

func (cm *cacheManager) GetCache(ctx context.Context, namespace, name string, maxSize int64, ttl time.Duration, enabled bool) (CInterface, error) {
func (cm *cacheManager) GetCache(ctx context.Context, namespace, name string, maxSize int64, ttl time.Duration, enabled bool, behaviors ...BehaviorOption) (CInterface, error) {
cm.m.Lock()
defer cm.m.Unlock()
fqName := fmt.Sprintf("%s:%s", namespace, name)
Expand All @@ -163,6 +192,14 @@ func (cm *cacheManager) GetCache(ctx context.Context, namespace, name string, ma
cacheTTL: ttl,
enabled: enabledValue,
}
for _, b := range behaviors {
switch b {
case StrictExpiry:
cache.strictExpiry = true
case TTLFromInitialAdd:
cache.ttlFromInitialAdd = true
}
}
cm.configuredCaches[fqName] = cache
}
return cache, nil
Expand Down
20 changes: 20 additions & 0 deletions pkg/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,23 @@ func TestResetCachesForNamespace(t *testing.T) {
assert.Nil(t, cacheNS1_b.Get("key1"))

}

func TestGetStrictExpiry(t *testing.T) {
ctx := context.Background()
cm := NewCacheManager(ctx, true)
cache0, _ := cm.GetCache(ctx, "ns1", "cacheA", 1, time.Nanosecond, true, StrictExpiry, TTLFromInitialAdd)
assert.True(t, cache0.(*CCache).strictExpiry)
assert.True(t, cache0.(*CCache).ttlFromInitialAdd)
cache1, _ := cm.GetCache(ctx, "ns1", "cacheB", 1, time.Nanosecond, true)
assert.False(t, cache1.(*CCache).strictExpiry)
assert.False(t, cache1.(*CCache).ttlFromInitialAdd)

cache0.Set("test", "will not be returned after expiry")
cache1.Set("test", "will be returned after expiry")

for cache0.Get("test") != nil {
time.Sleep(1 * time.Millisecond)
}

assert.NotNil(t, cache1.Get("test"))
}
Loading

0 comments on commit da6668c

Please sign in to comment.