Bump the go-modules-updates group with 2 updates

Bumps the go-modules-updates group with 2 updates: [github.com/didip/tollbooth/v7](https://github.com/didip/tollbooth) and [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `github.com/didip/tollbooth/v7` from 7.0.1 to 7.0.2
- [Commits](https://github.com/didip/tollbooth/compare/v7.0.1...v7.0.2)

Updates `golang.org/x/crypto` from 0.23.0 to 0.24.0
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: github.com/didip/tollbooth/v7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2024-07-01 12:07:21 +00:00
committed by Umputun
parent 1959706ab4
commit 1e81d22fef
22 changed files with 519 additions and 508 deletions

View File

@@ -1,21 +1,17 @@
linters:
enable:
- megacheck
- revive
- govet
- unconvert
- megacheck
- structcheck
- gas
- gocyclo
- dupl
- misspell
- unparam
- varcheck
- deadcode
- unused
- typecheck
- ineffassign
- varcheck
- stylecheck
- gochecknoinits
- exportloopref

View File

@@ -171,6 +171,8 @@ Sometimes, other frameworks require a little bit of shim to use Tollbooth. These
## My other Go libraries
* [ErrStack](https://github.com/didip/errstack): A small library to combine errors and also display filename and line number.
* [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite.
* [LaborUnion](https://github.com/didip/laborunion): A dynamic worker pool library.

View File

@@ -6,7 +6,7 @@ import (
"sync"
"time"
cache "github.com/go-pkgz/expirable-cache"
cache "github.com/go-pkgz/expirable-cache/v3"
"github.com/didip/tollbooth/v7/internal/time/rate"
)
@@ -36,9 +36,9 @@ func New(generalExpirableOptions *ExpirableOptions) *Limiter {
lmt.generalExpirableOptions.DefaultExpirationTTL = 87600 * time.Hour
}
lmt.tokenBuckets, _ = cache.NewCache(cache.TTL(lmt.generalExpirableOptions.DefaultExpirationTTL))
lmt.tokenBuckets = cache.NewCache[string, *rate.Limiter]().WithTTL(lmt.generalExpirableOptions.DefaultExpirationTTL)
lmt.basicAuthUsers, _ = cache.NewCache(cache.TTL(lmt.generalExpirableOptions.DefaultExpirationTTL))
lmt.basicAuthUsers = cache.NewCache[string, bool]().WithTTL(lmt.generalExpirableOptions.DefaultExpirationTTL)
return lmt
}
@@ -81,17 +81,17 @@ type Limiter struct {
generalExpirableOptions *ExpirableOptions
// List of basic auth usernames to limit.
basicAuthUsers cache.Cache
basicAuthUsers cache.Cache[string, bool]
// Map of HTTP headers to limit.
// Empty means skip headers checking.
headers map[string]cache.Cache
headers map[string]cache.Cache[string, bool]
// Map of Context values to limit.
contextValues map[string]cache.Cache
contextValues map[string]cache.Cache[string, bool]
// Map of limiters with TTL
tokenBuckets cache.Cache
tokenBuckets cache.Cache[string, *rate.Limiter]
// Ignore URL on the rate limiter keys
ignoreURL bool
@@ -261,9 +261,9 @@ func (l *Limiter) SetOnLimitReached(fn func(w http.ResponseWriter, r *http.Reque
// ExecOnLimitReached is thread-safe way of executing after-rejection function when limit is reached.
func (l *Limiter) ExecOnLimitReached(w http.ResponseWriter, r *http.Request) {
l.RLock()
defer l.RUnlock()
fn := l.onLimitReached
l.RUnlock()
if fn != nil {
fn(w, r)
}
@@ -383,7 +383,7 @@ func (l *Limiter) DeleteExpiredTokenBuckets() {
// SetHeaders is thread-safe way of setting map of HTTP headers to limit.
func (l *Limiter) SetHeaders(headers map[string][]string) *Limiter {
if l.headers == nil {
l.headers = make(map[string]cache.Cache)
l.headers = make(map[string]cache.Cache[string, bool])
}
for header, entries := range headers {
@@ -419,7 +419,7 @@ func (l *Limiter) SetHeader(header string, entries []string) *Limiter {
}
if !found {
existing, _ = cache.NewCache(cache.TTL(ttl))
existing = cache.NewCache[string, bool]().WithTTL(ttl)
}
for _, entry := range entries {
@@ -450,7 +450,7 @@ func (l *Limiter) RemoveHeader(header string) *Limiter {
}
l.Lock()
l.headers[header], _ = cache.NewCache(cache.TTL(ttl))
l.headers[header] = cache.NewCache[string, bool]().WithTTL(ttl)
l.Unlock()
return l
@@ -476,7 +476,7 @@ func (l *Limiter) RemoveHeaderEntries(header string, entriesForRemoval []string)
// SetContextValues is thread-safe way of setting map of HTTP headers to limit.
func (l *Limiter) SetContextValues(contextValues map[string][]string) *Limiter {
if l.contextValues == nil {
l.contextValues = make(map[string]cache.Cache)
l.contextValues = make(map[string]cache.Cache[string, bool])
}
for contextValue, entries := range contextValues {
@@ -512,7 +512,7 @@ func (l *Limiter) SetContextValue(contextValue string, entries []string) *Limite
}
if !found {
existing, _ = cache.NewCache(cache.TTL(ttl))
existing = cache.NewCache[string, bool]().WithTTL(ttl)
}
for _, entry := range entries {
@@ -543,7 +543,7 @@ func (l *Limiter) RemoveContextValue(contextValue string) *Limiter {
}
l.Lock()
l.contextValues[contextValue], _ = cache.NewCache(cache.TTL(ttl))
l.contextValues[contextValue] = cache.NewCache[string, bool]().WithTTL(ttl)
l.Unlock()
return l
@@ -585,7 +585,7 @@ func (l *Limiter) limitReachedWithTokenBucketTTL(key string, tokenBucketTTL time
return false
}
return !expiringMap.(*rate.Limiter).Allow()
return !expiringMap.Allow()
}
// LimitReached returns a bool indicating if the Bucket identified by key ran out of tokens.
@@ -606,5 +606,5 @@ func (l *Limiter) Tokens(key string) int {
return 0
}
return int(expiringMap.(*rate.Limiter).TokensAt(time.Now()))
return int(expiringMap.TokensAt(time.Now()))
}

View File

@@ -1,15 +0,0 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/

View File

@@ -1,58 +0,0 @@
linters-settings:
govet:
check-shadowing: true
gocyclo:
min-complexity: 15
maligned:
suggest-new: true
goconst:
min-len: 2
min-occurrences: 2
misspell:
locale: US
lll:
line-length: 140
gocritic:
enabled-tags:
- performance
- style
- experimental
disabled-checks:
- wrapperFunc
linters:
enable:
- megacheck
- revive
- govet
- unconvert
- megacheck
- structcheck
- gas
- gocyclo
- dupl
- misspell
- unparam
- varcheck
- deadcode
- typecheck
- ineffassign
- varcheck
- stylecheck
- gochecknoinits
- exportloopref
- gocritic
- nakedret
- gosimple
- prealloc
fast: false
disable-all: true
run:
output:
format: tab
skip-dirs:
- vendor
issues:
exclude-use-default: false

View File

@@ -1,70 +0,0 @@
# expirable-cache
[![Build Status](https://github.com/go-pkgz/expirable-cache/workflows/build/badge.svg)](https://github.com/go-pkgz/expirable-cache/actions)
[![Coverage Status](https://coveralls.io/repos/github/go-pkgz/expirable-cache/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/expirable-cache?branch=master)
[![godoc](https://godoc.org/github.com/go-pkgz/expirable-cache?status.svg)](https://pkg.go.dev/github.com/go-pkgz/expirable-cache?tab=doc)
Package cache implements expirable cache.
- Support LRC, LRU and TTL-based eviction.
- Package is thread-safe and doesn't spawn any goroutines.
- On every Set() call, cache deletes single oldest entry in case it's expired.
- In case MaxSize is set, cache deletes the oldest entry disregarding its expiration date to maintain the size,
either using LRC or LRU eviction.
- In case of default TTL (10 years) and default MaxSize (0, unlimited) the cache will be truly unlimited
and will never delete entries from itself automatically.
**Important**: only reliable way of not having expired entries stuck in a cache is to
run cache.DeleteExpired periodically using [time.Ticker](https://golang.org/pkg/time/#Ticker),
advisable period is 1/2 of TTL.
This cache is heavily inspired by [hashicorp/golang-lru](https://github.com/hashicorp/golang-lru) _simplelru_ implementation.
### Usage example
```go
package main
import (
"fmt"
"time"
"github.com/go-pkgz/expirable-cache/v2"
)
func main() {
// make cache with short TTL and 3 max keys
c := cache.NewCache[string, string]().WithMaxKeys(3).WithTTL(time.Millisecond * 10)
// set value under key1.
// with 0 ttl (last parameter) will use cache-wide setting instead (10ms).
c.Set("key1", "val1", 0)
// get value under key1
r, ok := c.Get("key1")
// check for OK value, because otherwise return would be nil and
// type conversion will panic
if ok {
rstr := r.(string) // convert cached value from interface{} to real type
fmt.Printf("value before expiration is found: %v, value: %v\n", ok, rstr)
}
time.Sleep(time.Millisecond * 11)
// get value under key1 after key expiration
r, ok = c.Get("key1")
// don't convert to string as with ok == false value would be nil
fmt.Printf("value after expiration is found: %v, value: %v\n", ok, r)
// set value under key2, would evict old entry because it is already expired.
// ttl (last parameter) overrides cache-wide ttl.
c.Set("key2", "val2", time.Minute*5)
fmt.Printf("%+v\n", c)
// Output:
// value before expiration is found: true, value: val1
// value after expiration is found: false, value: <nil>
// Size: 1, Stats: {Hits:1 Misses:1 Added:2 Evicted:1} (50.0%)
}
```

View File

@@ -1,272 +0,0 @@
// Package cache implements Cache similar to hashicorp/golang-lru
//
// Support LRC, LRU and TTL-based eviction.
// Package is thread-safe and doesn't spawn any goroutines.
// On every Set() call, cache deletes single oldest entry in case it's expired.
// In case MaxSize is set, cache deletes the oldest entry disregarding its expiration date to maintain the size,
// either using LRC or LRU eviction.
// In case of default TTL (10 years) and default MaxSize (0, unlimited) the cache will be truly unlimited
// and will never delete entries from itself automatically.
//
// Important: only reliable way of not having expired entries stuck in a cache is to
// run cache.DeleteExpired periodically using time.Ticker, advisable period is 1/2 of TTL.
package cache
import (
"container/list"
"fmt"
"sync"
"time"
)
// Cache defines cache interface
type Cache interface {
fmt.Stringer
Set(key string, value interface{}, ttl time.Duration)
Get(key string) (interface{}, bool)
Peek(key string) (interface{}, bool)
Keys() []string
Len() int
Invalidate(key string)
InvalidateFn(fn func(key string) bool)
RemoveOldest()
DeleteExpired()
Purge()
Stat() Stats
}
// Stats provides statistics for cache
type Stats struct {
Hits, Misses int // cache effectiveness
Added, Evicted int // number of added and evicted records
}
// cacheImpl provides Cache interface implementation.
type cacheImpl struct {
ttl time.Duration
maxKeys int
isLRU bool
onEvicted func(key string, value interface{})
sync.Mutex
stat Stats
items map[string]*list.Element
evictList *list.List
}
// noEvictionTTL - very long ttl to prevent eviction
const noEvictionTTL = time.Hour * 24 * 365 * 10
// NewCache returns a new Cache.
// Default MaxKeys is unlimited (0).
// Default TTL is 10 years, sane value for expirable cache is 5 minutes.
// Default eviction mode is LRC, appropriate option allow to change it to LRU.
func NewCache(options ...Option) (Cache, error) {
res := cacheImpl{
items: map[string]*list.Element{},
evictList: list.New(),
ttl: noEvictionTTL,
maxKeys: 0,
}
for _, opt := range options {
if err := opt(&res); err != nil {
return nil, fmt.Errorf("failed to set cache option: %w", err)
}
}
return &res, nil
}
// Set key, ttl of 0 would use cache-wide TTL
func (c *cacheImpl) Set(key string, value interface{}, ttl time.Duration) {
c.Lock()
defer c.Unlock()
now := time.Now()
if ttl == 0 {
ttl = c.ttl
}
// Check for existing item
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
ent.Value.(*cacheItem).value = value
ent.Value.(*cacheItem).expiresAt = now.Add(ttl)
return
}
// Add new item
ent := &cacheItem{key: key, value: value, expiresAt: now.Add(ttl)}
entry := c.evictList.PushFront(ent)
c.items[key] = entry
c.stat.Added++
// Remove oldest entry if it is expired, only in case of non-default TTL.
if c.ttl != noEvictionTTL || ttl != noEvictionTTL {
c.removeOldestIfExpired()
}
// Verify size not exceeded
if c.maxKeys > 0 && len(c.items) > c.maxKeys {
c.removeOldest()
}
}
// Get returns the key value if it's not expired
func (c *cacheImpl) Get(key string) (interface{}, bool) {
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
// Expired item check
if time.Now().After(ent.Value.(*cacheItem).expiresAt) {
c.stat.Misses++
return nil, false
}
if c.isLRU {
c.evictList.MoveToFront(ent)
}
c.stat.Hits++
return ent.Value.(*cacheItem).value, true
}
c.stat.Misses++
return nil, false
}
// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
// Works exactly the same as Get in case of LRC mode (default one).
func (c *cacheImpl) Peek(key string) (interface{}, bool) {
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
// Expired item check
if time.Now().After(ent.Value.(*cacheItem).expiresAt) {
c.stat.Misses++
return nil, false
}
c.stat.Hits++
return ent.Value.(*cacheItem).value, true
}
c.stat.Misses++
return nil, false
}
// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *cacheImpl) Keys() []string {
c.Lock()
defer c.Unlock()
return c.keys()
}
// Len return count of items in cache, including expired
func (c *cacheImpl) Len() int {
c.Lock()
defer c.Unlock()
return c.evictList.Len()
}
// Invalidate key (item) from the cache
func (c *cacheImpl) Invalidate(key string) {
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
}
}
// InvalidateFn deletes multiple keys if predicate is true
func (c *cacheImpl) InvalidateFn(fn func(key string) bool) {
c.Lock()
defer c.Unlock()
for key, ent := range c.items {
if fn(key) {
c.removeElement(ent)
}
}
}
// RemoveOldest remove oldest element in the cache
func (c *cacheImpl) RemoveOldest() {
c.Lock()
defer c.Unlock()
c.removeOldest()
}
// DeleteExpired clears cache of expired items
func (c *cacheImpl) DeleteExpired() {
c.Lock()
defer c.Unlock()
for _, key := range c.keys() {
if time.Now().After(c.items[key].Value.(*cacheItem).expiresAt) {
c.removeElement(c.items[key])
}
}
}
// Purge clears the cache completely.
func (c *cacheImpl) Purge() {
c.Lock()
defer c.Unlock()
for k, v := range c.items {
delete(c.items, k)
c.stat.Evicted++
if c.onEvicted != nil {
c.onEvicted(k, v.Value.(*cacheItem).value)
}
}
c.evictList.Init()
}
// Stat gets the current stats for cache
func (c *cacheImpl) Stat() Stats {
c.Lock()
defer c.Unlock()
return c.stat
}
func (c *cacheImpl) String() string {
stats := c.Stat()
size := c.Len()
return fmt.Sprintf("Size: %d, Stats: %+v (%0.1f%%)", size, stats, 100*float64(stats.Hits)/float64(stats.Hits+stats.Misses))
}
// Keys returns a slice of the keys in the cache, from oldest to newest. Has to be called with lock!
func (c *cacheImpl) keys() []string {
keys := make([]string, 0, len(c.items))
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
keys = append(keys, ent.Value.(*cacheItem).key)
}
return keys
}
// removeOldest removes the oldest item from the cache. Has to be called with lock!
func (c *cacheImpl) removeOldest() {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
}
}
// removeOldest removes the oldest item from the cache in case it's already expired. Has to be called with lock!
func (c *cacheImpl) removeOldestIfExpired() {
ent := c.evictList.Back()
if ent != nil && time.Now().After(ent.Value.(*cacheItem).expiresAt) {
c.removeElement(ent)
}
}
// removeElement is used to remove a given list element from the cache. Has to be called with lock!
func (c *cacheImpl) removeElement(e *list.Element) {
c.evictList.Remove(e)
kv := e.Value.(*cacheItem)
delete(c.items, kv.key)
c.stat.Evicted++
if c.onEvicted != nil {
c.onEvicted(kv.key, kv.value)
}
}
// cacheItem is used to hold a value in the evictList
type cacheItem struct {
expiresAt time.Time
key string
value interface{}
}

View File

@@ -1,40 +0,0 @@
package cache
import "time"
// Option func type
type Option func(lc *cacheImpl) error
// OnEvicted called automatically for automatically and manually deleted entries
func OnEvicted(fn func(key string, value interface{})) Option {
return func(lc *cacheImpl) error {
lc.onEvicted = fn
return nil
}
}
// MaxKeys functional option defines how many keys to keep.
// By default it is 0, which means unlimited.
func MaxKeys(max int) Option {
return func(lc *cacheImpl) error {
lc.maxKeys = max
return nil
}
}
// TTL functional option defines TTL for all cache entries.
// By default it is set to 10 years, sane option for expirable cache might be 5 minutes.
func TTL(ttl time.Duration) Option {
return func(lc *cacheImpl) error {
lc.ttl = ttl
return nil
}
}
// LRU sets cache to LRU (Least Recently Used) eviction mode.
func LRU() Option {
return func(lc *cacheImpl) error {
lc.isLRU = true
return nil
}
}

371
vendor/github.com/go-pkgz/expirable-cache/v3/cache.go generated vendored Normal file
View File

@@ -0,0 +1,371 @@
// Package cache implements Cache similar to hashicorp/golang-lru
//
// Support LRC, LRU and TTL-based eviction.
// Package is thread-safe and doesn't spawn any goroutines.
// On every Set() call, cache deletes single oldest entry in case it's expired.
// In case MaxSize is set, cache deletes the oldest entry disregarding its expiration date to maintain the size,
// either using LRC or LRU eviction.
// In case of default TTL (10 years) and default MaxSize (0, unlimited) the cache will be truly unlimited
// and will never delete entries from itself automatically.
//
// Important: only reliable way of not having expired entries stuck in a cache is to
// run cache.DeleteExpired periodically using time.Ticker, advisable period is 1/2 of TTL.
package cache
import (
"container/list"
"fmt"
"sync"
"time"
)
// Cache defines cache interface
type Cache[K comparable, V any] interface {
fmt.Stringer
options[K, V]
Add(key K, value V) bool
Set(key K, value V, ttl time.Duration)
Get(key K) (V, bool)
GetExpiration(key K) (time.Time, bool)
GetOldest() (K, V, bool)
Contains(key K) (ok bool)
Peek(key K) (V, bool)
Values() []V
Keys() []K
Len() int
Remove(key K) bool
Invalidate(key K)
InvalidateFn(fn func(key K) bool)
RemoveOldest() (K, V, bool)
DeleteExpired()
Purge()
Resize(int) int
Stat() Stats
}
// Stats provides statistics for cache
type Stats struct {
Hits, Misses int // cache effectiveness
Added, Evicted int // number of added and evicted records
}
// cacheImpl provides Cache interface implementation.
type cacheImpl[K comparable, V any] struct {
ttl time.Duration
maxKeys int
isLRU bool
onEvicted func(key K, value V)
sync.Mutex
stat Stats
items map[K]*list.Element
evictList *list.List
}
// noEvictionTTL - very long ttl to prevent eviction
const noEvictionTTL = time.Hour * 24 * 365 * 10
// NewCache returns a new Cache.
// Default MaxKeys is unlimited (0).
// Default TTL is 10 years, sane value for expirable cache is 5 minutes.
// Default eviction mode is LRC, appropriate option allow to change it to LRU.
func NewCache[K comparable, V any]() Cache[K, V] {
return &cacheImpl[K, V]{
items: map[K]*list.Element{},
evictList: list.New(),
ttl: noEvictionTTL,
maxKeys: 0,
}
}
// Add adds a value to the cache. Returns true if an eviction occurred.
// Returns false if there was no eviction: the item was already in the cache,
// or the size was not exceeded.
func (c *cacheImpl[K, V]) Add(key K, value V) (evicted bool) {
return c.addWithTTL(key, value, c.ttl)
}
// Set key, ttl of 0 would use cache-wide TTL
func (c *cacheImpl[K, V]) Set(key K, value V, ttl time.Duration) {
c.addWithTTL(key, value, ttl)
}
// Returns true if an eviction occurred.
// Returns false if there was no eviction: the item was already in the cache,
// or the size was not exceeded.
func (c *cacheImpl[K, V]) addWithTTL(key K, value V, ttl time.Duration) (evicted bool) {
c.Lock()
defer c.Unlock()
now := time.Now()
if ttl == 0 {
ttl = c.ttl
}
// Check for existing item
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
ent.Value.(*cacheItem[K, V]).value = value
ent.Value.(*cacheItem[K, V]).expiresAt = now.Add(ttl)
return false
}
// Add new item
ent := &cacheItem[K, V]{key: key, value: value, expiresAt: now.Add(ttl)}
entry := c.evictList.PushFront(ent)
c.items[key] = entry
c.stat.Added++
// Remove the oldest entry if it is expired, only in case of non-default TTL.
if c.ttl != noEvictionTTL || ttl != noEvictionTTL {
c.removeOldestIfExpired()
}
evict := c.maxKeys > 0 && len(c.items) > c.maxKeys
// Verify size not exceeded
if evict {
c.removeOldest()
}
return evict
}
// Get returns the key value if it's not expired
func (c *cacheImpl[K, V]) Get(key K) (V, bool) {
def := *new(V)
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
// Expired item check
if time.Now().After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.stat.Misses++
return ent.Value.(*cacheItem[K, V]).value, false
}
if c.isLRU {
c.evictList.MoveToFront(ent)
}
c.stat.Hits++
return ent.Value.(*cacheItem[K, V]).value, true
}
c.stat.Misses++
return def, false
}
// Contains checks if a key is in the cache, without updating the recent-ness
// or deleting it for being stale.
func (c *cacheImpl[K, V]) Contains(key K) (ok bool) {
c.Lock()
defer c.Unlock()
_, ok = c.items[key]
return ok
}
// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
// Works exactly the same as Get in case of LRC mode (default one).
func (c *cacheImpl[K, V]) Peek(key K) (V, bool) {
def := *new(V)
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
// Expired item check
if time.Now().After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.stat.Misses++
return ent.Value.(*cacheItem[K, V]).value, false
}
c.stat.Hits++
return ent.Value.(*cacheItem[K, V]).value, true
}
c.stat.Misses++
return def, false
}
// GetExpiration returns the expiration time of the key. Non-existing key returns zero time.
func (c *cacheImpl[K, V]) GetExpiration(key K) (time.Time, bool) {
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
return ent.Value.(*cacheItem[K, V]).expiresAt, true
}
return time.Time{}, false
}
// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *cacheImpl[K, V]) Keys() []K {
c.Lock()
defer c.Unlock()
return c.keys()
}
// Values returns a slice of the values in the cache, from oldest to newest.
// Expired entries are filtered out.
func (c *cacheImpl[K, V]) Values() []V {
c.Lock()
defer c.Unlock()
values := make([]V, 0, len(c.items))
now := time.Now()
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
if now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
continue
}
values = append(values, ent.Value.(*cacheItem[K, V]).value)
}
return values
}
// Len return count of items in cache, including expired
func (c *cacheImpl[K, V]) Len() int {
c.Lock()
defer c.Unlock()
return c.evictList.Len()
}
// Resize changes the cache size. Size of 0 means unlimited.
func (c *cacheImpl[K, V]) Resize(size int) int {
c.Lock()
defer c.Unlock()
if size <= 0 {
c.maxKeys = 0
return 0
}
diff := c.evictList.Len() - size
if diff < 0 {
diff = 0
}
for i := 0; i < diff; i++ {
c.removeOldest()
}
c.maxKeys = size
return diff
}
// Invalidate key (item) from the cache
func (c *cacheImpl[K, V]) Invalidate(key K) {
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
}
}
// InvalidateFn deletes multiple keys if predicate is true
func (c *cacheImpl[K, V]) InvalidateFn(fn func(key K) bool) {
c.Lock()
defer c.Unlock()
for key, ent := range c.items {
if fn(key) {
c.removeElement(ent)
}
}
}
// Remove removes the provided key from the cache, returning if the
// key was contained.
func (c *cacheImpl[K, V]) Remove(key K) bool {
c.Lock()
defer c.Unlock()
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
return true
}
return false
}
// RemoveOldest remove the oldest element in the cache
func (c *cacheImpl[K, V]) RemoveOldest() (key K, value V, ok bool) {
c.Lock()
defer c.Unlock()
if ent := c.evictList.Back(); ent != nil {
c.removeElement(ent)
return ent.Value.(*cacheItem[K, V]).key, ent.Value.(*cacheItem[K, V]).value, true
}
return
}
// GetOldest returns the oldest entry
func (c *cacheImpl[K, V]) GetOldest() (key K, value V, ok bool) {
c.Lock()
defer c.Unlock()
if ent := c.evictList.Back(); ent != nil {
return ent.Value.(*cacheItem[K, V]).key, ent.Value.(*cacheItem[K, V]).value, true
}
return
}
// DeleteExpired clears cache of expired items
func (c *cacheImpl[K, V]) DeleteExpired() {
c.Lock()
defer c.Unlock()
for _, key := range c.keys() {
if time.Now().After(c.items[key].Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(c.items[key])
}
}
}
// Purge clears the cache completely.
func (c *cacheImpl[K, V]) Purge() {
c.Lock()
defer c.Unlock()
for k, v := range c.items {
delete(c.items, k)
c.stat.Evicted++
if c.onEvicted != nil {
c.onEvicted(k, v.Value.(*cacheItem[K, V]).value)
}
}
c.evictList.Init()
}
// Stat gets the current stats for cache
func (c *cacheImpl[K, V]) Stat() Stats {
c.Lock()
defer c.Unlock()
return c.stat
}
func (c *cacheImpl[K, V]) String() string {
stats := c.Stat()
size := c.Len()
return fmt.Sprintf("Size: %d, Stats: %+v (%0.1f%%)", size, stats, 100*float64(stats.Hits)/float64(stats.Hits+stats.Misses))
}
// Keys returns a slice of the keys in the cache, from oldest to newest. Has to be called with lock!
func (c *cacheImpl[K, V]) keys() []K {
keys := make([]K, 0, len(c.items))
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
keys = append(keys, ent.Value.(*cacheItem[K, V]).key)
}
return keys
}
// removeOldest removes the oldest item from the cache. Has to be called with lock!
func (c *cacheImpl[K, V]) removeOldest() {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
}
}
// removeOldest removes the oldest item from the cache in case it's already expired. Has to be called with lock!
func (c *cacheImpl[K, V]) removeOldestIfExpired() {
ent := c.evictList.Back()
if ent != nil && time.Now().After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}
// removeElement is used to remove a given list element from the cache. Has to be called with lock!
func (c *cacheImpl[K, V]) removeElement(e *list.Element) {
c.evictList.Remove(e)
kv := e.Value.(*cacheItem[K, V])
delete(c.items, kv.key)
c.stat.Evicted++
if c.onEvicted != nil {
c.onEvicted(kv.key, kv.value)
}
}
// cacheItem is used to hold a value in the evictList
type cacheItem[K comparable, V any] struct {
expiresAt time.Time
key K
value V
}

