From 1313dee829ff781f20c6c5614473e2534a88bd30 Mon Sep 17 00:00:00 2001 From: Dmitry Verkhoturov Date: Tue, 20 Feb 2024 20:27:50 +0100 Subject: [PATCH] update to lcw v2 with generic types --- backend/app/cmd/server.go | 26 +- backend/app/cmd/server_test.go | 7 +- backend/app/main_test.go | 2 + backend/app/rest/api/admin.go | 2 +- backend/app/rest/api/admin_test.go | 4 +- backend/app/rest/api/migrator.go | 2 +- backend/app/rest/api/rest.go | 2 +- backend/app/rest/api/rest_private.go | 2 +- backend/app/rest/api/rest_public.go | 2 +- backend/app/rest/api/rest_public_test.go | 2 +- backend/app/rest/api/rest_test.go | 10 +- backend/app/rest/api/rss.go | 2 +- backend/app/store/service/service.go | 9 +- backend/app/store/service/title.go | 21 +- backend/go.mod | 4 +- backend/go.sum | 18 +- .../vendor/github.com/go-pkgz/lcw/.gitignore | 12 - .../github.com/go-pkgz/lcw/.golangci.yml | 50 --- .../vendor/github.com/go-pkgz/lcw/README.md | 86 ----- .../go-pkgz/lcw/internal/cache/cache.go | 247 ------------- .../go-pkgz/lcw/internal/cache/options.go | 42 --- .../github.com/go-pkgz/lcw/{ => v2}/LICENSE | 0 .../github.com/go-pkgz/lcw/{ => v2}/cache.go | 40 +-- .../go-pkgz/lcw/{ => v2}/eventbus/pubsub.go | 0 .../go-pkgz/lcw/{ => v2}/eventbus/redis.go | 4 +- .../go-pkgz/lcw/{ => v2}/expirable_cache.go | 106 +++--- .../go-pkgz/lcw/{ => v2}/lru_cache.go | 70 ++-- .../go-pkgz/lcw/{ => v2}/options.go | 57 +-- .../go-pkgz/lcw/{ => v2}/redis_cache.go | 87 +++-- .../github.com/go-pkgz/lcw/{ => v2}/scache.go | 20 +- .../github.com/go-pkgz/lcw/{ => v2}/url.go | 23 +- .../hashicorp/golang-lru/.golangci.yml | 30 -- .../github.com/hashicorp/golang-lru/README.md | 7 - .../github.com/hashicorp/golang-lru/arc.go | 256 ------------- .../github.com/hashicorp/golang-lru/doc.go | 21 -- .../hashicorp/golang-lru/testing.go | 16 - .../hashicorp/golang-lru/{ => v2}/.gitignore | 0 .../hashicorp/golang-lru/v2/.golangci.yml | 46 +++ .../hashicorp/golang-lru/{ => v2}/2q.go | 103 ++++-- .../hashicorp/golang-lru/{ => v2}/LICENSE | 0 .../hashicorp/golang-lru/v2/README.md | 79 ++++ .../github.com/hashicorp/golang-lru/v2/doc.go | 24 ++ .../golang-lru/v2/expirable/expirable_lru.go | 338 ++++++++++++++++++ .../hashicorp/golang-lru/v2/internal/list.go | 142 ++++++++ .../hashicorp/golang-lru/{ => v2}/lru.go | 89 +++-- .../golang-lru/v2/simplelru/LICENSE_list | 29 ++ .../golang-lru/{ => v2}/simplelru/lru.go | 122 +++---- .../{ => v2}/simplelru/lru_interface.go | 24 +- backend/vendor/modules.txt | 17 +- 49 files changed, 1162 insertions(+), 1140 deletions(-) delete mode 100644 backend/vendor/github.com/go-pkgz/lcw/.gitignore delete mode 100644 backend/vendor/github.com/go-pkgz/lcw/.golangci.yml delete mode 100644 backend/vendor/github.com/go-pkgz/lcw/README.md delete mode 100644 backend/vendor/github.com/go-pkgz/lcw/internal/cache/cache.go delete mode 100644 backend/vendor/github.com/go-pkgz/lcw/internal/cache/options.go rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/LICENSE (100%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/cache.go (58%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/eventbus/pubsub.go (100%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/eventbus/redis.go (100%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/expirable_cache.go (51%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/lru_cache.go (65%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/options.go (50%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/redis_cache.go (56%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/scache.go (88%) rename backend/vendor/github.com/go-pkgz/lcw/{ => v2}/url.go (85%) delete mode 100644 backend/vendor/github.com/hashicorp/golang-lru/.golangci.yml delete mode 100644 backend/vendor/github.com/hashicorp/golang-lru/README.md delete mode 100644 backend/vendor/github.com/hashicorp/golang-lru/arc.go delete mode 100644 backend/vendor/github.com/hashicorp/golang-lru/doc.go delete mode 100644 backend/vendor/github.com/hashicorp/golang-lru/testing.go rename backend/vendor/github.com/hashicorp/golang-lru/{ => v2}/.gitignore (100%) create mode 100644 backend/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml rename backend/vendor/github.com/hashicorp/golang-lru/{ => v2}/2q.go (64%) rename backend/vendor/github.com/hashicorp/golang-lru/{ => v2}/LICENSE (100%) create mode 100644 backend/vendor/github.com/hashicorp/golang-lru/v2/README.md create mode 100644 backend/vendor/github.com/hashicorp/golang-lru/v2/doc.go create mode 100644 backend/vendor/github.com/hashicorp/golang-lru/v2/expirable/expirable_lru.go create mode 100644 backend/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go rename backend/vendor/github.com/hashicorp/golang-lru/{ => v2}/lru.go (72%) create mode 100644 backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list rename backend/vendor/github.com/hashicorp/golang-lru/{ => v2}/simplelru/lru.go (50%) rename backend/vendor/github.com/hashicorp/golang-lru/{ => v2}/simplelru/lru_interface.go (68%) diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 84b27cb1..5936dda2 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -16,7 +16,7 @@ import ( "time" "github.com/go-pkgz/jrpc" - "github.com/go-pkgz/lcw/eventbus" + "github.com/go-pkgz/lcw/v2/eventbus" log "github.com/go-pkgz/lgr" ntf "github.com/go-pkgz/notify" "github.com/golang-jwt/jwt" @@ -28,7 +28,7 @@ import ( "github.com/go-pkgz/auth/provider" "github.com/go-pkgz/auth/provider/sender" "github.com/go-pkgz/auth/token" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" "github.com/umputun/remark42/backend/app/migrator" "github.com/umputun/remark42/backend/app/notify" @@ -869,27 +869,28 @@ func (s *ServerCommand) makeAdminStore() (admin.Store, error) { func (s *ServerCommand) makeCache() (LoadingCache, error) { log.Printf("[INFO] make cache, type=%s", s.Cache.Type) + o := cache.NewOpts[[]byte]() switch s.Cache.Type { case "redis_pub_sub": redisPubSub, err := eventbus.NewRedisPubSub(s.Cache.RedisAddr, "remark42-cache") if err != nil { return nil, fmt.Errorf("cache backend initialization, redis PubSub initialisation: %w", err) } - backend, err := cache.NewLruCache(cache.MaxCacheSize(s.Cache.Max.Size), cache.MaxValSize(s.Cache.Max.Value), - cache.MaxKeys(s.Cache.Max.Items), cache.EventBus(redisPubSub)) + backend, err := cache.NewLruCache(o.MaxCacheSize(s.Cache.Max.Size), o.MaxValSize(s.Cache.Max.Value), + o.MaxKeys(s.Cache.Max.Items), o.EventBus(redisPubSub)) if err != nil { return nil, fmt.Errorf("cache backend initialization: %w", err) } - return cache.NewScache(backend), nil + return cache.NewScache[[]byte](backend), nil case "mem": - backend, err := cache.NewLruCache(cache.MaxCacheSize(s.Cache.Max.Size), cache.MaxValSize(s.Cache.Max.Value), - cache.MaxKeys(s.Cache.Max.Items)) + backend, err := cache.NewLruCache(o.MaxCacheSize(s.Cache.Max.Size), o.MaxValSize(s.Cache.Max.Value), + o.MaxKeys(s.Cache.Max.Items)) if err != nil { return nil, fmt.Errorf("cache backend initialization: %w", err) } - return cache.NewScache(backend), nil + return cache.NewScache[[]byte](backend), nil case "none": - return cache.NewScache(&cache.Nop{}), nil + return cache.NewScache[[]byte](&cache.Nop[[]byte]{}), nil } return nil, fmt.Errorf("unsupported cache type %s", s.Cache.Type) } @@ -1326,11 +1327,12 @@ func splitAtCommas(s string) []string { // authRefreshCache used by authenticator to minimize repeatable token refreshes type authRefreshCache struct { - cache.LoadingCache + cache.LoadingCache[string] } func newAuthRefreshCache() *authRefreshCache { - expirableCache, _ := cache.NewExpirableCache(cache.TTL(5 * time.Minute)) + o := cache.NewOpts[string]() + expirableCache, _ := cache.NewExpirableCache(o.TTL(5 * time.Minute)) return &authRefreshCache{LoadingCache: expirableCache} } @@ -1341,5 +1343,5 @@ func (c *authRefreshCache) Get(key interface{}) (interface{}, bool) { // Set implements cache setter with key converted to string func (c *authRefreshCache) Set(key, value interface{}) { - _, _ = c.LoadingCache.Get(key.(string), func() (interface{}, error) { return value, nil }) + _, _ = c.LoadingCache.Get(key.(string), func() (string, error) { return value.(string), nil }) } diff --git a/backend/app/cmd/server_test.go b/backend/app/cmd/server_test.go index c46ada75..170e2d62 100644 --- a/backend/app/cmd/server_test.go +++ b/backend/app/cmd/server_test.go @@ -848,5 +848,10 @@ func createAppFromCmd(t *testing.T, cmd ServerCommand) (*serverApp, context.Cont func TestMain(m *testing.M) { // ignore is added only for GitHub Actions, can't reproduce locally - goleak.VerifyTestMain(m, goleak.IgnoreTopFunction("net/http.(*Server).Shutdown")) + goleak.VerifyTestMain( + m, + goleak.IgnoreTopFunction("net/http.(*Server).Shutdown"), + // this will be fixed in https://github.com/hashicorp/golang-lru/issues/159 + goleak.IgnoreTopFunction("github.com/hashicorp/golang-lru/v2/expirable.NewLRU[...].func1"), + ) } diff --git a/backend/app/main_test.go b/backend/app/main_test.go index 7ec9f631..2154e744 100644 --- a/backend/app/main_test.go +++ b/backend/app/main_test.go @@ -157,5 +157,7 @@ func TestMain(m *testing.M) { m, goleak.IgnoreTopFunction("github.com/umputun/remark42/backend/app.init.0.func1"), goleak.IgnoreTopFunction("net/http.(*Server).Shutdown"), + // this will be fixed in https://github.com/hashicorp/golang-lru/issues/159 + goleak.IgnoreTopFunction("github.com/hashicorp/golang-lru/v2/expirable.NewLRU[...].func1"), ) } diff --git a/backend/app/rest/api/admin.go b/backend/app/rest/api/admin.go index 98c0a1ce..d6e1f94b 100644 --- a/backend/app/rest/api/admin.go +++ b/backend/app/rest/api/admin.go @@ -9,7 +9,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/go-pkgz/auth" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" diff --git a/backend/app/rest/api/admin_test.go b/backend/app/rest/api/admin_test.go index a4c0283b..4b627980 100644 --- a/backend/app/rest/api/admin_test.go +++ b/backend/app/rest/api/admin_test.go @@ -14,7 +14,7 @@ import ( "time" "github.com/go-pkgz/auth/token" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" R "github.com/go-pkgz/rest" "github.com/golang-jwt/jwt" "github.com/stretchr/testify/assert" @@ -348,7 +348,7 @@ func TestAdmin_Block(t *testing.T) { assert.Equal(t, "test test #1", comments.Comments[2].Text, "comment not removed and not cleared") assert.False(t, comments.Comments[2].Deleted, "not deleted") - srv.pubRest.cache = cache.NewScache(cache.NewNopCache()) // TODO: with lru cache it won't be refreshed and invalidated for long + srv.pubRest.cache = cache.NewScache[[]byte](cache.NewNopCache[[]byte]()) // TODO: with lru cache it won't be refreshed and invalidated for long // time time.Sleep(50 * time.Millisecond) res, code = get(t, ts.URL+"/api/v1/find?site=remark42&url=https://radio-t.com/blah&sort=+time") diff --git a/backend/app/rest/api/migrator.go b/backend/app/rest/api/migrator.go index 4a38c2a9..00e43fa0 100644 --- a/backend/app/rest/api/migrator.go +++ b/backend/app/rest/api/migrator.go @@ -11,7 +11,7 @@ import ( "time" "github.com/go-chi/render" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 2e667ab0..a2e07ce7 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -22,7 +22,7 @@ import ( "github.com/go-chi/cors" "github.com/go-chi/render" "github.com/go-pkgz/auth" - "github.com/go-pkgz/lcw" + "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" "github.com/go-pkgz/rest/logger" diff --git a/backend/app/rest/api/rest_private.go b/backend/app/rest/api/rest_private.go index 493d8064..d723a1a2 100644 --- a/backend/app/rest/api/rest_private.go +++ b/backend/app/rest/api/rest_private.go @@ -18,7 +18,7 @@ import ( "github.com/go-chi/render" "github.com/go-pkgz/auth" "github.com/go-pkgz/auth/token" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" "github.com/golang-jwt/jwt" diff --git a/backend/app/rest/api/rest_public.go b/backend/app/rest/api/rest_public.go index 6e122dea..4d7230af 100644 --- a/backend/app/rest/api/rest_public.go +++ b/backend/app/rest/api/rest_public.go @@ -13,7 +13,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" "github.com/skip2/go-qrcode" diff --git a/backend/app/rest/api/rest_public_test.go b/backend/app/rest/api/rest_public_test.go index 8a0cf9a8..8d3db1ae 100644 --- a/backend/app/rest/api/rest_public_test.go +++ b/backend/app/rest/api/rest_public_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" R "github.com/go-pkgz/rest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/backend/app/rest/api/rest_test.go b/backend/app/rest/api/rest_test.go index c9900580..430d947f 100644 --- a/backend/app/rest/api/rest_test.go +++ b/backend/app/rest/api/rest_test.go @@ -20,7 +20,7 @@ import ( "github.com/go-pkgz/auth/avatar" "github.com/go-pkgz/auth/provider" "github.com/go-pkgz/auth/token" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" R "github.com/go-pkgz/rest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -438,7 +438,7 @@ func startupT(t *testing.T, srvHook ...func(srv *Rest)) (ts *httptest.Server, sr b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: testDB, SiteID: "remark42"}) require.NoError(t, err) - memCache := cache.NewScache(cache.NewNopCache()) + memCache := cache.NewScache[[]byte](cache.NewNopCache[[]byte]()) astore := adminstore.NewStaticStore("123456", []string{"remark42"}, []string{"a1", "a2"}, "admin@remark-42.com") restrictedWordsMatcher := service.NewRestrictedWordsMatcher(service.StaticRestrictedWordsLister{Words: []string{"duck"}}) @@ -658,5 +658,9 @@ func waitForHTTPSServerStart(port int) { } func TestMain(m *testing.M) { - goleak.VerifyTestMain(m) + goleak.VerifyTestMain( + m, + // this will be fixed in https://github.com/hashicorp/golang-lru/issues/159 + goleak.IgnoreTopFunction("github.com/hashicorp/golang-lru/v2/expirable.NewLRU[...].func1"), + ) } diff --git a/backend/app/rest/api/rss.go b/backend/app/rest/api/rss.go index da6617d6..82baf8f2 100644 --- a/backend/app/rest/api/rss.go +++ b/backend/app/rest/api/rss.go @@ -5,7 +5,7 @@ import ( "net/http" "time" - cache "github.com/go-pkgz/lcw" + cache "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" "github.com/gorilla/feeds" diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index d4dfb8ab..5f4a6dba 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -11,7 +11,7 @@ import ( "sync" "time" - "github.com/go-pkgz/lcw" + "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" "github.com/google/uuid" "github.com/hashicorp/go-multierror" @@ -49,7 +49,7 @@ type DataStore struct { } repliesCache struct { - lcw.LoadingCache + lcw.LoadingCache[struct{}] once sync.Once } } @@ -534,7 +534,8 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi func (s *DataStore) HasReplies(comment store.Comment) bool { s.repliesCache.once.Do(func() { // default expiration time of 5 minutes and cleanup time of 2.5 minutes - s.repliesCache.LoadingCache, _ = lcw.NewExpirableCache(lcw.TTL(5 * time.Minute)) + o := lcw.NewOpts[struct{}]() + s.repliesCache.LoadingCache, _ = lcw.NewExpirableCache[struct{}](o.TTL(5 * time.Minute)) }) if _, found := s.repliesCache.Peek(comment.ID); found { @@ -554,7 +555,7 @@ func (s *DataStore) HasReplies(comment store.Comment) bool { // When this code is reached, key "comment.ID" is not in cache. // Calling cache.Get on it will put it in cache with 5 minutes TTL. // We call it with empty struct as value as we care about keys and not values. - _, _ = s.repliesCache.Get(comment.ID, func() (interface{}, error) { return struct{}{}, nil }) + _, _ = s.repliesCache.Get(comment.ID, func() (struct{}, error) { return struct{}{}, nil }) return true } } diff --git a/backend/app/store/service/title.go b/backend/app/store/service/title.go index bc5e61b3..9ee497a9 100644 --- a/backend/app/store/service/title.go +++ b/backend/app/store/service/title.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/go-pkgz/lcw" + "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" "golang.org/x/net/html" ) @@ -21,7 +21,7 @@ const ( // TitleExtractor gets html title from remote page, cached type TitleExtractor struct { client http.Client - cache lcw.LoadingCache + cache lcw.LoadingCache[string] allowedDomains []string } @@ -33,10 +33,11 @@ func NewTitleExtractor(client http.Client, allowedDomains []string) *TitleExtrac allowedDomains: allowedDomains, } var err error - res.cache, err = lcw.NewExpirableCache(lcw.TTL(teCacheTTL), lcw.MaxKeySize(teCacheMaxRecs)) + o := lcw.NewOpts[string]() + res.cache, err = lcw.NewExpirableCache(o.TTL(teCacheTTL), o.MaxKeySize(teCacheMaxRecs)) if err != nil { log.Printf("[WARN] failed to make cache, caching disabled for titles, %v", err) - res.cache = &lcw.Nop{} + res.cache = &lcw.Nop[string]{} } return &res } @@ -62,10 +63,10 @@ func (t *TitleExtractor) Get(pageURL string) (string, error) { } client := http.Client{Timeout: t.client.Timeout, Transport: t.client.Transport} defer client.CloseIdleConnections() - b, err := t.cache.Get(pageURL, func() (interface{}, error) { + b, err := t.cache.Get(pageURL, func() (string, error) { resp, e := client.Get(pageURL) if e != nil { - return nil, fmt.Errorf("failed to load page %s: %w", pageURL, e) + return "", fmt.Errorf("failed to load page %s: %w", pageURL, e) } defer func() { if err = resp.Body.Close(); err != nil { @@ -73,23 +74,23 @@ func (t *TitleExtractor) Get(pageURL string) (string, error) { } }() if resp.StatusCode != 200 { - return nil, fmt.Errorf("can't load page %s, code %d", pageURL, resp.StatusCode) + return "", fmt.Errorf("can't load page %s, code %d", pageURL, resp.StatusCode) } title, ok := t.getTitle(resp.Body) if !ok { - return nil, fmt.Errorf("can't get title for %s", pageURL) + return "", fmt.Errorf("can't get title for %s", pageURL) } return title, nil }) // on error save result (empty string) to cache too and return "" title if err != nil { - _, _ = t.cache.Get(pageURL, func() (interface{}, error) { return "", nil }) + _, _ = t.cache.Get(pageURL, func() (string, error) { return "", nil }) return "", err } - return b.(string), nil + return b, nil } // Close title extractor diff --git a/backend/go.mod b/backend/go.mod index e7fc3572..9d04a58a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-chi/render v1.0.3 github.com/go-pkgz/auth v1.22.2-0.20240117071454-f721b8c33b05 github.com/go-pkgz/jrpc v0.3.0 - github.com/go-pkgz/lcw v1.1.0 + github.com/go-pkgz/lcw/v2 v2.0.0 github.com/go-pkgz/lgr v0.11.1 github.com/go-pkgz/notify v1.1.0 github.com/go-pkgz/repeater v1.1.3 @@ -56,7 +56,7 @@ require ( github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index f8607385..e4fbd4ad 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -15,8 +15,8 @@ github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6TTnfX8Y= github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -72,8 +72,8 @@ github.com/go-pkgz/expirable-cache v1.0.0 h1:ns5+1hjY8hntGv8bPaQd9Gr7Jyo+Uw5SLyI github.com/go-pkgz/expirable-cache v1.0.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs= github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U= github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00= -github.com/go-pkgz/lcw v1.1.0 h1:hDJdQJZf4iw19a7cTgQvF6Poz/L7mL4E7FgG5jGs4lA= -github.com/go-pkgz/lcw v1.1.0/go.mod h1:zwT7RSxFskQsHWHJezYq6n0iTn0n4yIYRDijXq3awM0= +github.com/go-pkgz/lcw/v2 v2.0.0 h1:gTwXpiJBhQeA1rXuqkRuLcV79uATFna8CckH8ZBBrH0= +github.com/go-pkgz/lcw/v2 v2.0.0/go.mod h1:yxJHOn+IbQBQHxUqkCtMrbGjIfdYcsBAZcVCBaL1Va8= 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/notify v1.1.0 h1:xdMKoY7W5t9lewGzn61yM6Z6oWafPiPS1WtAMO81p2k= @@ -132,8 +132,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 v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -189,8 +189,6 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ= -github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/slack-go/slack v0.12.4 h1:4iLT2opw+/QptmQxBNA7S8pNfSIvtn0NDGu7Jq0emi4= github.com/slack-go/slack v0.12.4/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -253,8 +251,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= -github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= diff --git a/backend/vendor/github.com/go-pkgz/lcw/.gitignore b/backend/vendor/github.com/go-pkgz/lcw/.gitignore deleted file mode 100644 index f1c181ec..00000000 --- a/backend/vendor/github.com/go-pkgz/lcw/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out diff --git a/backend/vendor/github.com/go-pkgz/lcw/.golangci.yml b/backend/vendor/github.com/go-pkgz/lcw/.golangci.yml deleted file mode 100644 index 00588ced..00000000 --- a/backend/vendor/github.com/go-pkgz/lcw/.golangci.yml +++ /dev/null @@ -1,50 +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 - - gas - - gocyclo - - dupl - - misspell - - unparam - - typecheck - - ineffassign - - stylecheck - - gochecknoinits - - exportloopref - - gocritic - - nakedret - - gosimple - - prealloc - fast: false - disable-all: true - -run: - output: - format: tab - skip-dirs: - - vendor diff --git a/backend/vendor/github.com/go-pkgz/lcw/README.md b/backend/vendor/github.com/go-pkgz/lcw/README.md deleted file mode 100644 index 6a553a10..00000000 --- a/backend/vendor/github.com/go-pkgz/lcw/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# 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) - -The library adds a thin layer on top of [lru cache](https://github.com/hashicorp/golang-lru) and internal implementation -of expirable cache. - -| Cache name | Constructor | Defaults | Description | -|----------------|-----------------------|-------------------|-------------------------| -| LruCache | lcw.NewLruCache | keys=1000 | LRU cache with limits | -| ExpirableCache | lcw.NewExpirableCache | keys=1000, ttl=5m | TTL cache with limits | -| RedisCache | lcw.NewRedisCache | ttl=5m | Redis cache with limits | -| Nop | lcw.NewNopCache | | Do-nothing cache | - -Main features: - -- LoadingCache (guava style) -- Limit maximum cache size (in bytes) -- Limit maximum key size -- Limit maximum size of a value -- Limit number of keys -- TTL support (`ExpirableCache` and `RedisCache`) -- Callback on eviction event (not supported in `RedisCache`) -- Functional style invalidation -- Functional options -- Sane defaults - -## Install and update - -`go get -u github.com/go-pkgz/lcw` - -## Usage - -```go -package main - -import ( - "github.com/go-pkgz/lcw" -) - -func main() { - cache, err := lcw.NewLruCache(lcw.MaxKeys(500), lcw.MaxCacheSize(65536), lcw.MaxValSize(200), lcw.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 - return res, err - }) - - if err != nil { - panic("failed to get data") - } - - s := val.(string) // cached value -} -``` - -### Cache with URI - -Cache can be created with URIs: - -- `mem://lru?max_key_size=10&max_val_size=1024&max_keys=50&max_cache_size=64000` - creates LRU cache with given limits -- `mem://expirable?ttl=30s&max_key_size=10&max_val_size=1024&max_keys=50&max_cache_size=64000` - create expirable cache -- `redis://10.0.0.1:1234?db=16&password=qwerty&network=tcp4&dial_timeout=1s&read_timeout=5s&write_timeout=3s` - create - redis cache -- `nop://` - create Nop cache - -## Scoped cache - -`Scache` provides a wrapper on top of all implementations of `LoadingCache` with a number of special features: - -1. Key is not a string, but a composed type made from partition, key-id and list of scopes (tags). -1. Value type limited to `[]byte` -1. Added `Flush` method for scoped/tagged invalidation of multiple records in a given partition -1. A simplified interface with Get, Stat, Flush and Close only. - -## Details - -- In all cache types other than Redis (e.g. LRU and Expirable at the moment) values are stored as-is which means - that mutable values can be changed outside of cache. `ExampleLoadingCache_Mutability` illustrates that. -- All byte-size limits (MaxCacheSize and MaxValSize) only work for values implementing `lcw.Sizer` interface. -- Negative limits (max options) rejected -- The implementation started as a part of [remark42](https://github.com/umputun/remark) - and later on moved to [go-pkgz/rest](https://github.com/go-pkgz/rest/tree/master/cache) - library and finally generalized to become `lcw`. diff --git a/backend/vendor/github.com/go-pkgz/lcw/internal/cache/cache.go b/backend/vendor/github.com/go-pkgz/lcw/internal/cache/cache.go deleted file mode 100644 index b1d386e3..00000000 --- a/backend/vendor/github.com/go-pkgz/lcw/internal/cache/cache.go +++ /dev/null @@ -1,247 +0,0 @@ -// Package cache implements LoadingCache. -// -// Support LRC TTL-based eviction. -package cache - -import ( - "fmt" - "sort" - "sync" - "time" -) - -// LoadingCache provides expirable loading cache with LRC eviction. -type LoadingCache struct { - purgeEvery time.Duration - ttl time.Duration - maxKeys int64 - done chan struct{} - onEvicted func(key string, value interface{}) - - mu sync.Mutex - data map[string]*cacheItem -} - -// noEvictionTTL - very long ttl to prevent eviction -const noEvictionTTL = time.Hour * 24 * 365 * 10 - -// NewLoadingCache returns a new expirable LRC cache, activates purge with purgeEvery (0 to never purge). -// Default MaxKeys is unlimited (0). -func NewLoadingCache(options ...Option) (*LoadingCache, error) { - res := LoadingCache{ - data: map[string]*cacheItem{}, - ttl: noEvictionTTL, - purgeEvery: 0, - maxKeys: 0, - done: make(chan struct{}), - } - - for _, opt := range options { - if err := opt(&res); err != nil { - return nil, fmt.Errorf("failed to set cache option: %w", err) - } - } - - if res.maxKeys > 0 || res.purgeEvery > 0 { - if res.purgeEvery == 0 { - res.purgeEvery = time.Minute * 5 // non-zero purge enforced because maxKeys defined - } - go func(done <-chan struct{}) { - ticker := time.NewTicker(res.purgeEvery) - for { - select { - case <-done: - return - case <-ticker.C: - res.mu.Lock() - res.purge(res.maxKeys) - res.mu.Unlock() - } - } - }(res.done) - } - return &res, nil -} - -// Set key -func (c *LoadingCache) Set(key string, value interface{}) { - c.mu.Lock() - defer c.mu.Unlock() - - now := time.Now() - if _, ok := c.data[key]; !ok { - c.data[key] = &cacheItem{} - } - c.data[key].data = value - c.data[key].expiresAt = now.Add(c.ttl) - - // Enforced purge call in addition the one from the ticker - // to limit the worst-case scenario with a lot of sets in the - // short period of time (between two timed purge calls) - if c.maxKeys > 0 && int64(len(c.data)) >= c.maxKeys*2 { - c.purge(c.maxKeys) - } -} - -// Get returns the key value -func (c *LoadingCache) Get(key string) (interface{}, bool) { - c.mu.Lock() - defer c.mu.Unlock() - value, ok := c.getValue(key) - if !ok { - return nil, false - } - return value, ok -} - -// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key. -func (c *LoadingCache) Peek(key string) (interface{}, bool) { - c.mu.Lock() - defer c.mu.Unlock() - value, ok := c.getValue(key) - if !ok { - return nil, false - } - return value, ok -} - -// Invalidate key (item) from the cache -func (c *LoadingCache) Invalidate(key string) { - c.mu.Lock() - if value, ok := c.data[key]; ok { - delete(c.data, key) - if c.onEvicted != nil { - c.onEvicted(key, value.data) - } - } - c.mu.Unlock() -} - -// InvalidateFn deletes multiple keys if predicate is true -func (c *LoadingCache) InvalidateFn(fn func(key string) bool) { - c.mu.Lock() - for key, value := range c.data { - if fn(key) { - delete(c.data, key) - if c.onEvicted != nil { - c.onEvicted(key, value.data) - } - } - } - c.mu.Unlock() -} - -// Keys return slice of current keys in the cache -func (c *LoadingCache) Keys() []string { - c.mu.Lock() - defer c.mu.Unlock() - keys := make([]string, 0, len(c.data)) - for k := range c.data { - keys = append(keys, k) - } - return keys -} - -// get value respecting the expiration, should be called with lock -func (c *LoadingCache) getValue(key string) (interface{}, bool) { - value, ok := c.data[key] - if !ok { - return nil, false - } - if time.Now().After(c.data[key].expiresAt) { - return nil, false - } - return value.data, ok -} - -// Purge clears the cache completely. -func (c *LoadingCache) Purge() { - c.mu.Lock() - defer c.mu.Unlock() - for k, v := range c.data { - delete(c.data, k) - if c.onEvicted != nil { - c.onEvicted(k, v.data) - } - } -} - -// DeleteExpired clears cache of expired items -func (c *LoadingCache) DeleteExpired() { - c.mu.Lock() - defer c.mu.Unlock() - c.purge(0) -} - -// ItemCount return count of items in cache -func (c *LoadingCache) ItemCount() int { - c.mu.Lock() - n := len(c.data) - c.mu.Unlock() - return n -} - -// Close cleans the cache and destroys running goroutines -func (c *LoadingCache) Close() { - c.mu.Lock() - defer c.mu.Unlock() - // don't panic in case service is already closed - select { - case <-c.done: - return - default: - } - close(c.done) -} - -// keysWithTS includes list of keys with ts. This is for sorting keys -// in order to provide least recently added sorting for size-based eviction -type keysWithTS []struct { - key string - ts time.Time -} - -// purge records > maxKeys. Has to be called with lock! -// call with maxKeys 0 will only clear expired entries. -func (c *LoadingCache) purge(maxKeys int64) { - kts := keysWithTS{} - - for key, value := range c.data { - // ttl eviction - if time.Now().After(c.data[key].expiresAt) { - delete(c.data, key) - if c.onEvicted != nil { - c.onEvicted(key, value.data) - } - } - - // prepare list of keysWithTS for size eviction - if maxKeys > 0 && int64(len(c.data)) > maxKeys { - ts := c.data[key].expiresAt - - kts = append(kts, struct { - key string - ts time.Time - }{key, ts}) - } - } - - // size eviction - size := int64(len(c.data)) - if len(kts) > 0 { - sort.Slice(kts, func(i int, j int) bool { return kts[i].ts.Before(kts[j].ts) }) - for d := 0; int64(d) < size-maxKeys; d++ { - key := kts[d].key - value := c.data[key].data - delete(c.data, key) - if c.onEvicted != nil { - c.onEvicted(key, value) - } - } - } -} - -type cacheItem struct { - expiresAt time.Time - data interface{} -} diff --git a/backend/vendor/github.com/go-pkgz/lcw/internal/cache/options.go b/backend/vendor/github.com/go-pkgz/lcw/internal/cache/options.go deleted file mode 100644 index f1262e9d..00000000 --- a/backend/vendor/github.com/go-pkgz/lcw/internal/cache/options.go +++ /dev/null @@ -1,42 +0,0 @@ -package cache - -import "time" - -// Option func type -type Option func(lc *LoadingCache) error - -// OnEvicted called automatically for expired and manually deleted entries -func OnEvicted(fn func(key string, value interface{})) Option { - return func(lc *LoadingCache) error { - lc.onEvicted = fn - return nil - } -} - -// PurgeEvery functional option defines purge interval -// by default it is 0, i.e. never. If MaxKeys set to any non-zero this default will be 5minutes -func PurgeEvery(interval time.Duration) Option { - return func(lc *LoadingCache) error { - lc.purgeEvery = interval - return nil - } -} - -// MaxKeys functional option defines how many keys to keep. -// By default it is 0, which means unlimited. -// If any non-zero MaxKeys set, default PurgeEvery will be set to 5 minutes -func MaxKeys(max int) Option { - return func(lc *LoadingCache) error { - lc.maxKeys = int64(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 *LoadingCache) error { - lc.ttl = ttl - return nil - } -} diff --git a/backend/vendor/github.com/go-pkgz/lcw/LICENSE b/backend/vendor/github.com/go-pkgz/lcw/v2/LICENSE similarity index 100% rename from backend/vendor/github.com/go-pkgz/lcw/LICENSE rename to backend/vendor/github.com/go-pkgz/lcw/v2/LICENSE diff --git a/backend/vendor/github.com/go-pkgz/lcw/cache.go b/backend/vendor/github.com/go-pkgz/lcw/v2/cache.go similarity index 58% rename from backend/vendor/github.com/go-pkgz/lcw/cache.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/cache.go index 65355c88..58e259bb 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/cache.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/cache.go @@ -17,15 +17,15 @@ type Sizer interface { } // LoadingCache defines guava-like cache with Get method returning cached value ao retrieving it if not in cache -type LoadingCache interface { - Get(key string, fn func() (interface{}, error)) (val interface{}, err error) // load or get from cache - Peek(key string) (interface{}, 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 +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 @@ -48,37 +48,37 @@ func (s CacheStat) String() string { } // Nop is do-nothing implementation of LoadingCache -type Nop struct{} +type Nop[V any] struct{} // NewNopCache makes new do-nothing cache -func NewNopCache() *Nop { - return &Nop{} +func NewNopCache[V any]() *Nop[V] { + return &Nop[V]{} } // Get calls fn without any caching -func (n *Nop) Get(_ string, fn func() (interface{}, error)) (interface{}, error) { return fn() } +func (n *Nop[V]) Get(_ string, fn func() (V, error)) (V, error) { return fn() } // Peek does nothing and always returns false -func (n *Nop) Peek(string) (interface{}, bool) { return nil, false } +func (n *Nop[V]) Peek(string) (V, bool) { var emptyValue V; return emptyValue, false } // Invalidate does nothing for nop cache -func (n *Nop) Invalidate(func(key string) bool) {} +func (n *Nop[V]) Invalidate(func(key string) bool) {} // Purge does nothing for nop cache -func (n *Nop) Purge() {} +func (n *Nop[V]) Purge() {} // Delete does nothing for nop cache -func (n *Nop) Delete(string) {} +func (n *Nop[V]) Delete(string) {} // Keys does nothing for nop cache -func (n *Nop) Keys() []string { return nil } +func (n *Nop[V]) Keys() []string { return nil } // Stat always 0s for nop cache -func (n *Nop) Stat() CacheStat { +func (n *Nop[V]) Stat() CacheStat { return CacheStat{} } // Close does nothing for nop cache -func (n *Nop) Close() error { +func (n *Nop[V]) Close() error { return nil } diff --git a/backend/vendor/github.com/go-pkgz/lcw/eventbus/pubsub.go b/backend/vendor/github.com/go-pkgz/lcw/v2/eventbus/pubsub.go similarity index 100% rename from backend/vendor/github.com/go-pkgz/lcw/eventbus/pubsub.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/eventbus/pubsub.go diff --git a/backend/vendor/github.com/go-pkgz/lcw/eventbus/redis.go b/backend/vendor/github.com/go-pkgz/lcw/v2/eventbus/redis.go similarity index 100% rename from backend/vendor/github.com/go-pkgz/lcw/eventbus/redis.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/eventbus/redis.go index c0672873..b97870c8 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/eventbus/redis.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/eventbus/redis.go @@ -6,9 +6,8 @@ import ( "strings" "time" - "github.com/redis/go-redis/v9" - "github.com/hashicorp/go-multierror" + "github.com/redis/go-redis/v9" ) // NewRedisPubSub creates new RedisPubSub with given parameters. @@ -67,6 +66,7 @@ func (m *RedisPubSub) Publish(fromID, key string) error { // Close cleans up running goroutines and closes Redis clients func (m *RedisPubSub) Close() error { close(m.done) + errs := new(multierror.Error) if err := m.pubSub.Close(); err != nil { errs = multierror.Append(errs, fmt.Errorf("problem closing pubSub client: %w", err)) diff --git a/backend/vendor/github.com/go-pkgz/lcw/expirable_cache.go b/backend/vendor/github.com/go-pkgz/lcw/v2/expirable_cache.go similarity index 51% rename from backend/vendor/github.com/go-pkgz/lcw/expirable_cache.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/expirable_cache.go index 513e0c0f..6d64f4a9 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/expirable_cache.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/expirable_cache.go @@ -5,24 +5,25 @@ import ( "sync/atomic" "time" - "github.com/go-pkgz/lcw/eventbus" - "github.com/go-pkgz/lcw/internal/cache" "github.com/google/uuid" + "github.com/hashicorp/golang-lru/v2/expirable" + + "github.com/go-pkgz/lcw/v2/eventbus" ) // ExpirableCache implements LoadingCache with TTL. -type ExpirableCache struct { - options +type ExpirableCache[V any] struct { + Workers[V] CacheStat currentSize int64 id string - backend *cache.LoadingCache + backend *expirable.LRU[string, V] } // NewExpirableCache makes expirable LoadingCache implementation, 1000 max keys by default and 5m TTL -func NewExpirableCache(opts ...Option) (*ExpirableCache, error) { - res := ExpirableCache{ - options: options{ +func NewExpirableCache[V any](opts ...Option[V]) (*ExpirableCache[V], error) { + res := ExpirableCache[V]{ + Workers: Workers[V]{ maxKeys: 1000, maxValueSize: 0, ttl: 5 * time.Minute, @@ -32,7 +33,7 @@ func NewExpirableCache(opts ...Option) (*ExpirableCache, error) { } for _, opt := range opts { - if err := opt(&res.options); err != nil { + if err := opt(&res.Workers); err != nil { return nil, fmt.Errorf("failed to set cache option: %w", err) } } @@ -41,34 +42,25 @@ func NewExpirableCache(opts ...Option) (*ExpirableCache, error) { return nil, fmt.Errorf("can't subscribe to event bus: %w", err) } - backend, err := cache.NewLoadingCache( - cache.MaxKeys(res.maxKeys), - cache.TTL(res.ttl), - cache.PurgeEvery(res.ttl/2), - cache.OnEvicted(func(key string, value interface{}) { - if res.onEvicted != nil { - res.onEvicted(key, value) - } - if s, ok := value.(Sizer); ok { - size := s.Size() - atomic.AddInt64(&res.currentSize, -1*int64(size)) - } - // ignore the error on Publish as we don't have log inside the module and - // there is no other way to handle it: we publish the cache invalidation - // and hope for the best - _ = res.eventBus.Publish(res.id, key) - }), - ) - if err != nil { - return nil, fmt.Errorf("error creating backend: %w", err) - } - res.backend = backend + res.backend = expirable.NewLRU[string, V](res.maxKeys, func(key string, value V) { + if res.onEvicted != nil { + res.onEvicted(key, value) + } + if s, ok := any(value).(Sizer); ok { + size := s.Size() + atomic.AddInt64(&res.currentSize, -1*int64(size)) + } + // ignore the error on Publish as we don't have log inside the module and + // there is no other way to handle it: we publish the cache invalidation + // and hope for the best + _ = res.eventBus.Publish(res.id, key) + }, res.ttl) return &res, nil } // Get gets value by key or load with fn if not found in cache -func (c *ExpirableCache) Get(key string, fn func() (interface{}, error)) (data interface{}, err error) { +func (c *ExpirableCache[V]) Get(key string, fn func() (V, error)) (data V, err error) { if v, ok := c.backend.Get(key); ok { atomic.AddInt64(&c.Hits, 1) return v, nil @@ -84,47 +76,50 @@ func (c *ExpirableCache) Get(key string, fn func() (interface{}, error)) (data i return data, nil } - if s, ok := data.(Sizer); ok { + if s, ok := any(data).(Sizer); ok { if c.maxCacheSize > 0 && atomic.LoadInt64(&c.currentSize)+int64(s.Size()) >= c.maxCacheSize { - c.backend.DeleteExpired() return data, nil } atomic.AddInt64(&c.currentSize, int64(s.Size())) } - c.backend.Set(key, data) + c.backend.Add(key, data) return data, nil } // Invalidate removes keys with passed predicate fn, i.e. fn(key) should be true to get evicted -func (c *ExpirableCache) Invalidate(fn func(key string) bool) { - c.backend.InvalidateFn(fn) +func (c *ExpirableCache[V]) Invalidate(fn func(key string) bool) { + for _, key := range c.backend.Keys() { + if fn(key) { + c.backend.Remove(key) + } + } } // Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key. -func (c *ExpirableCache) Peek(key string) (interface{}, bool) { +func (c *ExpirableCache[V]) Peek(key string) (V, bool) { return c.backend.Peek(key) } // Purge clears the cache completely. -func (c *ExpirableCache) Purge() { +func (c *ExpirableCache[V]) Purge() { c.backend.Purge() atomic.StoreInt64(&c.currentSize, 0) } // Delete cache item by key -func (c *ExpirableCache) Delete(key string) { - c.backend.Invalidate(key) +func (c *ExpirableCache[V]) Delete(key string) { + c.backend.Remove(key) } // Keys returns cache keys -func (c *ExpirableCache) Keys() (res []string) { +func (c *ExpirableCache[V]) Keys() (res []string) { return c.backend.Keys() } // Stat returns cache statistics -func (c *ExpirableCache) Stat() CacheStat { +func (c *ExpirableCache[V]) Stat() CacheStat { return CacheStat{ Hits: c.Hits, Misses: c.Misses, @@ -134,35 +129,38 @@ func (c *ExpirableCache) Stat() CacheStat { } } -// Close kills cleanup goroutine -func (c *ExpirableCache) Close() error { - c.backend.Close() +// Close supposed to kill cleanup goroutine, +// but it's not possible before https://github.com/hashicorp/golang-lru/issues/159 is solved +// so for now it just cleans it. +func (c *ExpirableCache[V]) Close() error { + c.backend.Purge() + atomic.StoreInt64(&c.currentSize, 0) return nil } // onBusEvent reacts on invalidation message triggered by event bus from another cache instance -func (c *ExpirableCache) onBusEvent(id, key string) { +func (c *ExpirableCache[V]) onBusEvent(id, key string) { if id != c.id { - c.backend.Invalidate(key) + c.backend.Remove(key) } } -func (c *ExpirableCache) size() int64 { +func (c *ExpirableCache[V]) size() int64 { return atomic.LoadInt64(&c.currentSize) } -func (c *ExpirableCache) keys() int { - return c.backend.ItemCount() +func (c *ExpirableCache[V]) keys() int { + return c.backend.Len() } -func (c *ExpirableCache) allowed(key string, data interface{}) bool { - if c.backend.ItemCount() >= c.maxKeys { +func (c *ExpirableCache[V]) allowed(key string, data V) bool { + if c.backend.Len() >= c.maxKeys { return false } if c.maxKeySize > 0 && len(key) > c.maxKeySize { return false } - if s, ok := data.(Sizer); ok { + if s, ok := any(data).(Sizer); ok { if c.maxValueSize > 0 && s.Size() >= c.maxValueSize { return false } diff --git a/backend/vendor/github.com/go-pkgz/lcw/lru_cache.go b/backend/vendor/github.com/go-pkgz/lcw/v2/lru_cache.go similarity index 65% rename from backend/vendor/github.com/go-pkgz/lcw/lru_cache.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/lru_cache.go index 72b8e57f..9c63490b 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/lru_cache.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/lru_cache.go @@ -4,24 +4,25 @@ import ( "fmt" "sync/atomic" - "github.com/go-pkgz/lcw/eventbus" "github.com/google/uuid" - lru "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru/v2" + + "github.com/go-pkgz/lcw/v2/eventbus" ) // LruCache wraps lru.LruCache with loading cache Get and size limits -type LruCache struct { - options +type LruCache[V any] struct { + Workers[V] CacheStat - backend *lru.Cache + backend *lru.Cache[string, V] currentSize int64 id string // uuid identifying cache instance } // NewLruCache makes LRU LoadingCache implementation, 1000 max keys by default -func NewLruCache(opts ...Option) (*LruCache, error) { - res := LruCache{ - options: options{ +func NewLruCache[V any](opts ...Option[V]) (*LruCache[V], error) { + res := LruCache[V]{ + Workers: Workers[V]{ maxKeys: 1000, maxValueSize: 0, eventBus: &eventbus.NopPubSub{}, @@ -29,7 +30,7 @@ func NewLruCache(opts ...Option) (*LruCache, error) { id: uuid.New().String(), } for _, opt := range opts { - if err := opt(&res.options); err != nil { + if err := opt(&res.Workers); err != nil { return nil, fmt.Errorf("failed to set cache option: %w", err) } } @@ -38,25 +39,25 @@ func NewLruCache(opts ...Option) (*LruCache, error) { return &res, err } -func (c *LruCache) init() error { +func (c *LruCache[V]) init() error { if err := c.eventBus.Subscribe(c.onBusEvent); err != nil { return fmt.Errorf("can't subscribe to event bus: %w", err) } - onEvicted := func(key interface{}, value interface{}) { + onEvicted := func(key string, value V) { if c.onEvicted != nil { - c.onEvicted(key.(string), value) + c.onEvicted(key, value) } - if s, ok := value.(Sizer); ok { + if s, ok := any(value).(Sizer); ok { size := s.Size() atomic.AddInt64(&c.currentSize, -1*int64(size)) } - _ = c.eventBus.Publish(c.id, key.(string)) // signal invalidation to other nodes + _ = c.eventBus.Publish(c.id, key) // signal invalidation to other nodes } var err error // OnEvicted called automatically for expired and manually deleted - if c.backend, err = lru.NewWithEvict(c.maxKeys, onEvicted); err != nil { + if c.backend, err = lru.NewWithEvict[string, V](c.maxKeys, onEvicted); err != nil { return fmt.Errorf("failed to make lru cache backend: %w", err) } @@ -64,7 +65,7 @@ func (c *LruCache) init() error { } // Get gets value by key or load with fn if not found in cache -func (c *LruCache) Get(key string, fn func() (interface{}, error)) (data interface{}, err error) { +func (c *LruCache[V]) Get(key string, fn func() (V, error)) (data V, err error) { if v, ok := c.backend.Get(key); ok { atomic.AddInt64(&c.Hits, 1) return v, nil @@ -83,7 +84,7 @@ func (c *LruCache) Get(key string, fn func() (interface{}, error)) (data interfa c.backend.Add(key, data) - if s, ok := data.(Sizer); ok { + if s, ok := any(data).(Sizer); ok { atomic.AddInt64(&c.currentSize, int64(s.Size())) if c.maxCacheSize > 0 && atomic.LoadInt64(&c.currentSize) > c.maxCacheSize { for atomic.LoadInt64(&c.currentSize) > c.maxCacheSize { @@ -96,42 +97,37 @@ func (c *LruCache) Get(key string, fn func() (interface{}, error)) (data interfa } // Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key. -func (c *LruCache) Peek(key string) (interface{}, bool) { +func (c *LruCache[V]) Peek(key string) (V, bool) { return c.backend.Peek(key) } // Purge clears the cache completely. -func (c *LruCache) Purge() { +func (c *LruCache[V]) Purge() { c.backend.Purge() atomic.StoreInt64(&c.currentSize, 0) } // Invalidate removes keys with passed predicate fn, i.e. fn(key) should be true to get evicted -func (c *LruCache) Invalidate(fn func(key string) bool) { +func (c *LruCache[V]) Invalidate(fn func(key string) bool) { for _, k := range c.backend.Keys() { // Keys() returns copy of cache's key, safe to remove directly - if key, ok := k.(string); ok && fn(key) { - c.backend.Remove(key) + if fn(k) { + c.backend.Remove(k) } } } // Delete cache item by key -func (c *LruCache) Delete(key string) { +func (c *LruCache[V]) Delete(key string) { c.backend.Remove(key) } // Keys returns cache keys -func (c *LruCache) Keys() (res []string) { - keys := c.backend.Keys() - res = make([]string, 0, len(keys)) - for _, key := range keys { - res = append(res, key.(string)) - } - return res +func (c *LruCache[V]) Keys() (res []string) { + return c.backend.Keys() } // Stat returns cache statistics -func (c *LruCache) Stat() CacheStat { +func (c *LruCache[V]) Stat() CacheStat { return CacheStat{ Hits: c.Hits, Misses: c.Misses, @@ -142,30 +138,30 @@ func (c *LruCache) Stat() CacheStat { } // Close does nothing for this type of cache -func (c *LruCache) Close() error { +func (c *LruCache[V]) Close() error { return nil } // onBusEvent reacts on invalidation message triggered by event bus from another cache instance -func (c *LruCache) onBusEvent(id, key string) { +func (c *LruCache[V]) onBusEvent(id, key string) { if id != c.id && c.backend.Contains(key) { // prevent reaction on event from this cache c.backend.Remove(key) } } -func (c *LruCache) size() int64 { +func (c *LruCache[V]) size() int64 { return atomic.LoadInt64(&c.currentSize) } -func (c *LruCache) keys() int { +func (c *LruCache[V]) keys() int { return c.backend.Len() } -func (c *LruCache) allowed(key string, data interface{}) bool { +func (c *LruCache[V]) allowed(key string, data V) bool { if c.maxKeySize > 0 && len(key) > c.maxKeySize { return false } - if s, ok := data.(Sizer); ok { + if s, ok := any(data).(Sizer); ok { if c.maxValueSize > 0 && s.Size() >= c.maxValueSize { return false } diff --git a/backend/vendor/github.com/go-pkgz/lcw/options.go b/backend/vendor/github.com/go-pkgz/lcw/v2/options.go similarity index 50% rename from backend/vendor/github.com/go-pkgz/lcw/options.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/options.go index 01de5667..c960d13e 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/options.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/options.go @@ -4,26 +4,35 @@ import ( "fmt" "time" - "github.com/go-pkgz/lcw/eventbus" + "github.com/go-pkgz/lcw/v2/eventbus" ) -type options struct { +type Workers[V any] struct { maxKeys int maxValueSize int maxKeySize int maxCacheSize int64 ttl time.Duration - onEvicted func(key string, value interface{}) + onEvicted func(key string, value V) eventBus eventbus.PubSub + strToV func(string) V } // Option func type -type Option func(o *options) error +type Option[V any] func(o *Workers[V]) error + +// WorkerOptions holds the option setting methods +type WorkerOptions[T any] struct{} + +// NewOpts creates a new WorkerOptions instance +func NewOpts[T any]() *WorkerOptions[T] { + return &WorkerOptions[T]{} +} // MaxValSize functional option defines the largest value's size allowed to be cached // By default it is 0, which means unlimited. -func MaxValSize(max int) Option { - return func(o *options) error { +func (o *WorkerOptions[V]) MaxValSize(max int) Option[V] { + return func(o *Workers[V]) error { if max < 0 { return fmt.Errorf("negative max value size") } @@ -34,8 +43,8 @@ func MaxValSize(max int) Option { // MaxKeySize functional option defines the largest key's size allowed to be used in cache // By default it is 0, which means unlimited. -func MaxKeySize(max int) Option { - return func(o *options) error { +func (o *WorkerOptions[V]) MaxKeySize(max int) Option[V] { + return func(o *Workers[V]) error { if max < 0 { return fmt.Errorf("negative max key size") } @@ -45,9 +54,9 @@ func MaxKeySize(max int) Option { } // MaxKeys functional option defines how many keys to keep. -// By default it is 0, which means unlimited. -func MaxKeys(max int) Option { - return func(o *options) error { +// By default, it is 0, which means unlimited. +func (o *WorkerOptions[V]) MaxKeys(max int) Option[V] { + return func(o *Workers[V]) error { if max < 0 { return fmt.Errorf("negative max keys") } @@ -57,9 +66,9 @@ func MaxKeys(max int) Option { } // MaxCacheSize functional option defines the total size of cached data. -// By default it is 0, which means unlimited. -func MaxCacheSize(max int64) Option { - return func(o *options) error { +// By default, it is 0, which means unlimited. +func (o *WorkerOptions[V]) MaxCacheSize(max int64) Option[V] { + return func(o *Workers[V]) error { if max < 0 { return fmt.Errorf("negative max cache size") } @@ -70,8 +79,8 @@ func MaxCacheSize(max int64) Option { // TTL functional option defines duration. // Works for ExpirableCache only -func TTL(ttl time.Duration) Option { - return func(o *options) error { +func (o *WorkerOptions[V]) TTL(ttl time.Duration) Option[V] { + return func(o *Workers[V]) error { if ttl < 0 { return fmt.Errorf("negative ttl") } @@ -81,17 +90,25 @@ func TTL(ttl time.Duration) Option { } // OnEvicted sets callback on invalidation event -func OnEvicted(fn func(key string, value interface{})) Option { - return func(o *options) error { +func (o *WorkerOptions[V]) OnEvicted(fn func(key string, value V)) Option[V] { + return func(o *Workers[V]) error { o.onEvicted = fn return nil } } // EventBus sets PubSub for distributed cache invalidation -func EventBus(pubSub eventbus.PubSub) Option { - return func(o *options) error { +func (o *WorkerOptions[V]) EventBus(pubSub eventbus.PubSub) Option[V] { + return func(o *Workers[V]) error { o.eventBus = pubSub return nil } } + +// StrToV sets strToV function for RedisCache +func (o *WorkerOptions[V]) StrToV(fn func(string) V) Option[V] { + return func(o *Workers[V]) error { + o.strToV = fn + return nil + } +} diff --git a/backend/vendor/github.com/go-pkgz/lcw/redis_cache.go b/backend/vendor/github.com/go-pkgz/lcw/v2/redis_cache.go similarity index 56% rename from backend/vendor/github.com/go-pkgz/lcw/redis_cache.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/redis_cache.go index dd68c30e..732ddf31 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/redis_cache.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/redis_cache.go @@ -2,7 +2,9 @@ package lcw import ( "context" + "errors" "fmt" + "reflect" "sync/atomic" "time" @@ -13,25 +15,42 @@ import ( const RedisValueSizeLimit = 512 * 1024 * 1024 // RedisCache implements LoadingCache for Redis. -type RedisCache struct { - options +type RedisCache[V any] struct { + Workers[V] CacheStat backend *redis.Client } // NewRedisCache makes Redis LoadingCache implementation. -func NewRedisCache(backend *redis.Client, opts ...Option) (*RedisCache, error) { - res := RedisCache{ - options: options{ +// Supports only string and string-based types and will return error otherwise. +func NewRedisCache[V any](backend *redis.Client, opts ...Option[V]) (*RedisCache[V], error) { + // check if V is string, not underlying type but directly, and otherwise return error if strToV is nil as it should be defined + + res := RedisCache[V]{ + Workers: Workers[V]{ ttl: 5 * time.Minute, }, } for _, opt := range opts { - if err := opt(&res.options); err != nil { + if err := opt(&res.Workers); err != nil { return nil, fmt.Errorf("failed to set cache option: %w", err) } } + // check if underlying type is string, so we can safely store it in Redis + var v V + if reflect.TypeOf(v).Kind() != reflect.String { + return nil, fmt.Errorf("can't store non-string types in Redis cache") + } + switch any(v).(type) { + case string: + // check strToV option only for string-like but non string types + default: + if res.strToV == nil { + return nil, fmt.Errorf("StrToV option should be set for string-like type") + } + } + if res.maxValueSize <= 0 || res.maxValueSize > RedisValueSizeLimit { res.maxValueSize = RedisValueSizeLimit } @@ -42,23 +61,33 @@ 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) { +func (c *RedisCache[V]) Get(key string, fn func() (V, error)) (data V, 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 + switch any(data).(type) { + case string: + return any(v).(V), nil + default: + return c.strToV(v), nil + } // RedisClient returns redis.Nil when doesn't find a key in DB - case redis.Nil: + 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 + switch any(data).(type) { + case string: + return any(v).(V), getErr + default: + return c.strToV(v), getErr + } } atomic.AddInt64(&c.Misses, 1) @@ -76,7 +105,7 @@ func (c *RedisCache) Get(key string, fn func() (interface{}, error)) (data inter } // Invalidate removes keys with passed predicate fn, i.e. fn(key) should be true to get evicted -func (c *RedisCache) Invalidate(fn func(key string) bool) { +func (c *RedisCache[V]) Invalidate(fn func(key string) bool) { for _, key := range c.backend.Keys(context.Background(), "*").Val() { // Keys() returns copy of cache's key, safe to remove directly if fn(key) { c.backend.Del(context.Background(), key) @@ -85,32 +114,38 @@ func (c *RedisCache) Invalidate(fn func(key string) bool) { } // Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key. -func (c *RedisCache) Peek(key string) (interface{}, bool) { +func (c *RedisCache[V]) Peek(key string) (data V, found bool) { ret, err := c.backend.Get(context.Background(), key).Result() if err != nil { - return nil, false + var emptyValue V + return emptyValue, false + } + switch any(data).(type) { + case string: + return any(ret).(V), true + default: + return any(ret).(V), true } - return ret, true } // Purge clears the cache completely. -func (c *RedisCache) Purge() { +func (c *RedisCache[V]) Purge() { c.backend.FlushDB(context.Background()) } // Delete cache item by key -func (c *RedisCache) Delete(key string) { +func (c *RedisCache[V]) Delete(key string) { c.backend.Del(context.Background(), key) } // Keys gets all keys for the cache -func (c *RedisCache) Keys() (res []string) { +func (c *RedisCache[V]) Keys() (res []string) { return c.backend.Keys(context.Background(), "*").Val() } // Stat returns cache statistics -func (c *RedisCache) Stat() CacheStat { +func (c *RedisCache[V]) Stat() CacheStat { return CacheStat{ Hits: c.Hits, Misses: c.Misses, @@ -121,26 +156,26 @@ func (c *RedisCache) Stat() CacheStat { } // Close closes underlying connections -func (c *RedisCache) Close() error { +func (c *RedisCache[V]) Close() error { return c.backend.Close() } -func (c *RedisCache) size() int64 { +func (c *RedisCache[V]) size() int64 { return 0 } -func (c *RedisCache) keys() int { +func (c *RedisCache[V]) keys() int { return int(c.backend.DBSize(context.Background()).Val()) } -func (c *RedisCache) allowed(key string, data interface{}) bool { +func (c *RedisCache[V]) allowed(key string, data V) bool { if c.maxKeys > 0 && c.backend.DBSize(context.Background()).Val() >= int64(c.maxKeys) { return false } if c.maxKeySize > 0 && len(key) > c.maxKeySize { return false } - if s, ok := data.(Sizer); ok { + if s, ok := any(data).(Sizer); ok { if c.maxValueSize > 0 && (s.Size() >= c.maxValueSize) { return false } diff --git a/backend/vendor/github.com/go-pkgz/lcw/scache.go b/backend/vendor/github.com/go-pkgz/lcw/v2/scache.go similarity index 88% rename from backend/vendor/github.com/go-pkgz/lcw/scache.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/scache.go index 2181b75c..d60cfc3e 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/scache.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/scache.go @@ -7,36 +7,36 @@ import ( // Scache wraps LoadingCache with partitions (sub-system), and scopes. // Simplified interface with just 4 funcs - Get, Flush, Stats and Close -type Scache struct { - lc LoadingCache +type Scache[V any] struct { + lc LoadingCache[V] } // NewScache creates Scache on top of LoadingCache -func NewScache(lc LoadingCache) *Scache { - return &Scache{lc: lc} +func NewScache[V any](lc LoadingCache[V]) *Scache[V] { + return &Scache[V]{lc: lc} } // Get retrieves a key from underlying backend -func (m *Scache) Get(key Key, fn func() ([]byte, error)) (data []byte, err error) { +func (m *Scache[V]) Get(key Key, fn func() (V, error)) (data V, err error) { keyStr := key.String() - val, err := m.lc.Get(keyStr, func() (value interface{}, e error) { + val, err := m.lc.Get(keyStr, func() (value V, e error) { return fn() }) - return val.([]byte), err + return val, err } // Stat delegates the call to the underlying cache backend -func (m *Scache) Stat() CacheStat { +func (m *Scache[V]) Stat() CacheStat { return m.lc.Stat() } // Close calls Close function of the underlying cache -func (m *Scache) Close() error { +func (m *Scache[V]) Close() error { return m.lc.Close() } // Flush clears cache and calls postFlushFn async -func (m *Scache) Flush(req FlusherRequest) { +func (m *Scache[V]) Flush(req FlusherRequest) { if len(req.scopes) == 0 { m.lc.Purge() return diff --git a/backend/vendor/github.com/go-pkgz/lcw/url.go b/backend/vendor/github.com/go-pkgz/lcw/v2/url.go similarity index 85% rename from backend/vendor/github.com/go-pkgz/lcw/url.go rename to backend/vendor/github.com/go-pkgz/lcw/v2/url.go index d696e88e..8328696e 100644 --- a/backend/vendor/github.com/go-pkgz/lcw/url.go +++ b/backend/vendor/github.com/go-pkgz/lcw/v2/url.go @@ -16,14 +16,14 @@ import ( // - mem://lru?max_keys=10&max_cache_size=1024 // - mem://expirable?ttl=30s&max_val_size=100 // - nop:// -func New(uri string) (LoadingCache, error) { +func New[V any](uri string) (LoadingCache[V], error) { u, err := url.Parse(uri) if err != nil { return nil, fmt.Errorf("parse cache uri %s: %w", uri, err) } query := u.Query() - opts, err := optionsFromQuery(query) + opts, err := optionsFromQuery[V](query) if err != nil { return nil, fmt.Errorf("parse uri options %s: %w", uri, err) } @@ -42,27 +42,28 @@ func New(uri string) (LoadingCache, error) { case "mem": switch u.Hostname() { case "lru": - return NewLruCache(opts...) + return NewLruCache[V](opts...) case "expirable": - return NewExpirableCache(opts...) + return NewExpirableCache[V](opts...) default: return nil, fmt.Errorf("unsupported mem cache type %s", u.Hostname()) } case "nop": - return NewNopCache(), nil + return NewNopCache[V](), nil } return nil, fmt.Errorf("unsupported cache type %s", u.Scheme) } -func optionsFromQuery(q url.Values) (opts []Option, err error) { +func optionsFromQuery[V any](q url.Values) (opts []Option[V], err error) { errs := new(multierror.Error) + o := NewOpts[V]() if v := q.Get("max_val_size"); v != "" { vv, e := strconv.Atoi(v) if e != nil { errs = multierror.Append(errs, fmt.Errorf("max_val_size query param %s: %w", v, e)) } else { - opts = append(opts, MaxValSize(vv)) + opts = append(opts, o.MaxValSize(vv)) } } @@ -71,7 +72,7 @@ func optionsFromQuery(q url.Values) (opts []Option, err error) { if e != nil { errs = multierror.Append(errs, fmt.Errorf("max_key_size query param %s: %w", v, e)) } else { - opts = append(opts, MaxKeySize(vv)) + opts = append(opts, o.MaxKeySize(vv)) } } @@ -80,7 +81,7 @@ func optionsFromQuery(q url.Values) (opts []Option, err error) { if e != nil { errs = multierror.Append(errs, fmt.Errorf("max_keys query param %s: %w", v, e)) } else { - opts = append(opts, MaxKeys(vv)) + opts = append(opts, o.MaxKeys(vv)) } } @@ -89,7 +90,7 @@ func optionsFromQuery(q url.Values) (opts []Option, err error) { if e != nil { errs = multierror.Append(errs, fmt.Errorf("max_cache_size query param %s: %w", v, e)) } else { - opts = append(opts, MaxCacheSize(vv)) + opts = append(opts, o.MaxCacheSize(vv)) } } @@ -98,7 +99,7 @@ func optionsFromQuery(q url.Values) (opts []Option, err error) { if e != nil { errs = multierror.Append(errs, fmt.Errorf("ttl query param %s: %w", v, e)) } else { - opts = append(opts, TTL(vv)) + opts = append(opts, o.TTL(vv)) } } diff --git a/backend/vendor/github.com/hashicorp/golang-lru/.golangci.yml b/backend/vendor/github.com/hashicorp/golang-lru/.golangci.yml deleted file mode 100644 index 49202fc4..00000000 --- a/backend/vendor/github.com/hashicorp/golang-lru/.golangci.yml +++ /dev/null @@ -1,30 +0,0 @@ -linters: - enable: - - megacheck - - revive - - govet - - unconvert - - megacheck - - gas - - gocyclo - - dupl - - misspell - - unparam - - unused - - typecheck - - ineffassign - - stylecheck - - exportloopref - - gocritic - - nakedret - - gosimple - - prealloc - fast: false - disable-all: true - -issues: - exclude-rules: - - path: _test\.go - linters: - - dupl - exclude-use-default: false diff --git a/backend/vendor/github.com/hashicorp/golang-lru/README.md b/backend/vendor/github.com/hashicorp/golang-lru/README.md deleted file mode 100644 index 03bcfb5b..00000000 --- a/backend/vendor/github.com/hashicorp/golang-lru/README.md +++ /dev/null @@ -1,7 +0,0 @@ -golang-lru -========== - -Please upgrade to github.com/hashicorp/golang-lru/v2 for all new code as v1 will -not be updated anymore. The v2 version supports generics and is faster; old code -can specify a specific tag, e.g. github.com/hashicorp/golang-lru/v1.0.2 for -backwards compatibility. diff --git a/backend/vendor/github.com/hashicorp/golang-lru/arc.go b/backend/vendor/github.com/hashicorp/golang-lru/arc.go deleted file mode 100644 index e396f842..00000000 --- a/backend/vendor/github.com/hashicorp/golang-lru/arc.go +++ /dev/null @@ -1,256 +0,0 @@ -package lru - -import ( - "sync" - - "github.com/hashicorp/golang-lru/simplelru" -) - -// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC). -// ARC is an enhancement over the standard LRU cache in that tracks both -// frequency and recency of use. This avoids a burst in access to new -// entries from evicting the frequently used older entries. It adds some -// additional tracking overhead to a standard LRU cache, computationally -// it is roughly 2x the cost, and the extra memory overhead is linear -// with the size of the cache. ARC has been patented by IBM, but is -// similar to the TwoQueueCache (2Q) which requires setting parameters. -type ARCCache struct { - size int // Size is the total capacity of the cache - p int // P is the dynamic preference towards T1 or T2 - - t1 simplelru.LRUCache // T1 is the LRU for recently accessed items - b1 simplelru.LRUCache // B1 is the LRU for evictions from t1 - - t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items - b2 simplelru.LRUCache // B2 is the LRU for evictions from t2 - - lock sync.RWMutex -} - -// NewARC creates an ARC of the given size -func NewARC(size int) (*ARCCache, error) { - // Create the sub LRUs - b1, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - b2, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - t1, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - t2, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - - // Initialize the ARC - c := &ARCCache{ - size: size, - p: 0, - t1: t1, - b1: b1, - t2: t2, - b2: b2, - } - return c, nil -} - -// Get looks up a key's value from the cache. -func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) { - c.lock.Lock() - defer c.lock.Unlock() - - // If the value is contained in T1 (recent), then - // promote it to T2 (frequent) - if val, ok := c.t1.Peek(key); ok { - c.t1.Remove(key) - c.t2.Add(key, val) - return val, ok - } - - // Check if the value is contained in T2 (frequent) - if val, ok := c.t2.Get(key); ok { - return val, ok - } - - // No hit - return nil, false -} - -// Add adds a value to the cache. -func (c *ARCCache) Add(key, value interface{}) { - c.lock.Lock() - defer c.lock.Unlock() - - // Check if the value is contained in T1 (recent), and potentially - // promote it to frequent T2 - if c.t1.Contains(key) { - c.t1.Remove(key) - c.t2.Add(key, value) - return - } - - // Check if the value is already in T2 (frequent) and update it - if c.t2.Contains(key) { - c.t2.Add(key, value) - return - } - - // Check if this value was recently evicted as part of the - // recently used list - if c.b1.Contains(key) { - // T1 set is too small, increase P appropriately - delta := 1 - b1Len := c.b1.Len() - b2Len := c.b2.Len() - if b2Len > b1Len { - delta = b2Len / b1Len - } - if c.p+delta >= c.size { - c.p = c.size - } else { - c.p += delta - } - - // Potentially need to make room in the cache - if c.t1.Len()+c.t2.Len() >= c.size { - c.replace(false) - } - - // Remove from B1 - c.b1.Remove(key) - - // Add the key to the frequently used list - c.t2.Add(key, value) - return - } - - // Check if this value was recently evicted as part of the - // frequently used list - if c.b2.Contains(key) { - // T2 set is too small, decrease P appropriately - delta := 1 - b1Len := c.b1.Len() - b2Len := c.b2.Len() - if b1Len > b2Len { - delta = b1Len / b2Len - } - if delta >= c.p { - c.p = 0 - } else { - c.p -= delta - } - - // Potentially need to make room in the cache - if c.t1.Len()+c.t2.Len() >= c.size { - c.replace(true) - } - - // Remove from B2 - c.b2.Remove(key) - - // Add the key to the frequently used list - c.t2.Add(key, value) - return - } - - // Potentially need to make room in the cache - if c.t1.Len()+c.t2.Len() >= c.size { - c.replace(false) - } - - // Keep the size of the ghost buffers trim - if c.b1.Len() > c.size-c.p { - c.b1.RemoveOldest() - } - if c.b2.Len() > c.p { - c.b2.RemoveOldest() - } - - // Add to the recently seen list - c.t1.Add(key, value) -} - -// replace is used to adaptively evict from either T1 or T2 -// based on the current learned value of P -func (c *ARCCache) replace(b2ContainsKey bool) { - t1Len := c.t1.Len() - if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) { - k, _, ok := c.t1.RemoveOldest() - if ok { - c.b1.Add(k, nil) - } - } else { - k, _, ok := c.t2.RemoveOldest() - if ok { - c.b2.Add(k, nil) - } - } -} - -// Len returns the number of cached entries -func (c *ARCCache) Len() int { - c.lock.RLock() - defer c.lock.RUnlock() - return c.t1.Len() + c.t2.Len() -} - -// Keys returns all the cached keys -func (c *ARCCache) Keys() []interface{} { - c.lock.RLock() - defer c.lock.RUnlock() - k1 := c.t1.Keys() - k2 := c.t2.Keys() - return append(k1, k2...) -} - -// Remove is used to purge a key from the cache -func (c *ARCCache) Remove(key interface{}) { - c.lock.Lock() - defer c.lock.Unlock() - if c.t1.Remove(key) { - return - } - if c.t2.Remove(key) { - return - } - if c.b1.Remove(key) { - return - } - if c.b2.Remove(key) { - return - } -} - -// Purge is used to clear the cache -func (c *ARCCache) Purge() { - c.lock.Lock() - defer c.lock.Unlock() - c.t1.Purge() - c.t2.Purge() - c.b1.Purge() - c.b2.Purge() -} - -// Contains is used to check if the cache contains a key -// without updating recency or frequency. -func (c *ARCCache) Contains(key interface{}) bool { - c.lock.RLock() - defer c.lock.RUnlock() - return c.t1.Contains(key) || c.t2.Contains(key) -} - -// Peek is used to inspect the cache value of a key -// without updating recency or frequency. -func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) { - c.lock.RLock() - defer c.lock.RUnlock() - if val, ok := c.t1.Peek(key); ok { - return val, ok - } - return c.t2.Peek(key) -} diff --git a/backend/vendor/github.com/hashicorp/golang-lru/doc.go b/backend/vendor/github.com/hashicorp/golang-lru/doc.go deleted file mode 100644 index 2547df97..00000000 --- a/backend/vendor/github.com/hashicorp/golang-lru/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package lru provides three different LRU caches of varying sophistication. -// -// Cache is a simple LRU cache. It is based on the -// LRU implementation in groupcache: -// https://github.com/golang/groupcache/tree/master/lru -// -// TwoQueueCache tracks frequently used and recently used entries separately. -// This avoids a burst of accesses from taking out frequently used entries, -// at the cost of about 2x computational overhead and some extra bookkeeping. -// -// ARCCache is an adaptive replacement cache. It tracks recent evictions as -// well as recent usage in both the frequent and recent caches. Its -// computational overhead is comparable to TwoQueueCache, but the memory -// overhead is linear with the size of the cache. -// -// ARC has been patented by IBM, so do not use it if that is problematic for -// your program. -// -// All caches in this package take locks while operating, and are therefore -// thread-safe for consumers. -package lru diff --git a/backend/vendor/github.com/hashicorp/golang-lru/testing.go b/backend/vendor/github.com/hashicorp/golang-lru/testing.go deleted file mode 100644 index 49276078..00000000 --- a/backend/vendor/github.com/hashicorp/golang-lru/testing.go +++ /dev/null @@ -1,16 +0,0 @@ -package lru - -import ( - "crypto/rand" - "math" - "math/big" - "testing" -) - -func getRand(tb testing.TB) int64 { - out, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - if err != nil { - tb.Fatal(err) - } - return out.Int64() -} diff --git a/backend/vendor/github.com/hashicorp/golang-lru/.gitignore b/backend/vendor/github.com/hashicorp/golang-lru/v2/.gitignore similarity index 100% rename from backend/vendor/github.com/hashicorp/golang-lru/.gitignore rename to backend/vendor/github.com/hashicorp/golang-lru/v2/.gitignore diff --git a/backend/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml b/backend/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml new file mode 100644 index 00000000..7e7b8a96 --- /dev/null +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml @@ -0,0 +1,46 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +linters: + fast: false + disable-all: true + enable: + - revive + - megacheck + - govet + - unconvert + - gas + - gocyclo + - dupl + - misspell + - unparam + - unused + - typecheck + - ineffassign + # - stylecheck + - exportloopref + - gocritic + - nakedret + - gosimple + - prealloc + +# golangci-lint configuration file +linters-settings: + revive: + ignore-generated-header: true + severity: warning + rules: + - name: package-comments + severity: warning + disabled: true + - name: exported + severity: warning + disabled: false + arguments: ["checkPrivateReceivers", "disableStutteringCheck"] + +issues: + exclude-use-default: false + exclude-rules: + - path: _test\.go + linters: + - dupl diff --git a/backend/vendor/github.com/hashicorp/golang-lru/2q.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/2q.go similarity index 64% rename from backend/vendor/github.com/hashicorp/golang-lru/2q.go rename to backend/vendor/github.com/hashicorp/golang-lru/v2/2q.go index 15fcad03..8c95252b 100644 --- a/backend/vendor/github.com/hashicorp/golang-lru/2q.go +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/2q.go @@ -1,10 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package lru import ( - "fmt" + "errors" "sync" - "github.com/hashicorp/golang-lru/simplelru" + "github.com/hashicorp/golang-lru/v2/simplelru" ) const ( @@ -26,33 +29,35 @@ const ( // computationally about 2x the cost, and adds some metadata over // head. The ARCCache is similar, but does not require setting any // parameters. -type TwoQueueCache struct { - size int - recentSize int +type TwoQueueCache[K comparable, V any] struct { + size int + recentSize int + recentRatio float64 + ghostRatio float64 - recent simplelru.LRUCache - frequent simplelru.LRUCache - recentEvict simplelru.LRUCache + recent simplelru.LRUCache[K, V] + frequent simplelru.LRUCache[K, V] + recentEvict simplelru.LRUCache[K, struct{}] lock sync.RWMutex } // New2Q creates a new TwoQueueCache using the default // values for the parameters. -func New2Q(size int) (*TwoQueueCache, error) { - return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries) +func New2Q[K comparable, V any](size int) (*TwoQueueCache[K, V], error) { + return New2QParams[K, V](size, Default2QRecentRatio, Default2QGhostEntries) } // New2QParams creates a new TwoQueueCache using the provided // parameter values. -func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, error) { +func New2QParams[K comparable, V any](size int, recentRatio, ghostRatio float64) (*TwoQueueCache[K, V], error) { if size <= 0 { - return nil, fmt.Errorf("invalid size") + return nil, errors.New("invalid size") } if recentRatio < 0.0 || recentRatio > 1.0 { - return nil, fmt.Errorf("invalid recent ratio") + return nil, errors.New("invalid recent ratio") } if ghostRatio < 0.0 || ghostRatio > 1.0 { - return nil, fmt.Errorf("invalid ghost ratio") + return nil, errors.New("invalid ghost ratio") } // Determine the sub-sizes @@ -60,23 +65,25 @@ func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, err evictSize := int(float64(size) * ghostRatio) // Allocate the LRUs - recent, err := simplelru.NewLRU(size, nil) + recent, err := simplelru.NewLRU[K, V](size, nil) if err != nil { return nil, err } - frequent, err := simplelru.NewLRU(size, nil) + frequent, err := simplelru.NewLRU[K, V](size, nil) if err != nil { return nil, err } - recentEvict, err := simplelru.NewLRU(evictSize, nil) + recentEvict, err := simplelru.NewLRU[K, struct{}](evictSize, nil) if err != nil { return nil, err } // Initialize the cache - c := &TwoQueueCache{ + c := &TwoQueueCache[K, V]{ size: size, recentSize: recentSize, + recentRatio: recentRatio, + ghostRatio: ghostRatio, recent: recent, frequent: frequent, recentEvict: recentEvict, @@ -85,7 +92,7 @@ func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, err } // Get looks up a key's value from the cache. -func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) { +func (c *TwoQueueCache[K, V]) Get(key K) (value V, ok bool) { c.lock.Lock() defer c.lock.Unlock() @@ -103,11 +110,11 @@ func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) { } // No hit - return nil, false + return } // Add adds a value to the cache. -func (c *TwoQueueCache) Add(key, value interface{}) { +func (c *TwoQueueCache[K, V]) Add(key K, value V) { c.lock.Lock() defer c.lock.Unlock() @@ -141,7 +148,7 @@ func (c *TwoQueueCache) Add(key, value interface{}) { } // ensureSpace is used to ensure we have space in the cache -func (c *TwoQueueCache) ensureSpace(recentEvict bool) { +func (c *TwoQueueCache[K, V]) ensureSpace(recentEvict bool) { // If we have space, nothing to do recentLen := c.recent.Len() freqLen := c.frequent.Len() @@ -153,7 +160,7 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) { // the target, evict from there if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { k, _, _ := c.recent.RemoveOldest() - c.recentEvict.Add(k, nil) + c.recentEvict.Add(k, struct{}{}) return } @@ -162,15 +169,43 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) { } // Len returns the number of items in the cache. -func (c *TwoQueueCache) Len() int { +func (c *TwoQueueCache[K, V]) Len() int { c.lock.RLock() defer c.lock.RUnlock() return c.recent.Len() + c.frequent.Len() } +// Resize changes the cache size. +func (c *TwoQueueCache[K, V]) Resize(size int) (evicted int) { + c.lock.Lock() + defer c.lock.Unlock() + + // Recalculate the sub-sizes + recentSize := int(float64(size) * c.recentRatio) + evictSize := int(float64(size) * c.ghostRatio) + c.size = size + c.recentSize = recentSize + + // ensureSpace + diff := c.recent.Len() + c.frequent.Len() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.ensureSpace(true) + } + + // Reallocate the LRUs + c.recent.Resize(size) + c.frequent.Resize(size) + c.recentEvict.Resize(evictSize) + + return diff +} + // Keys returns a slice of the keys in the cache. // The frequently used keys are first in the returned slice. -func (c *TwoQueueCache) Keys() []interface{} { +func (c *TwoQueueCache[K, V]) Keys() []K { c.lock.RLock() defer c.lock.RUnlock() k1 := c.frequent.Keys() @@ -178,8 +213,18 @@ func (c *TwoQueueCache) Keys() []interface{} { return append(k1, k2...) } +// Values returns a slice of the values in the cache. +// The frequently used values are first in the returned slice. +func (c *TwoQueueCache[K, V]) Values() []V { + c.lock.RLock() + defer c.lock.RUnlock() + v1 := c.frequent.Values() + v2 := c.recent.Values() + return append(v1, v2...) +} + // Remove removes the provided key from the cache. -func (c *TwoQueueCache) Remove(key interface{}) { +func (c *TwoQueueCache[K, V]) Remove(key K) { c.lock.Lock() defer c.lock.Unlock() if c.frequent.Remove(key) { @@ -194,7 +239,7 @@ func (c *TwoQueueCache) Remove(key interface{}) { } // Purge is used to completely clear the cache. -func (c *TwoQueueCache) Purge() { +func (c *TwoQueueCache[K, V]) Purge() { c.lock.Lock() defer c.lock.Unlock() c.recent.Purge() @@ -204,7 +249,7 @@ func (c *TwoQueueCache) Purge() { // Contains is used to check if the cache contains a key // without updating recency or frequency. -func (c *TwoQueueCache) Contains(key interface{}) bool { +func (c *TwoQueueCache[K, V]) Contains(key K) bool { c.lock.RLock() defer c.lock.RUnlock() return c.frequent.Contains(key) || c.recent.Contains(key) @@ -212,7 +257,7 @@ func (c *TwoQueueCache) Contains(key interface{}) bool { // Peek is used to inspect the cache value of a key // without updating recency or frequency. -func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) { +func (c *TwoQueueCache[K, V]) Peek(key K) (value V, ok bool) { c.lock.RLock() defer c.lock.RUnlock() if val, ok := c.frequent.Peek(key); ok { diff --git a/backend/vendor/github.com/hashicorp/golang-lru/LICENSE b/backend/vendor/github.com/hashicorp/golang-lru/v2/LICENSE similarity index 100% rename from backend/vendor/github.com/hashicorp/golang-lru/LICENSE rename to backend/vendor/github.com/hashicorp/golang-lru/v2/LICENSE diff --git a/backend/vendor/github.com/hashicorp/golang-lru/v2/README.md b/backend/vendor/github.com/hashicorp/golang-lru/v2/README.md new file mode 100644 index 00000000..a942eb53 --- /dev/null +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/README.md @@ -0,0 +1,79 @@ +golang-lru +========== + +This provides the `lru` package which implements a fixed-size +thread safe LRU cache. It is based on the cache in Groupcache. + +Documentation +============= + +Full docs are available on [Go Packages](https://pkg.go.dev/github.com/hashicorp/golang-lru/v2) + +LRU cache example +================= + +```go +package main + +import ( + "fmt" + "github.com/hashicorp/golang-lru/v2" +) + +func main() { + l, _ := lru.New[int, any](128) + for i := 0; i < 256; i++ { + l.Add(i, nil) + } + if l.Len() != 128 { + panic(fmt.Sprintf("bad len: %v", l.Len())) + } +} +``` + +Expirable LRU cache example +=========================== + +```go +package main + +import ( + "fmt" + "time" + + "github.com/hashicorp/golang-lru/v2/expirable" +) + +func main() { + // make cache with 10ms TTL and 5 max keys + cache := expirable.NewLRU[string, string](5, nil, time.Millisecond*10) + + + // set value under key1. + cache.Add("key1", "val1") + + // get value under key1 + r, ok := cache.Get("key1") + + // check for OK value + if ok { + fmt.Printf("value before expiration is found: %v, value: %q\n", ok, r) + } + + // wait for cache to expire + time.Sleep(time.Millisecond * 12) + + // get value under key1 after key expiration + r, ok = cache.Get("key1") + fmt.Printf("value after expiration is found: %v, value: %q\n", ok, r) + + // set value under key2, would evict old entry because it is already expired. + cache.Add("key2", "val2") + + fmt.Printf("Cache len: %d\n", cache.Len()) + // Output: + // value before expiration is found: true, value: "val1" + // value after expiration is found: false, value: "" + // Cache len: 1 +} +``` diff --git a/backend/vendor/github.com/hashicorp/golang-lru/v2/doc.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/doc.go new file mode 100644 index 00000000..24107ee0 --- /dev/null +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package lru provides three different LRU caches of varying sophistication. +// +// Cache is a simple LRU cache. It is based on the LRU implementation in +// groupcache: https://github.com/golang/groupcache/tree/master/lru +// +// TwoQueueCache tracks frequently used and recently used entries separately. +// This avoids a burst of accesses from taking out frequently used entries, at +// the cost of about 2x computational overhead and some extra bookkeeping. +// +// ARCCache is an adaptive replacement cache. It tracks recent evictions as well +// as recent usage in both the frequent and recent caches. Its computational +// overhead is comparable to TwoQueueCache, but the memory overhead is linear +// with the size of the cache. +// +// ARC has been patented by IBM, so do not use it if that is problematic for +// your program. For this reason, it is in a separate go module contained within +// this repository. +// +// All caches in this package take locks while operating, and are therefore +// thread-safe for consumers. +package lru diff --git a/backend/vendor/github.com/hashicorp/golang-lru/v2/expirable/expirable_lru.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/expirable/expirable_lru.go new file mode 100644 index 00000000..89978d6d --- /dev/null +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/expirable/expirable_lru.go @@ -0,0 +1,338 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package expirable + +import ( + "sync" + "time" + + "github.com/hashicorp/golang-lru/v2/internal" +) + +// EvictCallback is used to get a callback when a cache entry is evicted +type EvictCallback[K comparable, V any] func(key K, value V) + +// LRU implements a thread-safe LRU with expirable entries. +type LRU[K comparable, V any] struct { + size int + evictList *internal.LruList[K, V] + items map[K]*internal.Entry[K, V] + onEvict EvictCallback[K, V] + + // expirable options + mu sync.Mutex + ttl time.Duration + done chan struct{} + + // buckets for expiration + buckets []bucket[K, V] + // uint8 because it's number between 0 and numBuckets + nextCleanupBucket uint8 +} + +// bucket is a container for holding entries to be expired +type bucket[K comparable, V any] struct { + entries map[K]*internal.Entry[K, V] + newestEntry time.Time +} + +// noEvictionTTL - very long ttl to prevent eviction +const noEvictionTTL = time.Hour * 24 * 365 * 10 + +// because of uint8 usage for nextCleanupBucket, should not exceed 256. +// casting it as uint8 explicitly requires type conversions in multiple places +const numBuckets = 100 + +// NewLRU returns a new thread-safe cache with expirable entries. +// +// Size parameter set to 0 makes cache of unlimited size, e.g. turns LRU mechanism off. +// +// Providing 0 TTL turns expiring off. +// +// Delete expired entries every 1/100th of ttl value. Goroutine which deletes expired entries runs indefinitely. +func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V], ttl time.Duration) *LRU[K, V] { + if size < 0 { + size = 0 + } + if ttl <= 0 { + ttl = noEvictionTTL + } + + res := LRU[K, V]{ + ttl: ttl, + size: size, + evictList: internal.NewList[K, V](), + items: make(map[K]*internal.Entry[K, V]), + onEvict: onEvict, + done: make(chan struct{}), + } + + // initialize the buckets + res.buckets = make([]bucket[K, V], numBuckets) + for i := 0; i < numBuckets; i++ { + res.buckets[i] = bucket[K, V]{entries: make(map[K]*internal.Entry[K, V])} + } + + // enable deleteExpired() running in separate goroutine for cache with non-zero TTL + // + // Important: done channel is never closed, so deleteExpired() goroutine will never exit, + // it's decided to add functionality to close it in the version later than v2. + if res.ttl != noEvictionTTL { + go func(done <-chan struct{}) { + ticker := time.NewTicker(res.ttl / numBuckets) + defer ticker.Stop() + for { + select { + case <-done: + return + case <-ticker.C: + res.deleteExpired() + } + } + }(res.done) + } + return &res +} + +// Purge clears the cache completely. +// onEvict is called for each evicted key. +func (c *LRU[K, V]) Purge() { + c.mu.Lock() + defer c.mu.Unlock() + for k, v := range c.items { + if c.onEvict != nil { + c.onEvict(k, v.Value) + } + delete(c.items, k) + } + for _, b := range c.buckets { + for _, ent := range b.entries { + delete(b.entries, ent.Key) + } + } + c.evictList.Init() +} + +// 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 *LRU[K, V]) Add(key K, value V) (evicted bool) { + c.mu.Lock() + defer c.mu.Unlock() + now := time.Now() + + // Check for existing item + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + c.removeFromBucket(ent) // remove the entry from its current bucket as expiresAt is renewed + ent.Value = value + ent.ExpiresAt = now.Add(c.ttl) + c.addToBucket(ent) + return false + } + + // Add new item + ent := c.evictList.PushFrontExpirable(key, value, now.Add(c.ttl)) + c.items[key] = ent + c.addToBucket(ent) // adds the entry to the appropriate bucket and sets entry.expireBucket + + evict := c.size > 0 && c.evictList.Length() > c.size + // Verify size not exceeded + if evict { + c.removeOldest() + } + return evict +} + +// Get looks up a key's value from the cache. +func (c *LRU[K, V]) Get(key K) (value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + var ent *internal.Entry[K, V] + if ent, ok = c.items[key]; ok { + // Expired item check + if time.Now().After(ent.ExpiresAt) { + return value, false + } + c.evictList.MoveToFront(ent) + return ent.Value, true + } + return +} + +// Contains checks if a key is in the cache, without updating the recent-ness +// or deleting it for being stale. +func (c *LRU[K, V]) Contains(key K) (ok bool) { + c.mu.Lock() + defer c.mu.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. +func (c *LRU[K, V]) Peek(key K) (value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + var ent *internal.Entry[K, V] + if ent, ok = c.items[key]; ok { + // Expired item check + if time.Now().After(ent.ExpiresAt) { + return value, false + } + return ent.Value, true + } + return +} + +// Remove removes the provided key from the cache, returning if the +// key was contained. +func (c *LRU[K, V]) Remove(key K) bool { + c.mu.Lock() + defer c.mu.Unlock() + if ent, ok := c.items[key]; ok { + c.removeElement(ent) + return true + } + return false +} + +// RemoveOldest removes the oldest item from the cache. +func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + if ent := c.evictList.Back(); ent != nil { + c.removeElement(ent) + return ent.Key, ent.Value, true + } + return +} + +// GetOldest returns the oldest entry +func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + if ent := c.evictList.Back(); ent != nil { + return ent.Key, ent.Value, true + } + return +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *LRU[K, V]) Keys() []K { + c.mu.Lock() + defer c.mu.Unlock() + keys := make([]K, 0, len(c.items)) + for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { + keys = append(keys, ent.Key) + } + return keys +} + +// Values returns a slice of the values in the cache, from oldest to newest. +// Expired entries are filtered out. +func (c *LRU[K, V]) Values() []V { + c.mu.Lock() + defer c.mu.Unlock() + values := make([]V, len(c.items)) + i := 0 + now := time.Now() + for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { + if now.After(ent.ExpiresAt) { + continue + } + values[i] = ent.Value + i++ + } + return values +} + +// Len returns the number of items in the cache. +func (c *LRU[K, V]) Len() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.evictList.Length() +} + +// Resize changes the cache size. Size of 0 means unlimited. +func (c *LRU[K, V]) Resize(size int) (evicted int) { + c.mu.Lock() + defer c.mu.Unlock() + if size <= 0 { + c.size = 0 + return 0 + } + diff := c.evictList.Length() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.removeOldest() + } + c.size = size + return diff +} + +// Close destroys cleanup goroutine. To clean up the cache, run Purge() before Close(). +// func (c *LRU[K, V]) Close() { +// c.mu.Lock() +// defer c.mu.Unlock() +// select { +// case <-c.done: +// return +// default: +// } +// close(c.done) +// } + +// removeOldest removes the oldest item from the cache. Has to be called with lock! +func (c *LRU[K, V]) removeOldest() { + if ent := c.evictList.Back(); ent != nil { + c.removeElement(ent) + } +} + +// removeElement is used to remove a given list element from the cache. Has to be called with lock! +func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) { + c.evictList.Remove(e) + delete(c.items, e.Key) + c.removeFromBucket(e) + if c.onEvict != nil { + c.onEvict(e.Key, e.Value) + } +} + +// deleteExpired deletes expired records from the oldest bucket, waiting for the newest entry +// in it to expire first. +func (c *LRU[K, V]) deleteExpired() { + c.mu.Lock() + bucketIdx := c.nextCleanupBucket + timeToExpire := time.Until(c.buckets[bucketIdx].newestEntry) + // wait for newest entry to expire before cleanup without holding lock + if timeToExpire > 0 { + c.mu.Unlock() + time.Sleep(timeToExpire) + c.mu.Lock() + } + for _, ent := range c.buckets[bucketIdx].entries { + c.removeElement(ent) + } + c.nextCleanupBucket = (c.nextCleanupBucket + 1) % numBuckets + c.mu.Unlock() +} + +// addToBucket adds entry to expire bucket so that it will be cleaned up when the time comes. Has to be called with lock! +func (c *LRU[K, V]) addToBucket(e *internal.Entry[K, V]) { + bucketID := (numBuckets + c.nextCleanupBucket - 1) % numBuckets + e.ExpireBucket = bucketID + c.buckets[bucketID].entries[e.Key] = e + if c.buckets[bucketID].newestEntry.Before(e.ExpiresAt) { + c.buckets[bucketID].newestEntry = e.ExpiresAt + } +} + +// removeFromBucket removes the entry from its corresponding bucket. Has to be called with lock! +func (c *LRU[K, V]) removeFromBucket(e *internal.Entry[K, V]) { + delete(c.buckets[e.ExpireBucket].entries, e.Key) +} diff --git a/backend/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go new file mode 100644 index 00000000..5cd74a03 --- /dev/null +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go @@ -0,0 +1,142 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE_list file. + +package internal + +import "time" + +// Entry is an LRU Entry +type Entry[K comparable, V any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *Entry[K, V] + + // The list to which this element belongs. + list *LruList[K, V] + + // The LRU Key of this element. + Key K + + // The Value stored with this element. + Value V + + // The time this element would be cleaned up, optional + ExpiresAt time.Time + + // The expiry bucket item was put in, optional + ExpireBucket uint8 +} + +// PrevEntry returns the previous list element or nil. +func (e *Entry[K, V]) PrevEntry() *Entry[K, V] { + if p := e.prev; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// LruList represents a doubly linked list. +// The zero Value for LruList is an empty list ready to use. +type LruList[K comparable, V any] struct { + root Entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list Length excluding (this) sentinel element +} + +// Init initializes or clears list l. +func (l *LruList[K, V]) Init() *LruList[K, V] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// NewList returns an initialized list. +func NewList[K comparable, V any]() *LruList[K, V] { return new(LruList[K, V]).Init() } + +// Length returns the number of elements of list l. +// The complexity is O(1). +func (l *LruList[K, V]) Length() int { return l.len } + +// Back returns the last element of list l or nil if the list is empty. +func (l *LruList[K, V]) Back() *Entry[K, V] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List Value. +func (l *LruList[K, V]) lazyInit() { + if l.root.next == nil { + l.Init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *LruList[K, V]) insert(e, at *Entry[K, V]) *Entry[K, V] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Entry{Value: v, ExpiresAt: ExpiresAt}, at). +func (l *LruList[K, V]) insertValue(k K, v V, expiresAt time.Time, at *Entry[K, V]) *Entry[K, V] { + return l.insert(&Entry[K, V]{Value: v, Key: k, ExpiresAt: expiresAt}, at) +} + +// Remove removes e from its list, decrements l.len +func (l *LruList[K, V]) Remove(e *Entry[K, V]) V { + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- + + return e.Value +} + +// move moves e to next to at. +func (l *LruList[K, V]) move(e, at *Entry[K, V]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *LruList[K, V]) PushFront(k K, v V) *Entry[K, V] { + l.lazyInit() + return l.insertValue(k, v, time.Time{}, &l.root) +} + +// PushFrontExpirable inserts a new expirable element e with Value v at the front of list l and returns e. +func (l *LruList[K, V]) PushFrontExpirable(k K, v V, expiresAt time.Time) *Entry[K, V] { + l.lazyInit() + return l.insertValue(k, v, expiresAt, &l.root) +} + +// MoveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *LruList[K, V]) MoveToFront(e *Entry[K, V]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} diff --git a/backend/vendor/github.com/hashicorp/golang-lru/lru.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/lru.go similarity index 72% rename from backend/vendor/github.com/hashicorp/golang-lru/lru.go rename to backend/vendor/github.com/hashicorp/golang-lru/v2/lru.go index 895d8e3e..a2655f1f 100644 --- a/backend/vendor/github.com/hashicorp/golang-lru/lru.go +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/lru.go @@ -1,9 +1,12 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package lru import ( "sync" - "github.com/hashicorp/golang-lru/simplelru" + "github.com/hashicorp/golang-lru/v2/simplelru" ) const ( @@ -12,23 +15,24 @@ const ( ) // Cache is a thread-safe fixed size LRU cache. -type Cache struct { - lru *simplelru.LRU - evictedKeys, evictedVals []interface{} - onEvictedCB func(k, v interface{}) - lock sync.RWMutex +type Cache[K comparable, V any] struct { + lru *simplelru.LRU[K, V] + evictedKeys []K + evictedVals []V + onEvictedCB func(k K, v V) + lock sync.RWMutex } // New creates an LRU of the given size. -func New(size int) (*Cache, error) { - return NewWithEvict(size, nil) +func New[K comparable, V any](size int) (*Cache[K, V], error) { + return NewWithEvict[K, V](size, nil) } // NewWithEvict constructs a fixed size cache with the given eviction // callback. -func NewWithEvict(size int, onEvicted func(key, value interface{})) (c *Cache, err error) { +func NewWithEvict[K comparable, V any](size int, onEvicted func(key K, value V)) (c *Cache[K, V], err error) { // create a cache with default settings - c = &Cache{ + c = &Cache[K, V]{ onEvictedCB: onEvicted, } if onEvicted != nil { @@ -39,21 +43,22 @@ func NewWithEvict(size int, onEvicted func(key, value interface{})) (c *Cache, e return } -func (c *Cache) initEvictBuffers() { - c.evictedKeys = make([]interface{}, 0, DefaultEvictedBufferSize) - c.evictedVals = make([]interface{}, 0, DefaultEvictedBufferSize) +func (c *Cache[K, V]) initEvictBuffers() { + c.evictedKeys = make([]K, 0, DefaultEvictedBufferSize) + c.evictedVals = make([]V, 0, DefaultEvictedBufferSize) } // onEvicted save evicted key/val and sent in externally registered callback // outside of critical section -func (c *Cache) onEvicted(k, v interface{}) { +func (c *Cache[K, V]) onEvicted(k K, v V) { c.evictedKeys = append(c.evictedKeys, k) c.evictedVals = append(c.evictedVals, v) } // Purge is used to completely clear the cache. -func (c *Cache) Purge() { - var ks, vs []interface{} +func (c *Cache[K, V]) Purge() { + var ks []K + var vs []V c.lock.Lock() c.lru.Purge() if c.onEvictedCB != nil && len(c.evictedKeys) > 0 { @@ -70,8 +75,9 @@ func (c *Cache) Purge() { } // Add adds a value to the cache. Returns true if an eviction occurred. -func (c *Cache) Add(key, value interface{}) (evicted bool) { - var k, v interface{} +func (c *Cache[K, V]) Add(key K, value V) (evicted bool) { + var k K + var v V c.lock.Lock() evicted = c.lru.Add(key, value) if c.onEvictedCB != nil && evicted { @@ -86,7 +92,7 @@ func (c *Cache) Add(key, value interface{}) (evicted bool) { } // Get looks up a key's value from the cache. -func (c *Cache) Get(key interface{}) (value interface{}, ok bool) { +func (c *Cache[K, V]) Get(key K) (value V, ok bool) { c.lock.Lock() value, ok = c.lru.Get(key) c.lock.Unlock() @@ -95,7 +101,7 @@ func (c *Cache) Get(key interface{}) (value interface{}, ok bool) { // Contains checks if a key is in the cache, without updating the // recent-ness or deleting it for being stale. -func (c *Cache) Contains(key interface{}) bool { +func (c *Cache[K, V]) Contains(key K) bool { c.lock.RLock() containKey := c.lru.Contains(key) c.lock.RUnlock() @@ -104,7 +110,7 @@ func (c *Cache) Contains(key interface{}) bool { // Peek returns the key value (or undefined if not found) without updating // the "recently used"-ness of the key. -func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) { +func (c *Cache[K, V]) Peek(key K) (value V, ok bool) { c.lock.RLock() value, ok = c.lru.Peek(key) c.lock.RUnlock() @@ -114,8 +120,9 @@ func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) { // ContainsOrAdd checks if a key is in the cache without updating the // recent-ness or deleting it for being stale, and if not, adds the value. // Returns whether found and whether an eviction occurred. -func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) { - var k, v interface{} +func (c *Cache[K, V]) ContainsOrAdd(key K, value V) (ok, evicted bool) { + var k K + var v V c.lock.Lock() if c.lru.Contains(key) { c.lock.Unlock() @@ -136,8 +143,9 @@ func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) { // PeekOrAdd checks if a key is in the cache without updating the // recent-ness or deleting it for being stale, and if not, adds the value. // Returns whether found and whether an eviction occurred. -func (c *Cache) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evicted bool) { - var k, v interface{} +func (c *Cache[K, V]) PeekOrAdd(key K, value V) (previous V, ok, evicted bool) { + var k K + var v V c.lock.Lock() previous, ok = c.lru.Peek(key) if ok { @@ -153,12 +161,13 @@ func (c *Cache) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evi if c.onEvictedCB != nil && evicted { c.onEvictedCB(k, v) } - return nil, false, evicted + return } // Remove removes the provided key from the cache. -func (c *Cache) Remove(key interface{}) (present bool) { - var k, v interface{} +func (c *Cache[K, V]) Remove(key K) (present bool) { + var k K + var v V c.lock.Lock() present = c.lru.Remove(key) if c.onEvictedCB != nil && present { @@ -173,8 +182,9 @@ func (c *Cache) Remove(key interface{}) (present bool) { } // Resize changes the cache size. -func (c *Cache) Resize(size int) (evicted int) { - var ks, vs []interface{} +func (c *Cache[K, V]) Resize(size int) (evicted int) { + var ks []K + var vs []V c.lock.Lock() evicted = c.lru.Resize(size) if c.onEvictedCB != nil && evicted > 0 { @@ -191,8 +201,9 @@ func (c *Cache) Resize(size int) (evicted int) { } // RemoveOldest removes the oldest item from the cache. -func (c *Cache) RemoveOldest() (key, value interface{}, ok bool) { - var k, v interface{} +func (c *Cache[K, V]) RemoveOldest() (key K, value V, ok bool) { + var k K + var v V c.lock.Lock() key, value, ok = c.lru.RemoveOldest() if c.onEvictedCB != nil && ok { @@ -207,7 +218,7 @@ func (c *Cache) RemoveOldest() (key, value interface{}, ok bool) { } // GetOldest returns the oldest entry -func (c *Cache) GetOldest() (key, value interface{}, ok bool) { +func (c *Cache[K, V]) GetOldest() (key K, value V, ok bool) { c.lock.RLock() key, value, ok = c.lru.GetOldest() c.lock.RUnlock() @@ -215,15 +226,23 @@ func (c *Cache) GetOldest() (key, value interface{}, ok bool) { } // Keys returns a slice of the keys in the cache, from oldest to newest. -func (c *Cache) Keys() []interface{} { +func (c *Cache[K, V]) Keys() []K { c.lock.RLock() keys := c.lru.Keys() c.lock.RUnlock() return keys } +// Values returns a slice of the values in the cache, from oldest to newest. +func (c *Cache[K, V]) Values() []V { + c.lock.RLock() + values := c.lru.Values() + c.lock.RUnlock() + return values +} + // Len returns the number of items in the cache. -func (c *Cache) Len() int { +func (c *Cache[K, V]) Len() int { c.lock.RLock() length := c.lru.Len() c.lock.RUnlock() diff --git a/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list b/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list new file mode 100644 index 00000000..c4764e6b --- /dev/null +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list @@ -0,0 +1,29 @@ +This license applies to simplelru/list.go + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/backend/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go similarity index 50% rename from backend/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go rename to backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go index 9233583c..f6979238 100644 --- a/backend/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go @@ -1,46 +1,45 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package simplelru import ( - "container/list" "errors" + + "github.com/hashicorp/golang-lru/v2/internal" ) // EvictCallback is used to get a callback when a cache entry is evicted -type EvictCallback func(key interface{}, value interface{}) +type EvictCallback[K comparable, V any] func(key K, value V) // LRU implements a non-thread safe fixed size LRU cache -type LRU struct { +type LRU[K comparable, V any] struct { size int - evictList *list.List - items map[interface{}]*list.Element - onEvict EvictCallback -} - -// entry is used to hold a value in the evictList -type entry struct { - key interface{} - value interface{} + evictList *internal.LruList[K, V] + items map[K]*internal.Entry[K, V] + onEvict EvictCallback[K, V] } // NewLRU constructs an LRU of the given size -func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { +func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) { if size <= 0 { return nil, errors.New("must provide a positive size") } - c := &LRU{ + + c := &LRU[K, V]{ size: size, - evictList: list.New(), - items: make(map[interface{}]*list.Element), + evictList: internal.NewList[K, V](), + items: make(map[K]*internal.Entry[K, V]), onEvict: onEvict, } return c, nil } // Purge is used to completely clear the cache. -func (c *LRU) Purge() { +func (c *LRU[K, V]) Purge() { for k, v := range c.items { if c.onEvict != nil { - c.onEvict(k, v.Value.(*entry).value) + c.onEvict(k, v.Value) } delete(c.items, k) } @@ -48,20 +47,19 @@ func (c *LRU) Purge() { } // Add adds a value to the cache. Returns true if an eviction occurred. -func (c *LRU) Add(key, value interface{}) (evicted bool) { +func (c *LRU[K, V]) Add(key K, value V) (evicted bool) { // Check for existing item if ent, ok := c.items[key]; ok { c.evictList.MoveToFront(ent) - ent.Value.(*entry).value = value + ent.Value = value return false } // Add new item - ent := &entry{key, value} - entry := c.evictList.PushFront(ent) - c.items[key] = entry + ent := c.evictList.PushFront(key, value) + c.items[key] = ent - evict := c.evictList.Len() > c.size + evict := c.evictList.Length() > c.size // Verify size not exceeded if evict { c.removeOldest() @@ -70,37 +68,34 @@ func (c *LRU) Add(key, value interface{}) (evicted bool) { } // Get looks up a key's value from the cache. -func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { +func (c *LRU[K, V]) Get(key K) (value V, ok bool) { if ent, ok := c.items[key]; ok { c.evictList.MoveToFront(ent) - if ent.Value.(*entry) == nil { - return nil, false - } - return ent.Value.(*entry).value, true + return ent.Value, true } return } // Contains checks if a key is in the cache, without updating the recent-ness // or deleting it for being stale. -func (c *LRU) Contains(key interface{}) (ok bool) { +func (c *LRU[K, V]) Contains(key K) (ok bool) { _, 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. -func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { - var ent *list.Element +func (c *LRU[K, V]) Peek(key K) (value V, ok bool) { + var ent *internal.Entry[K, V] if ent, ok = c.items[key]; ok { - return ent.Value.(*entry).value, true + return ent.Value, true } - return nil, ok + return } // Remove removes the provided key from the cache, returning if the // key was contained. -func (c *LRU) Remove(key interface{}) (present bool) { +func (c *LRU[K, V]) Remove(key K) (present bool) { if ent, ok := c.items[key]; ok { c.removeElement(ent) return true @@ -109,44 +104,51 @@ func (c *LRU) Remove(key interface{}) (present bool) { } // RemoveOldest removes the oldest item from the cache. -func (c *LRU) RemoveOldest() (key, value interface{}, ok bool) { - ent := c.evictList.Back() - if ent != nil { +func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) { + if ent := c.evictList.Back(); ent != nil { c.removeElement(ent) - kv := ent.Value.(*entry) - return kv.key, kv.value, true + return ent.Key, ent.Value, true } - return nil, nil, false + return } // GetOldest returns the oldest entry -func (c *LRU) GetOldest() (key, value interface{}, ok bool) { - ent := c.evictList.Back() - if ent != nil { - kv := ent.Value.(*entry) - return kv.key, kv.value, true +func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) { + if ent := c.evictList.Back(); ent != nil { + return ent.Key, ent.Value, true } - return nil, nil, false + return } // Keys returns a slice of the keys in the cache, from oldest to newest. -func (c *LRU) Keys() []interface{} { - keys := make([]interface{}, len(c.items)) +func (c *LRU[K, V]) Keys() []K { + keys := make([]K, c.evictList.Length()) i := 0 - for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { - keys[i] = ent.Value.(*entry).key + for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { + keys[i] = ent.Key i++ } return keys } +// Values returns a slice of the values in the cache, from oldest to newest. +func (c *LRU[K, V]) Values() []V { + values := make([]V, len(c.items)) + i := 0 + for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { + values[i] = ent.Value + i++ + } + return values +} + // Len returns the number of items in the cache. -func (c *LRU) Len() int { - return c.evictList.Len() +func (c *LRU[K, V]) Len() int { + return c.evictList.Length() } // Resize changes the cache size. -func (c *LRU) Resize(size int) (evicted int) { +func (c *LRU[K, V]) Resize(size int) (evicted int) { diff := c.Len() - size if diff < 0 { diff = 0 @@ -159,19 +161,17 @@ func (c *LRU) Resize(size int) (evicted int) { } // removeOldest removes the oldest item from the cache. -func (c *LRU) removeOldest() { - ent := c.evictList.Back() - if ent != nil { +func (c *LRU[K, V]) removeOldest() { + if ent := c.evictList.Back(); ent != nil { c.removeElement(ent) } } // removeElement is used to remove a given list element from the cache -func (c *LRU) removeElement(e *list.Element) { +func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) { c.evictList.Remove(e) - kv := e.Value.(*entry) - delete(c.items, kv.key) + delete(c.items, e.Key) if c.onEvict != nil { - c.onEvict(kv.key, kv.value) + c.onEvict(e.Key, e.Value) } } diff --git a/backend/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go b/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go similarity index 68% rename from backend/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go rename to backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go index cb7f8caf..043b8bcc 100644 --- a/backend/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go +++ b/backend/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go @@ -1,33 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package simplelru provides simple LRU implementation based on build-in container/list. package simplelru // LRUCache is the interface for simple LRU cache. -type LRUCache interface { +type LRUCache[K comparable, V any] interface { // Adds a value to the cache, returns true if an eviction occurred and // updates the "recently used"-ness of the key. - Add(key, value interface{}) bool + Add(key K, value V) bool // Returns key's value from the cache and // updates the "recently used"-ness of the key. #value, isFound - Get(key interface{}) (value interface{}, ok bool) + Get(key K) (value V, ok bool) // Checks if a key exists in cache without updating the recent-ness. - Contains(key interface{}) (ok bool) + Contains(key K) (ok bool) // Returns key's value without updating the "recently used"-ness of the key. - Peek(key interface{}) (value interface{}, ok bool) + Peek(key K) (value V, ok bool) // Removes a key from the cache. - Remove(key interface{}) bool + Remove(key K) bool // Removes the oldest entry from cache. - RemoveOldest() (interface{}, interface{}, bool) + RemoveOldest() (K, V, bool) // Returns the oldest entry from the cache. #key, value, isFound - GetOldest() (interface{}, interface{}, bool) + GetOldest() (K, V, bool) // Returns a slice of the keys in the cache, from oldest to newest. - Keys() []interface{} + Keys() []K + + // Values returns a slice of the values in the cache, from oldest to newest. + Values() []V // Returns the number of items in the cache. Len() int diff --git a/backend/vendor/modules.txt b/backend/vendor/modules.txt index 6a0b0fdb..6288a853 100644 --- a/backend/vendor/modules.txt +++ b/backend/vendor/modules.txt @@ -86,11 +86,10 @@ github.com/go-pkgz/expirable-cache # github.com/go-pkgz/jrpc v0.3.0 ## explicit; go 1.16 github.com/go-pkgz/jrpc -# github.com/go-pkgz/lcw v1.1.0 +# github.com/go-pkgz/lcw/v2 v2.0.0 ## explicit; go 1.21 -github.com/go-pkgz/lcw -github.com/go-pkgz/lcw/eventbus -github.com/go-pkgz/lcw/internal/cache +github.com/go-pkgz/lcw/v2 +github.com/go-pkgz/lcw/v2/eventbus # github.com/go-pkgz/lgr v0.11.1 ## explicit; go 1.20 github.com/go-pkgz/lgr @@ -136,10 +135,12 @@ github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.1 ## explicit; go 1.13 github.com/hashicorp/go-multierror -# github.com/hashicorp/golang-lru v1.0.2 -## explicit; go 1.12 -github.com/hashicorp/golang-lru -github.com/hashicorp/golang-lru/simplelru +# github.com/hashicorp/golang-lru/v2 v2.0.7 +## explicit; go 1.18 +github.com/hashicorp/golang-lru/v2 +github.com/hashicorp/golang-lru/v2/expirable +github.com/hashicorp/golang-lru/v2/internal +github.com/hashicorp/golang-lru/v2/simplelru # github.com/jessevdk/go-flags v1.5.0 ## explicit; go 1.15 github.com/jessevdk/go-flags