diff --git a/go.mod b/go.mod index 3edc929..dc99f55 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/umputun/updater go 1.22 require ( - github.com/didip/tollbooth/v7 v7.0.1 + github.com/didip/tollbooth/v7 v7.0.2 github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f - github.com/go-chi/chi/v5 v5.0.12 + github.com/go-chi/chi/v5 v5.1.0 github.com/go-pkgz/lgr v0.11.1 github.com/go-pkgz/rest v1.19.0 github.com/go-pkgz/syncs v1.3.2 @@ -18,7 +18,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-pkgz/expirable-cache v1.0.0 // indirect + github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 64efdf1..be7f854 100644 --- a/go.sum +++ b/go.sum @@ -2,15 +2,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4= -github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulVAog= -github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4= +github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi69p4= +github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc= github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw= github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs= -github.com/go-pkgz/expirable-cache v1.0.0 h1:ns5+1hjY8hntGv8bPaQd9Gr7Jyo+Uw5SLyII40aQdtA= -github.com/go-pkgz/expirable-cache v1.0.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs= +github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw= +github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec= github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM= github.com/go-pkgz/lgr v0.11.1/go.mod h1:tgDF4RXQnBfIgJqjgkv0yOeTQ3F1yewWIZkpUhHnAkU= github.com/go-pkgz/rest v1.19.0 h1:FNMi5QX5dDIkuC+/e0r+CWsTuOTwUiWMRSA16Ou+9+A= @@ -22,6 +22,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/vendor/github.com/didip/tollbooth/v7/.golangci.yml b/vendor/github.com/didip/tollbooth/v7/.golangci.yml index 880786a..5d0a4b6 100644 --- a/vendor/github.com/didip/tollbooth/v7/.golangci.yml +++ b/vendor/github.com/didip/tollbooth/v7/.golangci.yml @@ -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 diff --git a/vendor/github.com/didip/tollbooth/v7/README.md b/vendor/github.com/didip/tollbooth/v7/README.md index 8a45bd8..f96ac2f 100644 --- a/vendor/github.com/didip/tollbooth/v7/README.md +++ b/vendor/github.com/didip/tollbooth/v7/README.md @@ -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. diff --git a/vendor/github.com/didip/tollbooth/v7/limiter/limiter.go b/vendor/github.com/didip/tollbooth/v7/limiter/limiter.go index c64a7f2..0153dc8 100644 --- a/vendor/github.com/didip/tollbooth/v7/limiter/limiter.go +++ b/vendor/github.com/didip/tollbooth/v7/limiter/limiter.go @@ -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())) } diff --git a/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md b/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md index c0ac2df..b4a6268 100644 --- a/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md +++ b/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md @@ -14,7 +14,7 @@ A typical workflow is: -1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] +1. [Fork the repository.][fork] 2. [Create a topic branch.][branch] 3. Add tests for your change. 4. Run `go test`. If your tests pass, return to the step 3. @@ -24,8 +24,8 @@ A typical workflow is: 8. [Submit a pull request.][pull-req] [go-install]: https://golang.org/doc/install -[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html -[fork]: https://help.github.com/articles/fork-a-repo -[branch]: http://learn.github.com/p/branching.html -[git-help]: https://guides.github.com -[pull-req]: https://help.github.com/articles/using-pull-requests +[fork]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo +[branch]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches +[git-help]: https://docs.github.com/en +[pull-req]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests + diff --git a/vendor/github.com/go-chi/chi/v5/context.go b/vendor/github.com/go-chi/chi/v5/context.go index 82e5f28..e2cd908 100644 --- a/vendor/github.com/go-chi/chi/v5/context.go +++ b/vendor/github.com/go-chi/chi/v5/context.go @@ -74,9 +74,8 @@ type Context struct { // patterns across a stack of sub-routers. RoutePatterns []string - // methodNotAllowed hint - methodNotAllowed bool methodsAllowed []methodTyp // allowed methods in case of a 405 + methodNotAllowed bool } // Reset a routing context to its initial state. diff --git a/vendor/github.com/go-pkgz/expirable-cache/.gitignore b/vendor/github.com/go-pkgz/expirable-cache/.gitignore deleted file mode 100644 index 66fd13c..0000000 --- a/vendor/github.com/go-pkgz/expirable-cache/.gitignore +++ /dev/null @@ -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/ diff --git a/vendor/github.com/go-pkgz/expirable-cache/.golangci.yml b/vendor/github.com/go-pkgz/expirable-cache/.golangci.yml deleted file mode 100644 index 870d3a5..0000000 --- a/vendor/github.com/go-pkgz/expirable-cache/.golangci.yml +++ /dev/null @@ -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 diff --git a/vendor/github.com/go-pkgz/expirable-cache/README.md b/vendor/github.com/go-pkgz/expirable-cache/README.md deleted file mode 100644 index 536e586..0000000 --- a/vendor/github.com/go-pkgz/expirable-cache/README.md +++ /dev/null @@ -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: - // Size: 1, Stats: {Hits:1 Misses:1 Added:2 Evicted:1} (50.0%) -} -``` \ No newline at end of file diff --git a/vendor/github.com/go-pkgz/expirable-cache/cache.go b/vendor/github.com/go-pkgz/expirable-cache/cache.go deleted file mode 100644 index 5966a8b..0000000 --- a/vendor/github.com/go-pkgz/expirable-cache/cache.go +++ /dev/null @@ -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{} -} diff --git a/vendor/github.com/go-pkgz/expirable-cache/options.go b/vendor/github.com/go-pkgz/expirable-cache/options.go deleted file mode 100644 index 924345a..0000000 --- a/vendor/github.com/go-pkgz/expirable-cache/options.go +++ /dev/null @@ -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 - } -} diff --git a/vendor/github.com/go-pkgz/expirable-cache/LICENSE b/vendor/github.com/go-pkgz/expirable-cache/v3/LICENSE similarity index 100% rename from vendor/github.com/go-pkgz/expirable-cache/LICENSE rename to vendor/github.com/go-pkgz/expirable-cache/v3/LICENSE diff --git a/vendor/github.com/go-pkgz/expirable-cache/v3/cache.go b/vendor/github.com/go-pkgz/expirable-cache/v3/cache.go new file mode 100644 index 0000000..5b4642d --- /dev/null +++ b/vendor/github.com/go-pkgz/expirable-cache/v3/cache.go @@ -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 +} diff --git a/vendor/github.com/go-pkgz/expirable-cache/v3/options.go b/vendor/github.com/go-pkgz/expirable-cache/v3/options.go new file mode 100644 index 0000000..b5fe65d --- /dev/null +++ b/vendor/github.com/go-pkgz/expirable-cache/v3/options.go @@ -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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ef08d09..9b8a3fc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,8 +1,8 @@ # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/didip/tollbooth/v7 v7.0.1 -## explicit; go 1.12 +# github.com/didip/tollbooth/v7 v7.0.2 +## explicit; go 1.19 github.com/didip/tollbooth/v7 github.com/didip/tollbooth/v7/errors github.com/didip/tollbooth/v7/internal/time/rate @@ -11,12 +11,12 @@ github.com/didip/tollbooth/v7/limiter # github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f ## explicit; go 1.14 github.com/didip/tollbooth_chi -# github.com/go-chi/chi/v5 v5.0.12 +# github.com/go-chi/chi/v5 v5.1.0 ## explicit; go 1.14 github.com/go-chi/chi/v5 -# github.com/go-pkgz/expirable-cache v1.0.0 -## explicit; go 1.14 -github.com/go-pkgz/expirable-cache +# github.com/go-pkgz/expirable-cache/v3 v3.0.0 +## explicit; go 1.20 +github.com/go-pkgz/expirable-cache/v3 # github.com/go-pkgz/lgr v0.11.1 ## explicit; go 1.20 github.com/go-pkgz/lgr