View File

@@ -0,0 +1,36 @@
package cache
import "time"
type options[K comparable, V any] interface {
WithTTL(ttl time.Duration) Cache[K, V]
WithMaxKeys(maxKeys int) Cache[K, V]
WithLRU() Cache[K, V]
WithOnEvicted(fn func(key K, value V)) Cache[K, V]
}
// WithTTL functional option defines TTL for all cache entries.
// By default, it is set to 10 years, sane option for expirable cache might be 5 minutes.
func (c *cacheImpl[K, V]) WithTTL(ttl time.Duration) Cache[K, V] {
c.ttl = ttl
return c
}
// WithMaxKeys functional option defines how many keys to keep.
// By default, it is 0, which means unlimited.
func (c *cacheImpl[K, V]) WithMaxKeys(maxKeys int) Cache[K, V] {
c.maxKeys = maxKeys
return c
}
// WithLRU sets cache to LRU (Least Recently Used) eviction mode.
func (c *cacheImpl[K, V]) WithLRU() Cache[K, V] {
c.isLRU = true
return c
}
// WithOnEvicted defined function which would be called automatically for automatically and manually deleted entries
func (c *cacheImpl[K, V]) WithOnEvicted(fn func(key K, value V)) Cache[K, V] {
c.onEvicted = fn
return c
}