diff --git a/common/data/share/user.go b/common/data/share/user.go index 2af260311..b280e8fe1 100644 --- a/common/data/share/user.go +++ b/common/data/share/user.go @@ -2,13 +2,13 @@ package share import ( "context" - "math/rand" "time" "blazing/cool" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/grand" ) // newSessionStore 创建会话缓存实例 @@ -94,8 +94,8 @@ func (m *sessionManager) UserOnlineExists(userID uint32) (bool, error) { // generate6DigitCode 生成6位数字注册码(100000-999999) func (m *sessionManager) generate6DigitCode() int { // 初始化随机数生成器(确保每次调用生成不同序列) - r := rand.New(rand.NewSource(time.Now().UnixNano())) - return r.Intn(900000) + 100000 + + return grand.Intn(900000) + 100000 } // SaveEmailCode 生成并保存邮件注册码(返回生成的验证码,支持设置过期时间) diff --git a/common/utils/golang-lru-main/.github/CODEOWNERS b/common/utils/golang-lru-main/.github/CODEOWNERS deleted file mode 100644 index 2cad39ac6..000000000 --- a/common/utils/golang-lru-main/.github/CODEOWNERS +++ /dev/null @@ -1,13 +0,0 @@ -# Each line is a file pattern followed by one or more owners. -# More on CODEOWNERS files: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners - -# Default owner -* @hashicorp/team-ip-compliance @hashicorp/raft-force - -# Add override rules below. Each line is a file/folder pattern followed by one or more owners. -# Being an owner means those groups or individuals will be added as reviewers to PRs affecting -# those areas of the code. -# Examples: -# /docs/ @docs-team -# *.js @js-team -# *.go @go-team diff --git a/common/utils/golang-lru-main/.github/dependabot.yml b/common/utils/golang-lru-main/.github/dependabot.yml deleted file mode 100644 index b3f70c8eb..000000000 --- a/common/utils/golang-lru-main/.github/dependabot.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -version: 2 - -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - day: "sunday" - commit-message: - prefix: "[chore] : " - groups: - actions: - patterns: - - "*" - - - package-ecosystem: "gomod" - directories: - - "/" - - "/arc" - schedule: - interval: "weekly" - day: "sunday" - commit-message: - prefix: "[chore] : " - groups: - go: - patterns: - - "*" - applies-to: "version-updates" - go-security: - patterns: - - "*" - applies-to: "security-updates" \ No newline at end of file diff --git a/common/utils/golang-lru-main/.github/pull_request_template.md b/common/utils/golang-lru-main/.github/pull_request_template.md deleted file mode 100644 index 877c03e4b..000000000 --- a/common/utils/golang-lru-main/.github/pull_request_template.md +++ /dev/null @@ -1,12 +0,0 @@ - -## Description - - - -## Related Issue - - - -## How Has This Been Tested? - - diff --git a/common/utils/golang-lru-main/.github/workflows/ci.yml b/common/utils/golang-lru-main/.github/workflows/ci.yml deleted file mode 100644 index 7daf57688..000000000 --- a/common/utils/golang-lru-main/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: build - -on: - push: - branches: [main] - tags: ["*"] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: set up go 1.19 - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version: 1.19 - id: go - - - name: checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: build, test and generate coverage report - run: | - go test -timeout=60s -race -v ./... -coverprofile=coverage.out - go build -race ./... - - - name: build and test ARC - working-directory: ./arc - run: | - go test -timeout=60s -race - go build -race - - - name: Upload the coverage report - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - path: coverage.out - name: Coverage-report - - - name: Display the coverage report - run: go tool cover -func=coverage.out - - - name: install golangci-lint - run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $GITHUB_WORKSPACE v1.53.3 - - - name: run golangci-lint - run: $GITHUB_WORKSPACE/golangci-lint run --out-format=github-actions ./... ./simplelru/... ./expirable/... - - - name: run golangci-lint on ARC - working-directory: ./arc - run: $GITHUB_WORKSPACE/golangci-lint run --out-format=github-actions ./... diff --git a/common/utils/golang-lru-main/.github/workflows/two-step-pr-approval.yml b/common/utils/golang-lru-main/.github/workflows/two-step-pr-approval.yml deleted file mode 100644 index c65741719..000000000 --- a/common/utils/golang-lru-main/.github/workflows/two-step-pr-approval.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Two-Stage PR Review Process - -on: - pull_request: - types: [opened, synchronize, reopened, labeled, unlabeled, ready_for_review, converted_to_draft] - pull_request_review: - types: [submitted] - -jobs: - manage-pr-status: - runs-on: ubuntu-latest - permissions: - pull-requests: write - contents: write - steps: - - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.0.0 - - - name: Two stage PR review - uses: hashicorp/two-stage-pr-approval@v0.1.0 diff --git a/common/utils/golang-lru-main/.go-version b/common/utils/golang-lru-main/.go-version deleted file mode 100644 index bc4493477..000000000 --- a/common/utils/golang-lru-main/.go-version +++ /dev/null @@ -1 +0,0 @@ -1.19 diff --git a/common/utils/golang-lru-main/.golangci.yml b/common/utils/golang-lru-main/.golangci.yml deleted file mode 100644 index 3c65b8f89..000000000 --- a/common/utils/golang-lru-main/.golangci.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright IBM Corp. 2014, 2025 -# 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/common/utils/golang-lru-main/2q.go b/common/utils/golang-lru-main/2q.go deleted file mode 100644 index 40e1672fe..000000000 --- a/common/utils/golang-lru-main/2q.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package lru - -import ( - "errors" - "sync" - - "github.com/hashicorp/golang-lru/v2/simplelru" -) - -const ( - // Default2QRecentRatio is the ratio of the 2Q cache dedicated - // to recently added entries that have only been accessed once. - Default2QRecentRatio = 0.25 - - // Default2QGhostEntries is the default ratio of ghost - // entries kept to track entries recently evicted - Default2QGhostEntries = 0.50 -) - -// TwoQueueCache is a thread-safe fixed size 2Q cache. -// 2Q is an enhancement over the standard LRU cache -// in that it tracks both frequently and recently used -// entries separately. This avoids a burst in access to new -// entries from evicting frequently used entries. It adds some -// additional tracking overhead to the standard LRU cache, and is -// computationally about 2x the cost, and adds some metadata over -// head. The ARCCache is similar, but does not require setting any -// parameters. -type TwoQueueCache[K comparable, V any] struct { - size int - recentSize int - recentRatio float64 - ghostRatio float64 - - 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[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[K comparable, V any](size int, recentRatio, ghostRatio float64) (*TwoQueueCache[K, V], error) { - if size <= 0 { - return nil, errors.New("invalid size") - } - if recentRatio < 0.0 || recentRatio > 1.0 { - return nil, errors.New("invalid recent ratio") - } - if ghostRatio < 0.0 || ghostRatio > 1.0 { - return nil, errors.New("invalid ghost ratio") - } - - // Determine the sub-sizes - recentSize := int(float64(size) * recentRatio) - evictSize := int(float64(size) * ghostRatio) - - // Allocate the LRUs - recent, err := simplelru.NewLRU[K, V](size, nil) - if err != nil { - return nil, err - } - frequent, err := simplelru.NewLRU[K, V](size, nil) - if err != nil { - return nil, err - } - recentEvict, err := simplelru.NewLRU[K, struct{}](evictSize, nil) - if err != nil { - return nil, err - } - - // Initialize the cache - c := &TwoQueueCache[K, V]{ - size: size, - recentSize: recentSize, - recentRatio: recentRatio, - ghostRatio: ghostRatio, - recent: recent, - frequent: frequent, - recentEvict: recentEvict, - } - return c, nil -} - -// Get looks up a key's value from the cache. -func (c *TwoQueueCache[K, V]) Get(key K) (value V, ok bool) { - c.lock.Lock() - defer c.lock.Unlock() - - // Check if this is a frequent value - if val, ok := c.frequent.Get(key); ok { - return val, ok - } - - // If the value is contained in recent, then we - // promote it to frequent - if val, ok := c.recent.Peek(key); ok { - c.recent.Remove(key) - c.frequent.Add(key, val) - return val, ok - } - - // No hit - return -} - -// Add adds a value to the cache. -func (c *TwoQueueCache[K, V]) Add(key K, value V) { - c.lock.Lock() - defer c.lock.Unlock() - - // Check if the value is frequently used already, - // and just update the value - if c.frequent.Contains(key) { - c.frequent.Add(key, value) - return - } - - // Check if the value is recently used, and promote - // the value into the frequent list - if c.recent.Contains(key) { - c.recent.Remove(key) - c.frequent.Add(key, value) - return - } - - // If the value was recently evicted, add it to the - // frequently used list - if c.recentEvict.Contains(key) { - c.ensureSpace(true) - c.recentEvict.Remove(key) - c.frequent.Add(key, value) - return - } - - // Add to the recently seen list - c.ensureSpace(false) - c.recent.Add(key, value) -} - -// ensureSpace is used to ensure we have space in the cache -func (c *TwoQueueCache[K, V]) ensureSpace(recentEvict bool) { - // If we have space, nothing to do - recentLen := c.recent.Len() - freqLen := c.frequent.Len() - if recentLen+freqLen < c.size { - return - } - - // If the recent buffer is larger than - // the target, evict from there - if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { - k, _, _ := c.recent.RemoveOldest() - c.recentEvict.Add(k, struct{}{}) - return - } - - // Remove from the frequent list otherwise - c.frequent.RemoveOldest() -} - -// Len returns the number of items in the cache. -func (c *TwoQueueCache[K, V]) Len() int { - c.lock.RLock() - defer c.lock.RUnlock() - return c.recent.Len() + c.frequent.Len() -} - -// Cap returns the capacity of the cache -func (c *TwoQueueCache[K, V]) Cap() int { - return c.size -} - -// 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[K, V]) Keys() []K { - c.lock.RLock() - defer c.lock.RUnlock() - k1 := c.frequent.Keys() - k2 := c.recent.Keys() - 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[K, V]) Remove(key K) { - c.lock.Lock() - defer c.lock.Unlock() - if c.frequent.Remove(key) { - return - } - if c.recent.Remove(key) { - return - } - if c.recentEvict.Remove(key) { - return - } -} - -// Purge is used to completely clear the cache. -func (c *TwoQueueCache[K, V]) Purge() { - c.lock.Lock() - defer c.lock.Unlock() - c.recent.Purge() - c.frequent.Purge() - c.recentEvict.Purge() -} - -// Contains is used to check if the cache contains a key -// without updating recency or frequency. -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) -} - -// Peek is used to inspect the cache value of a key -// without updating recency or frequency. -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 { - return val, ok - } - return c.recent.Peek(key) -} diff --git a/common/utils/golang-lru-main/2q_test.go b/common/utils/golang-lru-main/2q_test.go deleted file mode 100644 index d58a27334..000000000 --- a/common/utils/golang-lru-main/2q_test.go +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package lru - -import ( - "testing" -) - -func Benchmark2Q_Rand(b *testing.B) { - l, err := New2Q[int64, int64](8192) - if err != nil { - b.Fatalf("err: %v", err) - } - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - trace[i] = getRand(b) % 32768 - } - - b.ResetTimer() - - var hit, miss int - for i := 0; i < 2*b.N; i++ { - if i%2 == 0 { - l.Add(trace[i], trace[i]) - } else { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func Benchmark2Q_Freq(b *testing.B) { - l, err := New2Q[int64, int64](8192) - if err != nil { - b.Fatalf("err: %v", err) - } - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - if i%2 == 0 { - trace[i] = getRand(b) % 16384 - } else { - trace[i] = getRand(b) % 32768 - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - l.Add(trace[i], trace[i]) - } - var hit, miss int - for i := 0; i < b.N; i++ { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func Test2Q_RandomOps(t *testing.T) { - size := 128 - l, err := New2Q[int64, int64](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - n := 200000 - for i := 0; i < n; i++ { - key := getRand(t) % 512 - r := getRand(t) - switch r % 3 { - case 0: - l.Add(key, key) - case 1: - l.Get(key) - case 2: - l.Remove(key) - } - - if l.recent.Len()+l.frequent.Len() > size { - t.Fatalf("bad: recent: %d freq: %d", - l.recent.Len(), l.frequent.Len()) - } - } -} - -func Test2Q_Get_RecentToFrequent(t *testing.T) { - l, err := New2Q[int, int](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Touch all the entries, should be in t1 - for i := 0; i < 128; i++ { - l.Add(i, i) - } - if n := l.recent.Len(); n != 128 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - - // Get should upgrade to t2 - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("missing: %d", i) - } - } - if n := l.recent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 128 { - t.Fatalf("bad: %d", n) - } - - // Get be from t2 - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("missing: %d", i) - } - } - if n := l.recent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 128 { - t.Fatalf("bad: %d", n) - } -} - -func Test2Q_Add_RecentToFrequent(t *testing.T) { - l, err := New2Q[int, int](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Add initially to recent - l.Add(1, 1) - if n := l.recent.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - - // Add should upgrade to frequent - l.Add(1, 1) - if n := l.recent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - - // Add should remain in frequent - l.Add(1, 1) - if n := l.recent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } -} - -func Test2Q_Add_RecentEvict(t *testing.T) { - l, err := New2Q[int, int](4) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Add 1,2,3,4,5 -> Evict 1 - l.Add(1, 1) - l.Add(2, 2) - l.Add(3, 3) - l.Add(4, 4) - l.Add(5, 5) - if n := l.recent.Len(); n != 4 { - t.Fatalf("bad: %d", n) - } - if n := l.recentEvict.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - - // Pull in the recently evicted - l.Add(1, 1) - if n := l.recent.Len(); n != 3 { - t.Fatalf("bad: %d", n) - } - if n := l.recentEvict.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - - // Add 6, should cause another recent evict - l.Add(6, 6) - if n := l.recent.Len(); n != 3 { - t.Fatalf("bad: %d", n) - } - if n := l.recentEvict.Len(); n != 2 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } -} - -func Test2Q_Resize(t *testing.T) { - l, err := New2Q[int, int](100) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Touch all the entries, should be in t1 - for i := 0; i < 100; i++ { - l.Add(i, i) - } - - evicted := l.Resize(50) - if evicted != 50 { - t.Fatalf("bad: %d", evicted) - } - - if n := l.recent.Len(); n != 50 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - - l, err = New2Q[int, int](100) - if err != nil { - t.Fatalf("err: %v", err) - } - for i := 0; i < 100; i++ { - l.Add(i, i) - } - - for i := 0; i < 50; i++ { - l.Add(i, i) - } - - evicted = l.Resize(50) - if evicted != 50 { - t.Fatalf("bad: %d", evicted) - } - - if n := l.recent.Len(); n != 12 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 38 { - t.Fatalf("bad: %d", n) - } - - l, err = New2Q[int, int](100) - if err != nil { - t.Fatalf("err: %v", err) - } - for i := 0; i < 100; i++ { - l.Add(i, i) - l.Add(i, i) - } - - evicted = l.Resize(50) - if evicted != 50 { - t.Fatalf("bad: %d", evicted) - } - - if n := l.recent.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.frequent.Len(); n != 50 { - t.Fatalf("bad: %d", n) - } -} - -func Test2Q(t *testing.T) { - l, err := New2Q[int, int](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - for i := 0; i < 256; i++ { - l.Add(i, i) - } - if l.Len() != 128 { - t.Fatalf("bad len: %v", l.Len()) - } - if l.Cap() != 128 { - t.Fatalf("expect %d, but %d", 128, l.Cap()) - } - - for i, k := range l.Keys() { - if v, ok := l.Get(k); !ok || v != k || v != i+128 { - t.Fatalf("bad key: %v", k) - } - } - for i, v := range l.Values() { - if v != i+128 { - t.Fatalf("bad key: %v", v) - } - } - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); ok { - t.Fatalf("should be evicted") - } - } - for i := 128; i < 256; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("should not be evicted") - } - } - for i := 128; i < 192; i++ { - l.Remove(i) - if _, ok := l.Get(i); ok { - t.Fatalf("should be deleted") - } - } - - l.Purge() - if l.Len() != 0 { - t.Fatalf("bad len: %v", l.Len()) - } - if _, ok := l.Get(200); ok { - t.Fatalf("should contain nothing") - } -} - -// Test that Contains doesn't update recent-ness -func Test2Q_Contains(t *testing.T) { - l, err := New2Q[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if !l.Contains(1) { - t.Errorf("1 should be contained") - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("Contains should not have updated recent-ness of 1") - } -} - -// Test that Peek doesn't update recent-ness -func Test2Q_Peek(t *testing.T) { - l, err := New2Q[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if v, ok := l.Peek(1); !ok || v != 1 { - t.Errorf("1 should be set to 1: %v, %v", v, ok) - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("should not have updated recent-ness of 1") - } -} diff --git a/common/utils/golang-lru-main/CHANGELOG.md b/common/utils/golang-lru-main/CHANGELOG.md deleted file mode 100644 index a0b285324..000000000 --- a/common/utils/golang-lru-main/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -## Unreleased - -### Improvements - -### Changes - -### Fixed - -### Security diff --git a/common/utils/golang-lru-main/LICENSE b/common/utils/golang-lru-main/LICENSE deleted file mode 100644 index 625865029..000000000 --- a/common/utils/golang-lru-main/LICENSE +++ /dev/null @@ -1,364 +0,0 @@ -Copyright IBM Corp. 2014, 2025 - -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the terms of - a Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a - separate file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether - at the time of the initial grant or subsequently, any and all of the - rights conveyed by this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the License, - by the making, using, selling, offering for sale, having made, import, - or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights to - grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter the - recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, or - limitations of liability) contained within the Source Code Form of the - Covered Software, except that You may alter any license notices to the - extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the - limitations and the code they affect. Such description must be placed in a - text file included with all distributions of the Covered Software under - this License. Except to the extent prohibited by statute or regulation, - such description must be sufficiently detailed for a recipient of ordinary - skill to be able to understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing - basis, if such Contributor fails to notify You of the non-compliance by - some reasonable means prior to 60 days after You have come back into - compliance. Moreover, Your grants from a particular Contributor are - reinstated on an ongoing basis if such Contributor notifies You of the - non-compliance by some reasonable means, this is the first time You have - received notice of non-compliance with this License from such - Contributor, and You become compliant prior to 30 days after Your receipt - of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, - without warranty of any kind, either expressed, implied, or statutory, - including, without limitation, warranties that the Covered Software is free - of defects, merchantable, fit for a particular purpose or non-infringing. - The entire risk as to the quality and performance of the Covered Software - is with You. Should any Covered Software prove defective in any respect, - You (not any Contributor) assume the cost of any necessary servicing, - repair, or correction. This disclaimer of warranty constitutes an essential - part of this License. No use of any Covered Software is authorized under - this License except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from - such party's negligence to the extent applicable law prohibits such - limitation. Some jurisdictions do not allow the exclusion or limitation of - incidental or consequential damages, so this exclusion and limitation may - not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts - of a jurisdiction where the defendant maintains its principal place of - business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. Nothing - in this Section shall prevent a party's ability to bring cross-claims or - counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides that - the language of a contract shall be construed against the drafter shall not - be used to construe this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses If You choose to distribute Source Code Form that is - Incompatible With Secondary Licenses under the terms of this version of - the License, the notice described in Exhibit B of this License must be - attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file in a -relevant directory) where a recipient would be likely to look for such a -notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice - - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. diff --git a/common/utils/golang-lru-main/README.md b/common/utils/golang-lru-main/README.md deleted file mode 100644 index a942eb539..000000000 --- a/common/utils/golang-lru-main/README.md +++ /dev/null @@ -1,79 +0,0 @@ -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/common/utils/golang-lru-main/arc/arc.go b/common/utils/golang-lru-main/arc/arc.go deleted file mode 100644 index 1c3989443..000000000 --- a/common/utils/golang-lru-main/arc/arc.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package arc - -import ( - "sync" - - "github.com/hashicorp/golang-lru/v2/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[K comparable, V any] 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[K, V] // T1 is the LRU for recently accessed items - b1 simplelru.LRUCache[K, struct{}] // B1 is the LRU for evictions from t1 - - t2 simplelru.LRUCache[K, V] // T2 is the LRU for frequently accessed items - b2 simplelru.LRUCache[K, struct{}] // B2 is the LRU for evictions from t2 - - lock sync.RWMutex -} - -// NewARC creates an ARC of the given size -func NewARC[K comparable, V any](size int) (*ARCCache[K, V], error) { - // Create the sub LRUs - b1, err := simplelru.NewLRU[K, struct{}](size, nil) - if err != nil { - return nil, err - } - b2, err := simplelru.NewLRU[K, struct{}](size, nil) - if err != nil { - return nil, err - } - t1, err := simplelru.NewLRU[K, V](size, nil) - if err != nil { - return nil, err - } - t2, err := simplelru.NewLRU[K, V](size, nil) - if err != nil { - return nil, err - } - - // Initialize the ARC - c := &ARCCache[K, V]{ - 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[K, V]) Get(key K) (value V, 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 -} - -// Add adds a value to the cache. -func (c *ARCCache[K, V]) Add(key K, value V) { - 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[K, V]) 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, struct{}{}) - } - } else { - k, _, ok := c.t2.RemoveOldest() - if ok { - c.b2.Add(k, struct{}{}) - } - } -} - -// Len returns the number of cached entries -func (c *ARCCache[K, V]) Len() int { - c.lock.RLock() - defer c.lock.RUnlock() - return c.t1.Len() + c.t2.Len() -} - -// Cap returns the capacity of the cache -func (c *ARCCache[K, V]) Cap() int { - return c.size -} - -// Keys returns all the cached keys -func (c *ARCCache[K, V]) Keys() []K { - c.lock.RLock() - defer c.lock.RUnlock() - k1 := c.t1.Keys() - k2 := c.t2.Keys() - return append(k1, k2...) -} - -// Values returns all the cached values -func (c *ARCCache[K, V]) Values() []V { - c.lock.RLock() - defer c.lock.RUnlock() - v1 := c.t1.Values() - v2 := c.t2.Values() - return append(v1, v2...) -} - -// Remove is used to purge a key from the cache -func (c *ARCCache[K, V]) Remove(key K) { - 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[K, V]) 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[K, V]) Contains(key K) 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[K, V]) Peek(key K) (value V, 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/common/utils/golang-lru-main/arc/arc_test.go b/common/utils/golang-lru-main/arc/arc_test.go deleted file mode 100644 index 4d0ae9a74..000000000 --- a/common/utils/golang-lru-main/arc/arc_test.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package arc - -import ( - "crypto/rand" - "math" - "math/big" - mathrand "math/rand" - "testing" - "time" -) - -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() -} - -func init() { - mathrand.Seed(time.Now().Unix()) -} - -func BenchmarkARC_Rand(b *testing.B) { - l, err := NewARC[int64, int64](8192) - if err != nil { - b.Fatalf("err: %v", err) - } - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - trace[i] = getRand(b) % 32768 - } - - b.ResetTimer() - - var hit, miss int - for i := 0; i < 2*b.N; i++ { - if i%2 == 0 { - l.Add(trace[i], trace[i]) - } else { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func BenchmarkARC_Freq(b *testing.B) { - l, err := NewARC[int64, int64](8192) - if err != nil { - b.Fatalf("err: %v", err) - } - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - if i%2 == 0 { - trace[i] = getRand(b) % 16384 - } else { - trace[i] = getRand(b) % 32768 - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - l.Add(trace[i], trace[i]) - } - var hit, miss int - for i := 0; i < b.N; i++ { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func TestARC_RandomOps(t *testing.T) { - size := 128 - l, err := NewARC[int64, int64](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - n := 200000 - for i := 0; i < n; i++ { - key := getRand(t) % 512 - r := getRand(t) - switch r % 3 { - case 0: - l.Add(key, key) - case 1: - l.Get(key) - case 2: - l.Remove(key) - } - - if l.t1.Len()+l.t2.Len() > size { - t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d", - l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p) - } - if l.b1.Len()+l.b2.Len() > size { - t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d", - l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p) - } - } -} - -func TestARC_Get_RecentToFrequent(t *testing.T) { - l, err := NewARC[int, int](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Touch all the entries, should be in t1 - for i := 0; i < 128; i++ { - l.Add(i, i) - } - if n := l.t1.Len(); n != 128 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - - // Get should upgrade to t2 - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("missing: %d", i) - } - } - if n := l.t1.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 128 { - t.Fatalf("bad: %d", n) - } - - // Get be from t2 - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("missing: %d", i) - } - } - if n := l.t1.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 128 { - t.Fatalf("bad: %d", n) - } -} - -func TestARC_Add_RecentToFrequent(t *testing.T) { - l, err := NewARC[int, int](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Add initially to t1 - l.Add(1, 1) - if n := l.t1.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - - // Add should upgrade to t2 - l.Add(1, 1) - if n := l.t1.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - - // Add should remain in t2 - l.Add(1, 1) - if n := l.t1.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } -} - -func TestARC_Adaptive(t *testing.T) { - l, err := NewARC[int, int](4) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Fill t1 - for i := 0; i < 4; i++ { - l.Add(i, i) - } - if n := l.t1.Len(); n != 4 { - t.Fatalf("bad: %d", n) - } - - // Move to t2 - l.Get(0) - l.Get(1) - if n := l.t2.Len(); n != 2 { - t.Fatalf("bad: %d", n) - } - - // Evict from t1 - l.Add(4, 4) - if n := l.b1.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - - // Current state - // t1 : (MRU) [4, 3] (LRU) - // t2 : (MRU) [1, 0] (LRU) - // b1 : (MRU) [2] (LRU) - // b2 : (MRU) [] (LRU) - - // Add 2, should cause hit on b1 - l.Add(2, 2) - if n := l.b1.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - if l.p != 1 { - t.Fatalf("bad: %d", l.p) - } - if n := l.t2.Len(); n != 3 { - t.Fatalf("bad: %d", n) - } - - // Current state - // t1 : (MRU) [4] (LRU) - // t2 : (MRU) [2, 1, 0] (LRU) - // b1 : (MRU) [3] (LRU) - // b2 : (MRU) [] (LRU) - - // Add 4, should migrate to t2 - l.Add(4, 4) - if n := l.t1.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 4 { - t.Fatalf("bad: %d", n) - } - - // Current state - // t1 : (MRU) [] (LRU) - // t2 : (MRU) [4, 2, 1, 0] (LRU) - // b1 : (MRU) [3] (LRU) - // b2 : (MRU) [] (LRU) - - // Add 4, should evict to b2 - l.Add(5, 5) - if n := l.t1.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 3 { - t.Fatalf("bad: %d", n) - } - if n := l.b2.Len(); n != 1 { - t.Fatalf("bad: %d", n) - } - - // Current state - // t1 : (MRU) [5] (LRU) - // t2 : (MRU) [4, 2, 1] (LRU) - // b1 : (MRU) [3] (LRU) - // b2 : (MRU) [0] (LRU) - - // Add 0, should decrease p - l.Add(0, 0) - if n := l.t1.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if n := l.t2.Len(); n != 4 { - t.Fatalf("bad: %d", n) - } - if n := l.b1.Len(); n != 2 { - t.Fatalf("bad: %d", n) - } - if n := l.b2.Len(); n != 0 { - t.Fatalf("bad: %d", n) - } - if l.p != 0 { - t.Fatalf("bad: %d", l.p) - } - - // Current state - // t1 : (MRU) [] (LRU) - // t2 : (MRU) [0, 4, 2, 1] (LRU) - // b1 : (MRU) [5, 3] (LRU) - // b2 : (MRU) [0] (LRU) -} - -func TestARC(t *testing.T) { - l, err := NewARC[int, int](128) - if err != nil { - t.Fatalf("err: %v", err) - } - - for i := 0; i < 256; i++ { - l.Add(i, i) - } - if l.Len() != 128 { - t.Fatalf("bad len: %v", l.Len()) - } - if l.Cap() != 128 { - t.Fatalf("expect %d, but %d", 128, l.Cap()) - } - - for i, k := range l.Keys() { - if v, ok := l.Get(k); !ok || v != k || v != i+128 { - t.Fatalf("bad key: %v", k) - } - } - for i, v := range l.Values() { - if v != i+128 { - t.Fatalf("bad value: %v", v) - } - } - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); ok { - t.Fatalf("should be evicted") - } - } - for i := 128; i < 256; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("should not be evicted") - } - } - for i := 128; i < 192; i++ { - l.Remove(i) - if _, ok := l.Get(i); ok { - t.Fatalf("should be deleted") - } - } - if l.Cap() != 128 { - t.Fatalf("expect %d, but %d", 128, l.Cap()) - } - - l.Purge() - if l.Len() != 0 { - t.Fatalf("bad len: %v", l.Len()) - } - if _, ok := l.Get(200); ok { - t.Fatalf("should contain nothing") - } - if l.Cap() != 128 { - t.Fatalf("expect %d, but %d", 128, l.Cap()) - } -} - -// Test that Contains doesn't update recent-ness -func TestARC_Contains(t *testing.T) { - l, err := NewARC[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if !l.Contains(1) { - t.Errorf("1 should be contained") - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("Contains should not have updated recent-ness of 1") - } -} - -// Test that Peek doesn't update recent-ness -func TestARC_Peek(t *testing.T) { - l, err := NewARC[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if v, ok := l.Peek(1); !ok || v != 1 { - t.Errorf("1 should be set to 1: %v, %v", v, ok) - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("should not have updated recent-ness of 1") - } -} diff --git a/common/utils/golang-lru-main/arc/go.mod b/common/utils/golang-lru-main/arc/go.mod deleted file mode 100644 index 21dd4c2b9..000000000 --- a/common/utils/golang-lru-main/arc/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/hashicorp/golang-lru/arc/v2 - -go 1.18 - -require github.com/hashicorp/golang-lru/v2 v2.0.7 diff --git a/common/utils/golang-lru-main/arc/go.sum b/common/utils/golang-lru-main/arc/go.sum deleted file mode 100644 index a33c54a15..000000000 --- a/common/utils/golang-lru-main/arc/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -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= diff --git a/common/utils/golang-lru-main/doc.go b/common/utils/golang-lru-main/doc.go deleted file mode 100644 index dd6aed555..000000000 --- a/common/utils/golang-lru-main/doc.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// 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/common/utils/golang-lru-main/expirable/expirable_lru.go b/common/utils/golang-lru-main/expirable/expirable_lru.go deleted file mode 100644 index 486c263ca..000000000 --- a/common/utils/golang-lru-main/expirable/expirable_lru.go +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// 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. -// Expired entries are filtered out. -func (c *LRU[K, V]) Keys() []K { - c.mu.Lock() - defer c.mu.Unlock() - keys := make([]K, 0, len(c.items)) - now := time.Now() - for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { - if now.After(ent.ExpiresAt) { - continue - } - 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, 0, len(c.items)) - now := time.Now() - for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { - if now.After(ent.ExpiresAt) { - continue - } - values = append(values, ent.Value) - } - 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) -} - -// Cap returns the capacity of the cache -func (c *LRU[K, V]) Cap() int { - return c.size -} \ No newline at end of file diff --git a/common/utils/golang-lru-main/expirable/expirable_lru_test.go b/common/utils/golang-lru-main/expirable/expirable_lru_test.go deleted file mode 100644 index b3844f648..000000000 --- a/common/utils/golang-lru-main/expirable/expirable_lru_test.go +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package expirable - -import ( - "crypto/rand" - "fmt" - "math" - "math/big" - "reflect" - "sync" - "testing" - "time" - - "github.com/hashicorp/golang-lru/v2/simplelru" -) - -func BenchmarkLRU_Rand_NoExpire(b *testing.B) { - l := NewLRU[int64, int64](8192, nil, 0) - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - trace[i] = getRand(b) % 32768 - } - - b.ResetTimer() - - var hit, miss int - for i := 0; i < 2*b.N; i++ { - if i%2 == 0 { - l.Add(trace[i], trace[i]) - } else { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func BenchmarkLRU_Freq_NoExpire(b *testing.B) { - l := NewLRU[int64, int64](8192, nil, 0) - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - if i%2 == 0 { - trace[i] = getRand(b) % 16384 - } else { - trace[i] = getRand(b) % 32768 - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - l.Add(trace[i], trace[i]) - } - var hit, miss int - for i := 0; i < b.N; i++ { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func BenchmarkLRU_Rand_WithExpire(b *testing.B) { - l := NewLRU[int64, int64](8192, nil, time.Millisecond*10) - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - trace[i] = getRand(b) % 32768 - } - - b.ResetTimer() - - var hit, miss int - for i := 0; i < 2*b.N; i++ { - if i%2 == 0 { - l.Add(trace[i], trace[i]) - } else { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func BenchmarkLRU_Freq_WithExpire(b *testing.B) { - l := NewLRU[int64, int64](8192, nil, time.Millisecond*10) - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - if i%2 == 0 { - trace[i] = getRand(b) % 16384 - } else { - trace[i] = getRand(b) % 32768 - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - l.Add(trace[i], trace[i]) - } - var hit, miss int - for i := 0; i < b.N; i++ { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func TestLRUInterface(_ *testing.T) { - var _ simplelru.LRUCache[int, int] = &LRU[int, int]{} -} - -func TestLRUNoPurge(t *testing.T) { - lc := NewLRU[string, string](10, nil, 0) - - lc.Add("key1", "val1") - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - - v, ok := lc.Peek("key1") - if v != "val1" { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - - if !lc.Contains("key1") { - t.Fatalf("should contain key1") - } - if lc.Contains("key2") { - t.Fatalf("should not contain key2") - } - - v, ok = lc.Peek("key2") - if v != "" { - t.Fatalf("should be empty") - } - if ok { - t.Fatalf("should be false") - } - - if !reflect.DeepEqual(lc.Keys(), []string{"key1"}) { - t.Fatalf("value differs from expected") - } - - if lc.Resize(0) != 0 { - t.Fatalf("evicted count differs from expected") - } - if lc.Resize(2) != 0 { - t.Fatalf("evicted count differs from expected") - } - lc.Add("key2", "val2") - if lc.Resize(1) != 1 { - t.Fatalf("evicted count differs from expected") - } -} - -func TestLRUEdgeCases(t *testing.T) { - lc := NewLRU[string, *string](2, nil, 0) - - // Adding a nil value - lc.Add("key1", nil) - - value, exists := lc.Get("key1") - if value != nil || !exists { - t.Fatalf("unexpected value or existence flag for key1: value=%v, exists=%v", value, exists) - } - - // Adding an entry with the same key but different value - newVal := "val1" - lc.Add("key1", &newVal) - - value, exists = lc.Get("key1") - if value != &newVal || !exists { - t.Fatalf("unexpected value or existence flag for key1: value=%v, exists=%v", value, exists) - } -} - -func TestLRU_Values(t *testing.T) { - lc := NewLRU[string, string](3, nil, 0) - - lc.Add("key1", "val1") - lc.Add("key2", "val2") - lc.Add("key3", "val3") - - values := lc.Values() - if !reflect.DeepEqual(values, []string{"val1", "val2", "val3"}) { - t.Fatalf("values differs from expected") - } -} - -// func TestExpirableMultipleClose(_ *testing.T) { -// lc := NewLRU[string, string](10, nil, 0) -// lc.Close() -// // should not panic -// lc.Close() -// } - -func TestLRUWithPurge(t *testing.T) { - var evicted []string - lc := NewLRU(10, func(key string, value string) { evicted = append(evicted, key, value) }, 150*time.Millisecond) - - k, v, ok := lc.GetOldest() - if k != "" { - t.Fatalf("should be empty") - } - if v != "" { - t.Fatalf("should be empty") - } - if ok { - t.Fatalf("should be false") - } - - lc.Add("key1", "val1") - - time.Sleep(100 * time.Millisecond) // not enough to expire - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - - v, ok = lc.Get("key1") - if v != "val1" { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - - time.Sleep(200 * time.Millisecond) // expire - v, ok = lc.Get("key1") - if ok { - t.Fatalf("should be false") - } - if v != "" { - t.Fatalf("should be nil") - } - - if lc.Len() != 0 { - t.Fatalf("length differs from expected") - } - if !reflect.DeepEqual(evicted, []string{"key1", "val1"}) { - t.Fatalf("value differs from expected") - } - - // add new entry - lc.Add("key2", "val2") - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - - k, v, ok = lc.GetOldest() - if k != "key2" { - t.Fatalf("value differs from expected") - } - if v != "val2" { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - - // DeleteExpired, nothing deleted - lc.deleteExpired() - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - if !reflect.DeepEqual(evicted, []string{"key1", "val1"}) { - t.Fatalf("value differs from expected") - } - - // Purge, cache should be clean - lc.Purge() - if lc.Len() != 0 { - t.Fatalf("length differs from expected") - } - if !reflect.DeepEqual(evicted, []string{"key1", "val1", "key2", "val2"}) { - t.Fatalf("value differs from expected") - } -} - -func TestLRUWithPurgeEnforcedBySize(t *testing.T) { - lc := NewLRU[string, string](10, nil, time.Hour) - - for i := 0; i < 100; i++ { - i := i - lc.Add(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i)) - v, ok := lc.Get(fmt.Sprintf("key%d", i)) - if v != fmt.Sprintf("val%d", i) { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - if lc.Len() > 20 { - t.Fatalf("length should be less than 20") - } - } - - if lc.Len() != 10 { - t.Fatalf("length differs from expected") - } -} - -func TestLRUConcurrency(t *testing.T) { - lc := NewLRU[string, string](0, nil, 0) - wg := sync.WaitGroup{} - wg.Add(1000) - for i := 0; i < 1000; i++ { - go func(i int) { - lc.Add(fmt.Sprintf("key-%d", i/10), fmt.Sprintf("val-%d", i/10)) - wg.Done() - }(i) - } - wg.Wait() - if lc.Len() != 100 { - t.Fatalf("length differs from expected") - } -} - -func TestLRUInvalidateAndEvict(t *testing.T) { - var evicted int - lc := NewLRU(-1, func(_, _ string) { evicted++ }, 0) - - lc.Add("key1", "val1") - lc.Add("key2", "val2") - - val, ok := lc.Get("key1") - if !ok { - t.Fatalf("should be true") - } - if val != "val1" { - t.Fatalf("value differs from expected") - } - if evicted != 0 { - t.Fatalf("value differs from expected") - } - - lc.Remove("key1") - if evicted != 1 { - t.Fatalf("value differs from expected") - } - val, ok = lc.Get("key1") - if val != "" { - t.Fatalf("should be empty") - } - if ok { - t.Fatalf("should be false") - } -} - -func TestLoadingExpired(t *testing.T) { - lc := NewLRU[string, string](0, nil, time.Millisecond*5) - - lc.Add("key1", "val1") - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - - v, ok := lc.Peek("key1") - if v != "val1" { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - - v, ok = lc.Get("key1") - if v != "val1" { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - - for { - result, ok := lc.Get("key1") - if ok && result == "" { - t.Fatalf("ok should return a result") - } - if !ok { - break - } - } - - time.Sleep(time.Millisecond * 100) // wait for expiration reaper - if lc.Len() != 0 { - t.Fatalf("length differs from expected") - } - - v, ok = lc.Peek("key1") - if v != "" { - t.Fatalf("should be empty") - } - if ok { - t.Fatalf("should be false") - } - - v, ok = lc.Get("key1") - if v != "" { - t.Fatalf("should be empty") - } - if ok { - t.Fatalf("should be false") - } -} - -func TestLRURemoveOldest(t *testing.T) { - lc := NewLRU[string, string](2, nil, 0) - - if lc.Cap() != 2 { - t.Fatalf("expect cap is 2") - } - - k, v, ok := lc.RemoveOldest() - if k != "" { - t.Fatalf("should be empty") - } - if v != "" { - t.Fatalf("should be empty") - } - if ok { - t.Fatalf("should be false") - } - - ok = lc.Remove("non_existent") - if ok { - t.Fatalf("should be false") - } - - lc.Add("key1", "val1") - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - - v, ok = lc.Get("key1") - if !ok { - t.Fatalf("should be true") - } - if v != "val1" { - t.Fatalf("value differs from expected") - } - - if !reflect.DeepEqual(lc.Keys(), []string{"key1"}) { - t.Fatalf("value differs from expected") - } - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } - - lc.Add("key2", "val2") - if !reflect.DeepEqual(lc.Keys(), []string{"key1", "key2"}) { - t.Fatalf("value differs from expected") - } - if lc.Len() != 2 { - t.Fatalf("length differs from expected") - } - - k, v, ok = lc.RemoveOldest() - if k != "key1" { - t.Fatalf("value differs from expected") - } - if v != "val1" { - t.Fatalf("value differs from expected") - } - if !ok { - t.Fatalf("should be true") - } - - if !reflect.DeepEqual(lc.Keys(), []string{"key2"}) { - t.Fatalf("value differs from expected") - } - if lc.Len() != 1 { - t.Fatalf("length differs from expected") - } -} - -func ExampleLRU() { - // make cache with 10ms TTL and 5 max keys - cache := 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 * 100) - - // 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 -} - -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() -} - -func (c *LRU[K, V]) wantKeys(t *testing.T, want []K) { - t.Helper() - got := c.Keys() - if !reflect.DeepEqual(got, want) { - t.Errorf("wrong keys got: %v, want: %v ", got, want) - } -} - -func TestCache_EvictionSameKey(t *testing.T) { - var evictedKeys []int - - cache := NewLRU[int, struct{}]( - 2, - func(key int, _ struct{}) { - evictedKeys = append(evictedKeys, key) - }, - 0) - - if evicted := cache.Add(1, struct{}{}); evicted { - t.Error("First 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1}) - - if evicted := cache.Add(2, struct{}{}); evicted { - t.Error("2: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - if evicted := cache.Add(1, struct{}{}); evicted { - t.Error("Second 1: got unexpected eviction") - } - cache.wantKeys(t, []int{2, 1}) - - if evicted := cache.Add(3, struct{}{}); !evicted { - t.Error("3: did not get expected eviction") - } - cache.wantKeys(t, []int{1, 3}) - - want := []int{2} - if !reflect.DeepEqual(evictedKeys, want) { - t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want) - } -} diff --git a/common/utils/golang-lru-main/go.mod b/common/utils/golang-lru-main/go.mod deleted file mode 100644 index 3db952904..000000000 --- a/common/utils/golang-lru-main/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/hashicorp/golang-lru/v2 - -go 1.19 diff --git a/common/utils/golang-lru-main/internal/list.go b/common/utils/golang-lru-main/internal/list.go deleted file mode 100644 index 5cd74a034..000000000 --- a/common/utils/golang-lru-main/internal/list.go +++ /dev/null @@ -1,142 +0,0 @@ -// 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/common/utils/golang-lru-main/lru.go b/common/utils/golang-lru-main/lru.go deleted file mode 100644 index 8d2ca6f18..000000000 --- a/common/utils/golang-lru-main/lru.go +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package lru - -import ( - "sync" - - "github.com/hashicorp/golang-lru/v2/simplelru" -) - -const ( - // DefaultEvictedBufferSize defines the default buffer size to store evicted key/val - DefaultEvictedBufferSize = 16 -) - -// Cache is a thread-safe fixed size LRU cache. -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[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[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[K, V]{ - onEvictedCB: onEvicted, - } - if onEvicted != nil { - c.initEvictBuffers() - onEvicted = c.onEvicted - } - c.lru, err = simplelru.NewLRU(size, onEvicted) - return -} - -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[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[K, V]) Purge() { - var ks []K - var vs []V - c.lock.Lock() - c.lru.Purge() - if c.onEvictedCB != nil && len(c.evictedKeys) > 0 { - ks, vs = c.evictedKeys, c.evictedVals - c.initEvictBuffers() - } - c.lock.Unlock() - // invoke callback outside of critical section - if c.onEvictedCB != nil { - for i := 0; i < len(ks); i++ { - c.onEvictedCB(ks[i], vs[i]) - } - } -} - -// Add adds a value to the cache. Returns true if an eviction occurred. -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 { - k, v = c.evictedKeys[0], c.evictedVals[0] - c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] - } - c.lock.Unlock() - if c.onEvictedCB != nil && evicted { - c.onEvictedCB(k, v) - } - return -} - -// Get looks up a key's value from the cache. -func (c *Cache[K, V]) Get(key K) (value V, ok bool) { - c.lock.Lock() - value, ok = c.lru.Get(key) - c.lock.Unlock() - return value, ok -} - -// Contains checks if a key is in the cache, without updating the -// recent-ness or deleting it for being stale. -func (c *Cache[K, V]) Contains(key K) bool { - c.lock.RLock() - containKey := c.lru.Contains(key) - c.lock.RUnlock() - return containKey -} - -// Peek returns the key value (or undefined if not found) without updating -// the "recently used"-ness of the key. -func (c *Cache[K, V]) Peek(key K) (value V, ok bool) { - c.lock.RLock() - value, ok = c.lru.Peek(key) - c.lock.RUnlock() - return value, ok -} - -// 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[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() - return true, false - } - evicted = c.lru.Add(key, value) - if c.onEvictedCB != nil && evicted { - k, v = c.evictedKeys[0], c.evictedVals[0] - c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] - } - c.lock.Unlock() - if c.onEvictedCB != nil && evicted { - c.onEvictedCB(k, v) - } - return false, evicted -} - -// 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[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 { - c.lock.Unlock() - return previous, true, false - } - evicted = c.lru.Add(key, value) - if c.onEvictedCB != nil && evicted { - k, v = c.evictedKeys[0], c.evictedVals[0] - c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] - } - c.lock.Unlock() - if c.onEvictedCB != nil && evicted { - c.onEvictedCB(k, v) - } - return -} - -// Remove removes the provided key from the cache. -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 { - k, v = c.evictedKeys[0], c.evictedVals[0] - c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] - } - c.lock.Unlock() - if c.onEvictedCB != nil && present { - c.onEvictedCB(k, v) - } - return -} - -// Resize changes the cache size. -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 { - ks, vs = c.evictedKeys, c.evictedVals - c.initEvictBuffers() - } - c.lock.Unlock() - if c.onEvictedCB != nil && evicted > 0 { - for i := 0; i < len(ks); i++ { - c.onEvictedCB(ks[i], vs[i]) - } - } - return evicted -} - -// RemoveOldest removes the oldest item from the cache. -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 { - k, v = c.evictedKeys[0], c.evictedVals[0] - c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] - } - c.lock.Unlock() - if c.onEvictedCB != nil && ok { - c.onEvictedCB(k, v) - } - return -} - -// GetOldest returns the oldest entry -func (c *Cache[K, V]) GetOldest() (key K, value V, ok bool) { - c.lock.RLock() - key, value, ok = c.lru.GetOldest() - c.lock.RUnlock() - return -} - -// Keys returns a slice of the keys in the cache, from oldest to newest. -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[K, V]) Len() int { - c.lock.RLock() - length := c.lru.Len() - c.lock.RUnlock() - return length -} - -// Cap returns the capacity of the cache -func (c *Cache[K, V]) Cap() int { - return c.lru.Cap() -} diff --git a/common/utils/golang-lru-main/lru_test.go b/common/utils/golang-lru-main/lru_test.go deleted file mode 100644 index 513062d3b..000000000 --- a/common/utils/golang-lru-main/lru_test.go +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package lru - -import ( - "reflect" - "testing" -) - -func BenchmarkLRU_Rand(b *testing.B) { - l, err := New[int64, int64](8192) - if err != nil { - b.Fatalf("err: %v", err) - } - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - trace[i] = getRand(b) % 32768 - } - - b.ResetTimer() - - var hit, miss int - for i := 0; i < 2*b.N; i++ { - if i%2 == 0 { - l.Add(trace[i], trace[i]) - } else { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func BenchmarkLRU_Freq(b *testing.B) { - l, err := New[int64, int64](8192) - if err != nil { - b.Fatalf("err: %v", err) - } - - trace := make([]int64, b.N*2) - for i := 0; i < b.N*2; i++ { - if i%2 == 0 { - trace[i] = getRand(b) % 16384 - } else { - trace[i] = getRand(b) % 32768 - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - l.Add(trace[i], trace[i]) - } - var hit, miss int - for i := 0; i < b.N; i++ { - if _, ok := l.Get(trace[i]); ok { - hit++ - } else { - miss++ - } - } - b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) -} - -func TestLRU(t *testing.T) { - evictCounter := 0 - onEvicted := func(k int, v int) { - if k != v { - t.Fatalf("Evict values not equal (%v!=%v)", k, v) - } - evictCounter++ - } - l, err := NewWithEvict(128, onEvicted) - if err != nil { - t.Fatalf("err: %v", err) - } - - for i := 0; i < 256; i++ { - l.Add(i, i) - } - if l.Len() != 128 { - t.Fatalf("bad len: %v", l.Len()) - } - if l.Cap() != 128 { - t.Fatalf("expect %d, but %d", 128, l.Cap()) - } - - if evictCounter != 128 { - t.Fatalf("bad evict count: %v", evictCounter) - } - - for i, k := range l.Keys() { - if v, ok := l.Get(k); !ok || v != k || v != i+128 { - t.Fatalf("bad key: %v", k) - } - } - for i, v := range l.Values() { - if v != i+128 { - t.Fatalf("bad value: %v", v) - } - } - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); ok { - t.Fatalf("should be evicted") - } - } - for i := 128; i < 256; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("should not be evicted") - } - } - for i := 128; i < 192; i++ { - l.Remove(i) - if _, ok := l.Get(i); ok { - t.Fatalf("should be deleted") - } - } - - l.Get(192) // expect 192 to be last key in l.Keys() - - for i, k := range l.Keys() { - if (i < 63 && k != i+193) || (i == 63 && k != 192) { - t.Fatalf("out of order key: %v", k) - } - } - - l.Purge() - if l.Len() != 0 { - t.Fatalf("bad len: %v", l.Len()) - } - if _, ok := l.Get(200); ok { - t.Fatalf("should contain nothing") - } -} - -// test that Add returns true/false if an eviction occurred -func TestLRUAdd(t *testing.T) { - evictCounter := 0 - onEvicted := func(k int, v int) { - evictCounter++ - } - - l, err := NewWithEvict(1, onEvicted) - if err != nil { - t.Fatalf("err: %v", err) - } - - if l.Add(1, 1) == true || evictCounter != 0 { - t.Errorf("should not have an eviction") - } - if l.Add(2, 2) == false || evictCounter != 1 { - t.Errorf("should have an eviction") - } -} - -// test that Contains doesn't update recent-ness -func TestLRUContains(t *testing.T) { - l, err := New[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if !l.Contains(1) { - t.Errorf("1 should be contained") - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("Contains should not have updated recent-ness of 1") - } -} - -// test that ContainsOrAdd doesn't update recent-ness -func TestLRUContainsOrAdd(t *testing.T) { - l, err := New[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - contains, evict := l.ContainsOrAdd(1, 1) - if !contains { - t.Errorf("1 should be contained") - } - if evict { - t.Errorf("nothing should be evicted here") - } - - l.Add(3, 3) - contains, evict = l.ContainsOrAdd(1, 1) - if contains { - t.Errorf("1 should not have been contained") - } - if !evict { - t.Errorf("an eviction should have occurred") - } - if !l.Contains(1) { - t.Errorf("now 1 should be contained") - } -} - -// test that PeekOrAdd doesn't update recent-ness -func TestLRUPeekOrAdd(t *testing.T) { - l, err := New[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - previous, contains, evict := l.PeekOrAdd(1, 1) - if !contains { - t.Errorf("1 should be contained") - } - if evict { - t.Errorf("nothing should be evicted here") - } - if previous != 1 { - t.Errorf("previous is not equal to 1") - } - - l.Add(3, 3) - contains, evict = l.ContainsOrAdd(1, 1) - if contains { - t.Errorf("1 should not have been contained") - } - if !evict { - t.Errorf("an eviction should have occurred") - } - if !l.Contains(1) { - t.Errorf("now 1 should be contained") - } -} - -// test that Peek doesn't update recent-ness -func TestLRUPeek(t *testing.T) { - l, err := New[int, int](2) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if v, ok := l.Peek(1); !ok || v != 1 { - t.Errorf("1 should be set to 1: %v, %v", v, ok) - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("should not have updated recent-ness of 1") - } -} - -// test that Resize can upsize and downsize -func TestLRUResize(t *testing.T) { - onEvictCounter := 0 - onEvicted := func(k int, v int) { - onEvictCounter++ - } - l, err := NewWithEvict(2, onEvicted) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Downsize - l.Add(1, 1) - l.Add(2, 2) - evicted := l.Resize(1) - if evicted != 1 { - t.Errorf("1 element should have been evicted: %v", evicted) - } - if onEvictCounter != 1 { - t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter) - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("Element 1 should have been evicted") - } - - // Upsize - evicted = l.Resize(2) - if evicted != 0 { - t.Errorf("0 elements should have been evicted: %v", evicted) - } - - l.Add(4, 4) - if !l.Contains(3) || !l.Contains(4) { - t.Errorf("Cache should have contained 2 elements") - } -} - -func (c *Cache[K, V]) wantKeys(t *testing.T, want []K) { - t.Helper() - got := c.Keys() - if !reflect.DeepEqual(got, want) { - t.Errorf("wrong keys got: %v, want: %v ", got, want) - } -} - -func TestCache_EvictionSameKey(t *testing.T) { - t.Run("Add", func(t *testing.T) { - var evictedKeys []int - - cache, _ := NewWithEvict( - 2, - func(key int, _ struct{}) { - evictedKeys = append(evictedKeys, key) - }) - - if evicted := cache.Add(1, struct{}{}); evicted { - t.Error("First 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1}) - - if evicted := cache.Add(2, struct{}{}); evicted { - t.Error("2: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - if evicted := cache.Add(1, struct{}{}); evicted { - t.Error("Second 1: got unexpected eviction") - } - cache.wantKeys(t, []int{2, 1}) - - if evicted := cache.Add(3, struct{}{}); !evicted { - t.Error("3: did not get expected eviction") - } - cache.wantKeys(t, []int{1, 3}) - - want := []int{2} - if !reflect.DeepEqual(evictedKeys, want) { - t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want) - } - }) - - t.Run("ContainsOrAdd", func(t *testing.T) { - var evictedKeys []int - - cache, _ := NewWithEvict( - 2, - func(key int, _ struct{}) { - evictedKeys = append(evictedKeys, key) - }) - - contained, evicted := cache.ContainsOrAdd(1, struct{}{}) - if contained { - t.Error("First 1: got unexpected contained") - } - if evicted { - t.Error("First 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1}) - - contained, evicted = cache.ContainsOrAdd(2, struct{}{}) - if contained { - t.Error("2: got unexpected contained") - } - if evicted { - t.Error("2: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - contained, evicted = cache.ContainsOrAdd(1, struct{}{}) - if !contained { - t.Error("Second 1: did not get expected contained") - } - if evicted { - t.Error("Second 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - contained, evicted = cache.ContainsOrAdd(3, struct{}{}) - if contained { - t.Error("3: got unexpected contained") - } - if !evicted { - t.Error("3: did not get expected eviction") - } - cache.wantKeys(t, []int{2, 3}) - - want := []int{1} - if !reflect.DeepEqual(evictedKeys, want) { - t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want) - } - }) - - t.Run("PeekOrAdd", func(t *testing.T) { - var evictedKeys []int - - cache, _ := NewWithEvict( - 2, - func(key int, _ struct{}) { - evictedKeys = append(evictedKeys, key) - }) - - _, contained, evicted := cache.PeekOrAdd(1, struct{}{}) - if contained { - t.Error("First 1: got unexpected contained") - } - if evicted { - t.Error("First 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1}) - - _, contained, evicted = cache.PeekOrAdd(2, struct{}{}) - if contained { - t.Error("2: got unexpected contained") - } - if evicted { - t.Error("2: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - _, contained, evicted = cache.PeekOrAdd(1, struct{}{}) - if !contained { - t.Error("Second 1: did not get expected contained") - } - if evicted { - t.Error("Second 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - _, contained, evicted = cache.PeekOrAdd(3, struct{}{}) - if contained { - t.Error("3: got unexpected contained") - } - if !evicted { - t.Error("3: did not get expected eviction") - } - cache.wantKeys(t, []int{2, 3}) - - want := []int{1} - if !reflect.DeepEqual(evictedKeys, want) { - t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want) - } - }) -} diff --git a/common/utils/golang-lru-main/simplelru/LICENSE_list b/common/utils/golang-lru-main/simplelru/LICENSE_list deleted file mode 100644 index c4764e6b2..000000000 --- a/common/utils/golang-lru-main/simplelru/LICENSE_list +++ /dev/null @@ -1,29 +0,0 @@ -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/common/utils/golang-lru-main/simplelru/lru.go b/common/utils/golang-lru-main/simplelru/lru.go deleted file mode 100644 index 047f6d02f..000000000 --- a/common/utils/golang-lru-main/simplelru/lru.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package simplelru - -import ( - "errors" - - "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 non-thread safe fixed size LRU cache -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] -} - -// NewLRU constructs an LRU of the given size -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[K, V]{ - size: size, - 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[K, V]) Purge() { - for k, v := range c.items { - if c.onEvict != nil { - c.onEvict(k, v.Value) - } - delete(c.items, k) - } - c.evictList.Init() -} - -// Add adds a value to the cache. Returns true if an eviction occurred. -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 = value - return false - } - - // Add new item - ent := c.evictList.PushFront(key, value) - c.items[key] = ent - - evict := 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) { - if ent, ok := c.items[key]; ok { - 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) { - _, 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) { - var ent *internal.Entry[K, V] - if ent, ok = c.items[key]; ok { - 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) (present bool) { - 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) { - 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) { - 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 { - keys := make([]K, c.evictList.Length()) - i := 0 - 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[K, V]) Len() int { - return c.evictList.Length() -} - -// Cap returns the capacity of the cache -func (c *LRU[K, V]) Cap() int { - return c.size -} - -// Resize changes the cache size. -func (c *LRU[K, V]) Resize(size int) (evicted int) { - diff := c.Len() - size - if diff < 0 { - diff = 0 - } - for i := 0; i < diff; i++ { - c.removeOldest() - } - c.size = size - return diff -} - -// removeOldest removes the oldest item from the cache. -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[K, V]) removeElement(e *internal.Entry[K, V]) { - c.evictList.Remove(e) - delete(c.items, e.Key) - if c.onEvict != nil { - c.onEvict(e.Key, e.Value) - } -} diff --git a/common/utils/golang-lru-main/simplelru/lru_interface.go b/common/utils/golang-lru-main/simplelru/lru_interface.go deleted file mode 100644 index 3508d26ff..000000000 --- a/common/utils/golang-lru-main/simplelru/lru_interface.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// 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[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 K, value V) bool - - // Returns key's value from the cache and - // updates the "recently used"-ness of the key. #value, isFound - Get(key K) (value V, ok bool) - - // Checks if a key exists in cache without updating the recent-ness. - Contains(key K) (ok bool) - - // Returns key's value without updating the "recently used"-ness of the key. - Peek(key K) (value V, ok bool) - - // Removes a key from the cache. - Remove(key K) bool - - // Removes the oldest entry from cache. - RemoveOldest() (K, V, bool) - - // Returns the oldest entry from the cache. #key, value, isFound - GetOldest() (K, V, bool) - - // Returns a slice of the keys in the cache, from oldest to newest. - 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 - - // Returns the capacity of the cache. - Cap() int - - // Clears all cache entries. - Purge() - - // Resizes cache, returning number evicted - Resize(int) int -} diff --git a/common/utils/golang-lru-main/simplelru/lru_test.go b/common/utils/golang-lru-main/simplelru/lru_test.go deleted file mode 100644 index 34176c3cc..000000000 --- a/common/utils/golang-lru-main/simplelru/lru_test.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package simplelru - -import ( - "reflect" - "testing" -) - -func TestLRU(t *testing.T) { - evictCounter := 0 - onEvicted := func(k int, v int) { - if k != v { - t.Fatalf("Evict values not equal (%v!=%v)", k, v) - } - evictCounter++ - } - l, err := NewLRU(128, onEvicted) - if err != nil { - t.Fatalf("err: %v", err) - } - - for i := 0; i < 256; i++ { - l.Add(i, i) - } - if l.Len() != 128 { - t.Fatalf("bad len: %v", l.Len()) - } - if l.Cap() != 128 { - t.Fatalf("expect %d, but %d", 128, l.Cap()) - } - - if evictCounter != 128 { - t.Fatalf("bad evict count: %v", evictCounter) - } - - for i, k := range l.Keys() { - if v, ok := l.Get(k); !ok || v != k || v != i+128 { - t.Fatalf("bad key: %v", k) - } - } - for i, v := range l.Values() { - if v != i+128 { - t.Fatalf("bad value: %v", v) - } - } - for i := 0; i < 128; i++ { - if _, ok := l.Get(i); ok { - t.Fatalf("should be evicted") - } - } - for i := 128; i < 256; i++ { - if _, ok := l.Get(i); !ok { - t.Fatalf("should not be evicted") - } - } - for i := 128; i < 192; i++ { - if ok := l.Remove(i); !ok { - t.Fatalf("should be contained") - } - if ok := l.Remove(i); ok { - t.Fatalf("should not be contained") - } - if _, ok := l.Get(i); ok { - t.Fatalf("should be deleted") - } - } - - l.Get(192) // expect 192 to be last key in l.Keys() - - for i, k := range l.Keys() { - if (i < 63 && k != i+193) || (i == 63 && k != 192) { - t.Fatalf("out of order key: %v", k) - } - } - - l.Purge() - if l.Len() != 0 { - t.Fatalf("bad len: %v", l.Len()) - } - if _, ok := l.Get(200); ok { - t.Fatalf("should contain nothing") - } -} - -func TestLRU_GetOldest_RemoveOldest(t *testing.T) { - l, err := NewLRU[int, int](128, nil) - if err != nil { - t.Fatalf("err: %v", err) - } - for i := 0; i < 256; i++ { - l.Add(i, i) - } - k, _, ok := l.GetOldest() - if !ok { - t.Fatalf("missing") - } - if k != 128 { - t.Fatalf("bad: %v", k) - } - - k, _, ok = l.RemoveOldest() - if !ok { - t.Fatalf("missing") - } - if k != 128 { - t.Fatalf("bad: %v", k) - } - - k, _, ok = l.RemoveOldest() - if !ok { - t.Fatalf("missing") - } - if k != 129 { - t.Fatalf("bad: %v", k) - } -} - -// Test that Add returns true/false if an eviction occurred -func TestLRU_Add(t *testing.T) { - evictCounter := 0 - onEvicted := func(k int, v int) { - evictCounter++ - } - - l, err := NewLRU(1, onEvicted) - if err != nil { - t.Fatalf("err: %v", err) - } - - if l.Add(1, 1) == true || evictCounter != 0 { - t.Errorf("should not have an eviction") - } - if l.Add(2, 2) == false || evictCounter != 1 { - t.Errorf("should have an eviction") - } -} - -// Test that Contains doesn't update recent-ness -func TestLRU_Contains(t *testing.T) { - l, err := NewLRU[int, int](2, nil) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if !l.Contains(1) { - t.Errorf("1 should be contained") - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("Contains should not have updated recent-ness of 1") - } -} - -// Test that Peek doesn't update recent-ness -func TestLRU_Peek(t *testing.T) { - l, err := NewLRU[int, int](2, nil) - if err != nil { - t.Fatalf("err: %v", err) - } - - l.Add(1, 1) - l.Add(2, 2) - if v, ok := l.Peek(1); !ok || v != 1 { - t.Errorf("1 should be set to 1: %v, %v", v, ok) - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("should not have updated recent-ness of 1") - } -} - -// Test that Resize can upsize and downsize -func TestLRU_Resize(t *testing.T) { - onEvictCounter := 0 - onEvicted := func(k int, v int) { - onEvictCounter++ - } - l, err := NewLRU(2, onEvicted) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Downsize - l.Add(1, 1) - l.Add(2, 2) - evicted := l.Resize(1) - if evicted != 1 { - t.Errorf("1 element should have been evicted: %v", evicted) - } - if onEvictCounter != 1 { - t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter) - } - - l.Add(3, 3) - if l.Contains(1) { - t.Errorf("Element 1 should have been evicted") - } - - // Upsize - evicted = l.Resize(2) - if evicted != 0 { - t.Errorf("0 elements should have been evicted: %v", evicted) - } - - l.Add(4, 4) - if !l.Contains(3) || !l.Contains(4) { - t.Errorf("Cache should have contained 2 elements") - } -} - -func (c *LRU[K, V]) wantKeys(t *testing.T, want []K) { - t.Helper() - got := c.Keys() - if !reflect.DeepEqual(got, want) { - t.Errorf("wrong keys got: %v, want: %v ", got, want) - } -} - -func TestCache_EvictionSameKey(t *testing.T) { - var evictedKeys []int - - cache, _ := NewLRU( - 2, - func(key int, _ struct{}) { - evictedKeys = append(evictedKeys, key) - }) - - if evicted := cache.Add(1, struct{}{}); evicted { - t.Error("First 1: got unexpected eviction") - } - cache.wantKeys(t, []int{1}) - - if evicted := cache.Add(2, struct{}{}); evicted { - t.Error("2: got unexpected eviction") - } - cache.wantKeys(t, []int{1, 2}) - - if evicted := cache.Add(1, struct{}{}); evicted { - t.Error("Second 1: got unexpected eviction") - } - cache.wantKeys(t, []int{2, 1}) - - if evicted := cache.Add(3, struct{}{}); !evicted { - t.Error("3: did not get expected eviction") - } - cache.wantKeys(t, []int{1, 3}) - - want := []int{2} - if !reflect.DeepEqual(evictedKeys, want) { - t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want) - } -} diff --git a/common/utils/golang-lru-main/testing_test.go b/common/utils/golang-lru-main/testing_test.go deleted file mode 100644 index a93ef0332..000000000 --- a/common/utils/golang-lru-main/testing_test.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.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/common/utils/help.go b/common/utils/help.go index a2fdd2b15..9bdb41e7d 100644 --- a/common/utils/help.go +++ b/common/utils/help.go @@ -8,3 +8,12 @@ func FindWithIndex[T any](slice []T, predicate func(item T) bool) (int, *T, bool } return -1, nil, false } +func LastFourElements[T any](s []T, n1 int) []T { + n := len(s) + if n <= n1 { + // 切片长度小于等于4时,返回整个切片 + return s + } + // 切片长度大于4时,返回最后4个元素(从n-4索引到末尾) + return s[n-n1:] +} diff --git a/go.work b/go.work index a6eefbcea..51ee86589 100644 --- a/go.work +++ b/go.work @@ -13,7 +13,6 @@ use ( ./common/utils/go-jsonrpc ./common/utils/go-sensitive-word-1.3.3 ./common/utils/goja - ./common/utils/golang-lru-main ./common/utils/limit ./common/utils/log ./common/utils/qqwry diff --git a/logic/controller/fight_boss.go b/logic/controller/fight_boss.go index 870c200aa..70781688b 100644 --- a/logic/controller/fight_boss.go +++ b/logic/controller/fight_boss.go @@ -3,7 +3,6 @@ package controller import ( "blazing/common/data/xmlres" "blazing/common/socket/errorcode" - "math/rand" "strings" "blazing/logic/service/fight" @@ -39,7 +38,7 @@ func processMonID(bm string) string { selected = monid[0] default: // 长度大于1时,随机选取一个 - randomIdx := rand.Intn(len(monid)) + randomIdx := grand.Intn(len(monid)) selected = monid[randomIdx] } return selected diff --git a/logic/controller/item.go b/logic/controller/item.go index fe0a67a12..da8b7f9a2 100644 --- a/logic/controller/item.go +++ b/logic/controller/item.go @@ -3,12 +3,12 @@ package controller import ( "blazing/common/data/xmlres" "blazing/common/socket/errorcode" - "math/rand" - "time" "blazing/logic/service/item" "blazing/logic/service/player" "blazing/modules/blazing/model" + + "github.com/gogf/gf/v2/util/grand" ) func (h Controller) UserItemList(data *item.ItemListInboundInfo, c *player.Player) (result *item.ItemListOutboundInfo, err errorcode.ErrorCode) { @@ -113,10 +113,7 @@ func (h Controller) TalkCate(data *item.TalkCateInboundInfo, c *player.Player) ( } } - rand.Seed(time.Now().UnixNano()) // UnixNano 精度更高,避免短时间内种子重复 - - // 2. 生成 1-10 的随机数:rand.Intn(10) → 0-9,+1 后范围变为 1-10 - randomNum := rand.Intn(10) + 1 + randomNum := grand.Intn(10) + 1 c.Service.Talk.Exec(func(t map[uint32]uint32) bool { if t == nil { t = make(map[uint32]uint32) diff --git a/logic/controller/pet.go b/logic/controller/pet.go index b471f4aa9..ecf808d1a 100644 --- a/logic/controller/pet.go +++ b/logic/controller/pet.go @@ -17,24 +17,20 @@ func (h *Controller) GetPetInfo( data *pet.InInfo, c *player.Player) (result *pet.OutInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 + _, tet, ok := c.FindPet(data.CatchTime) - for _, pi := range c.Info.PetList { - if pi.CatchTime == data.CatchTime { - result = &pet.OutInfo{ - PetInfo: pi, - } - return result, 0 + if ok { + result = &pet.OutInfo{ + PetInfo: *tet, } - - } - - if result == nil { - + return result, 0 + } else { result = &pet.OutInfo{ PetInfo: c.Service.Pet.PetInfo_One(data.CatchTime).Data, } } + return result, 0 } @@ -73,46 +69,36 @@ func (h *Controller) PetRelease( } switch data.Flag { case 0: - var temp []model.PetInfo - - for _, v := range c.Info.PetList { - if v.CatchTime == uint32(data.CatchTime) { - c.Service.Pet.PetInfo_One_exec(data.CatchTime, func(t *model.PetEX) { - t.Data = v - //t.InBag = 0 - - }) - - } else { - temp = append(temp, v) - } + index, _, ok := c.FindPet(data.CatchTime) + if ok { + c.Info.PetList = append(c.Info.PetList[:index], c.Info.PetList[index+1:]...) } - c.Info.PetList = temp + // break // 只移除第一个匹配值,若需移除所有,可省略 break 继续循环 case 1: - //todo 背包 - c.Service.Pet.PetInfo_One_exec(data.CatchTime, func(t *model.PetEX) { + if len(c.Info.PetList) < 6 { + //todo 背包 + c.Service.Pet.PetInfo_One_exec(data.CatchTime, func(t *model.PetEX) { - _, _, ok := utils.FindWithIndex(c.Info.PetList, func(item model.PetInfo) bool { - return item.CatchTime == uint32(data.CatchTime) + _, _, ok := c.FindPet(data.CatchTime) + + //如果背包没找到,再放入背包 + if !ok && t.CatchTime != 0 { + //t.InBag = 1 + c.Info.PetList = append(c.Info.PetList, t.Data) + result.PetInfo = t.Data + } }) - - //如果背包没找到,再放入背包 - if !ok && t.CatchTime != 0 { - //t.InBag = 1 - c.Info.PetList = append(c.Info.PetList, t.Data) - result.PetInfo = t.Data - } - - }) + } } + if len(c.Info.PetList) > 0 { result.FirstPetTime = c.Info.PetList[0].CatchTime //设置首发 } - //service.NewUserService(c.Info.UserID).PetAdd( *r) + return result, 0 } @@ -120,16 +106,13 @@ func (h *Controller) PetRelease( func (h *Controller) PlayerShowPet( data *pet.PetShowInboundInfo, c *player.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 result = &pet.PetShowOutboundInfo{} + _, onpet, ok := c.FindPet(data.CatchTime) - for _, pi := range c.Info.PetList { - if pi.CatchTime == data.CatchTime { - copier.Copy(&result, pi) - result.Flag = data.Flag - result.UserID = data.Head.UserID - c.GetSpace().Broadcast(c, data.Head.CMD, result) - - } - + if ok { + copier.Copy(&result, onpet) + result.Flag = data.Flag + result.UserID = data.Head.UserID + c.GetSpace().Broadcast(c, data.Head.CMD, result) } return @@ -139,9 +122,7 @@ func (h *Controller) PetOneCure( if c.GetSpace().Owner.UserID == c.Info.UserID { return result, errorcode.ErrorCodes.ErrChampionCannotHeal } - _, onpet, ok := utils.FindWithIndex(c.Info.PetList, func(item model.PetInfo) bool { - return item.CatchTime == data.CatchTime - }) + _, onpet, ok := c.FindPet(data.CatchTime) if ok { onpet.Cure() } @@ -155,20 +136,21 @@ func (h *Controller) PetOneCure( // 精灵首发 func (h *Controller) PetFirst( data *pet.PetDefaultInboundInfo, c *player.Player) (result *pet.PetDefaultOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 - result = &pet.PetDefaultOutboundInfo{} - var ttt []model.PetInfo - - for index, pi := range c.Info.PetList { - if pi.CatchTime == data.CatchTime { - ttt = append(ttt, pi) - ttt = append(ttt, c.Info.PetList[:index]...) - ttt = append(ttt, c.Info.PetList[index+1:]...) - result.IsDefault = 1 - break - } - + //擂台住不能换精灵 + if c.GetSpace().Owner.UserID == c.Info.UserID { + return result, errorcode.ErrorCodes.ErrChampionCannotSwitch } - c.Info.PetList = ttt + + result = &pet.PetDefaultOutboundInfo{} + + index, _, ok := c.FindPet(data.CatchTime) + if ok && index != 0 { + + c.Info.PetList[index], c.Info.PetList[0] = c.Info.PetList[0], c.Info.PetList[index] + + result.IsDefault = 1 + } + return result, 0 } @@ -177,9 +159,7 @@ func (h *Controller) PetFirst( // 返回:索引、元素指针、是否找到 func (h Controller) SetPetExp(data *pet.PetSetExpInboundInfo, c *player.Player) (result *pet.PetSetExpOutboundInfo, err errorcode.ErrorCode) { - _, onpet, ok := utils.FindWithIndex(c.Info.PetList, func(item model.PetInfo) bool { - return item.CatchTime == data.CatchTime - }) + _, onpet, ok := c.FindPet(data.CatchTime) if ok { c.AddPetExp(onpet, data.Exp) @@ -190,28 +170,18 @@ func (h Controller) SetPetExp(data *pet.PetSetExpInboundInfo, c *player.Player) }, 0 } func (h Controller) SetPetSkill(data *pet.ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) { - _, onpet, ok := utils.FindWithIndex(c.Info.PetList, func(item model.PetInfo) bool { - return item.CatchTime == data.CatchTime - }) + _, onpet, ok := c.FindPet(data.CatchTime) if ok { - _, _, ok := utils.FindWithIndex(onpet.SkillList, func(item model.SkillInfo) bool { //已经存在技能 + _, HasSkill, ok := utils.FindWithIndex(onpet.SkillList, func(item model.SkillInfo) bool { //已经存在技能 return item.ID == data.ReplaceSkill }) - if ok { - return - } - for i := 0; i < len(onpet.SkillList); i++ { - if onpet.SkillList[i].ID == data.HasSkill { - onpet.SkillList[i].ID = data.ReplaceSkill - onpet.SkillList[i].PP = uint32(xmlres.SkillMap[int(onpet.SkillList[i].ID)].MaxPP) - } - + if !ok { + HasSkill.ID = data.ReplaceSkill + HasSkill.PP = uint32(xmlres.SkillMap[int(HasSkill.ID)].MaxPP) } } - // onpet.SkillList = lo.UniqBy(onpet.SkillList, func(s model.SkillInfo) int { - // return int(s.ID) - // }) + return &pet.ChangeSkillOutInfo{ CatchTime: data.CatchTime, }, 0 diff --git a/logic/service/fight/effect/EffectRandomPower.go b/logic/service/fight/effect/EffectRandomPower.go index 8e82cdbf9..5e7d30442 100644 --- a/logic/service/fight/effect/EffectRandomPower.go +++ b/logic/service/fight/effect/EffectRandomPower.go @@ -3,7 +3,8 @@ package effect import ( "blazing/logic/service/fight/input" "blazing/logic/service/fight/node" - "math/rand" + + "github.com/gogf/gf/v2/util/grand" ) // ----------------------------------------------------------- @@ -49,7 +50,7 @@ func (e *EffectRandomPower) Skill_Hit() bool { if e.Input != nil && e.Input.FightC != nil { n = int(e.Input.FightC.GetRand().Int31n(int32(e.Max-e.Min+1))) + e.Min } else { - n = rand.Intn(e.Max-e.Min+1) + e.Min + n = grand.Intn(e.Max-e.Min+1) + e.Min } e.Ctx().SkillEntity.Power = n diff --git a/logic/service/fight/effect/effect_13.go b/logic/service/fight/effect/effect_13.go index 6018c6b10..f0e857b7f 100644 --- a/logic/service/fight/effect/effect_13.go +++ b/logic/service/fight/effect/effect_13.go @@ -1,9 +1,12 @@ package effect import ( + element "blazing/common/data/Element" "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" "blazing/logic/service/fight/node" + + "github.com/gogf/gf/v2/util/gconv" ) // ----------------------------------------------------------- @@ -26,7 +29,9 @@ func (e *Effect13) OnSkill() bool { if !e.Hit() { return true } - + if gconv.Int(e.Ctx().Opp.CurrentPet.PetInfo.Type) == int(element.ElementTypeGrass) { + return true + } duration := e.EffectNode.SideEffectArgs[0] - 1 //duration++ // 获取状态效果 diff --git a/logic/service/fight/effect/effect_status.go b/logic/service/fight/effect/effect_status.go index 9677b487a..631da7c1b 100644 --- a/logic/service/fight/effect/effect_status.go +++ b/logic/service/fight/effect/effect_status.go @@ -8,7 +8,6 @@ import ( "blazing/logic/service/fight/input" "blazing/logic/service/fight/node" - "github.com/gogf/gf/v2/util/gconv" "github.com/shopspring/decimal" ) @@ -100,9 +99,6 @@ type ParasiticSeed struct { // 技能命中前触发寄生效果 func (e *ParasiticSeed) Action_start_ex(attacker, defender *action.SelectSkillAction) bool { // 过滤特定类型单位(假设1是植物类型,使用枚举替代魔法数字) - if gconv.Int(e.Ctx().Our.CurrentPet.Type) == int(element.ElementTypeGrass) { - return true - } damage := decimal.NewFromUint64(uint64(e.Ctx().Our.CurrentPet.Info.MaxHp)). Div(decimal.NewFromInt(8)) diff --git a/logic/service/fight/info/BattlePetEntity.go b/logic/service/fight/info/BattlePetEntity.go index 2925bd2b0..df7714ab7 100644 --- a/logic/service/fight/info/BattlePetEntity.go +++ b/logic/service/fight/info/BattlePetEntity.go @@ -27,8 +27,8 @@ type BattlePetEntity struct { Info model.PetInfo //通过偏移赋值 //*input.Input //PType int - statusConditions sync.Map // key: StatusCondition, value: int (剩余回合) - Skills [4]*SkillEntity // 技能槽(最多4个技能) + statusConditions sync.Map // key: StatusCondition, value: int (剩余回合) + Skills map[uint32]*SkillEntity // 技能槽(最多4个技能) //Status StatusDict //精灵的状态 //能力提升属性 //Prop PropDict @@ -42,10 +42,12 @@ func CreateBattlePetEntity(info model.PetInfo, rand *rand.Rand) *BattlePetEntity ret.PetInfo = xmlres.PetMAP[int(info.ID)] //注入精灵信息 ret.Info = info + ret.Skills = make(map[uint32]*SkillEntity) + for i := 0; i < len(info.SkillList); i++ { //todo 技能信息应该每回合进行深拷贝,保证每次的技能效果都是不一样的 - ret.Skills[i] = CreateSkill(&info.SkillList[i], rand, ret) + ret.Skills[info.SkillList[i].ID] = CreateSkill(&info.SkillList[i], rand, ret) } diff --git a/logic/service/fight/input.go b/logic/service/fight/input.go index 498105318..a18944d5b 100644 --- a/logic/service/fight/input.go +++ b/logic/service/fight/input.go @@ -14,6 +14,7 @@ import ( "sync" "time" + "github.com/gogf/gf/v2/util/grand" "github.com/jinzhu/copier" ) @@ -156,7 +157,7 @@ func RandomElfIDs(n int) []int { for len(ids) < n { // 生成1-2000的随机数 - id := rand.Intn(2000) + 1 // rand.Intn(2000)生成0-1999,+1后为1-2000 + id := grand.Intn(2000) + 1 // rand.Intn(2000)生成0-1999,+1后为1-2000 // 检查是否已抽取 if _, exists := used[id]; !exists { diff --git a/logic/service/fight/input/fight.go b/logic/service/fight/input/fight.go index 832708ea7..790f69753 100644 --- a/logic/service/fight/input/fight.go +++ b/logic/service/fight/input/fight.go @@ -7,8 +7,8 @@ import ( "blazing/logic/service/fight/action" "blazing/logic/service/fight/info" - "math/rand" + "github.com/gogf/gf/v2/util/grand" "github.com/shopspring/decimal" ) @@ -262,10 +262,14 @@ func (our *Input) GetAction(opp *Input) { return } - randomIdx := rand.Intn(len(allSkills)) - chosenSkill := skills[randomIdx] - our.FightC.UseSkill(our.Player, int32(chosenSkill.ID)) - // i.FightC.UseSkill(i.Player, int32(bestSkill.skill.ID)) + randomIdx := grand.Intn(len(allSkills)) + for i, v := range skills { + if randomIdx == int(i) { + our.FightC.UseSkill(our.Player, int32(v.ID)) + } + + } + our.FightC.UseSkill(our.Player, 0) } // 计算技能威力 diff --git a/logic/service/player/base.go b/logic/service/player/base.go index e0fda8871..a1246f317 100644 --- a/logic/service/player/base.go +++ b/logic/service/player/base.go @@ -1,11 +1,14 @@ package player import ( + "blazing/common/utils" "blazing/logic/service/common" "blazing/logic/service/fight/info" "blazing/modules/blazing/model" "math/rand" "time" + + "github.com/gogf/gf/v2/util/grand" ) type baseplayer struct { @@ -17,7 +20,7 @@ type baseplayer struct { // NewPlayerCaptureContext 创建用户捕捉上下文(每次登录调用) func newbaseplayer() baseplayer { - rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(rand.Intn(1000000)))) + rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(grand.Intn(1000000)))) ret := baseplayer{} ret.PlayerCaptureContext = &info.PlayerCaptureContext{ rng, @@ -41,6 +44,13 @@ func (f *baseplayer) GetPlayerCaptureContext() *info.PlayerCaptureContext { return f.PlayerCaptureContext } +func (f *baseplayer) FindPet(CatchTime uint32) (int, *model.PetInfo, bool) { + + return utils.FindWithIndex(f.Info.PetList, func(item model.PetInfo) bool { + return item.CatchTime == CatchTime + }) +} + // // 计算整数的二进制1的个数(Integer.bitCount) // func bitsCount(n int) int { // count := 0 diff --git a/logic/service/player/pet.go b/logic/service/player/pet.go index 4ff375504..e8ae184b1 100644 --- a/logic/service/player/pet.go +++ b/logic/service/player/pet.go @@ -41,7 +41,7 @@ func (p *Player) AddPetExp(petinfo *model.PetInfo, addExp uint32) { } // 处理技能学习 - canLearnSkillList := LastFourElements(petinfo.GetLevelRangeCanLearningSkills(originalLevel, petinfo.Level)) //获取最后四个技能,如果不足,那就取全部技能 + canLearnSkillList := utils.LastFourElements(petinfo.GetLevelRangeCanLearningSkills(originalLevel, petinfo.Level), 4) //获取最后四个技能,如果不足,那就取全部技能 for i := 0; i < 4; i++ { @@ -86,16 +86,6 @@ func (p *Player) AddPetExp(petinfo *model.PetInfo, addExp uint32) { } -func LastFourElements[T any](s []T) []T { - n := len(s) - if n <= 4 { - // 切片长度小于等于4时,返回整个切片 - return s - } - // 切片长度大于4时,返回最后4个元素(从n-4索引到末尾) - return s[n-4:] -} - // GenPetInfo 生成一个新的精灵实例 // - 参数为 -1 时表示随机生成对应属性 // * @param petTypeId 精灵类型ID diff --git a/logic/service/player/player.go b/logic/service/player/player.go index d4c3e4bbd..0f812ba66 100644 --- a/logic/service/player/player.go +++ b/logic/service/player/player.go @@ -20,6 +20,7 @@ import ( "github.com/antlabs/timer" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/grand" "github.com/panjf2000/gnet/v2" ) @@ -224,13 +225,13 @@ func (p *Player) genMonster(mapid uint32) { // 生成0-9之间三个不重复的随机数 进地图5s func generateThreeUniqueNumbers() [3]int { - rand.Seed(time.Now().UnixNano()) + selected := make(map[int]bool) var result [3]int index := 0 for index < 3 { - num := rand.Intn(9) + num := grand.Intn(9) if !selected[num] { selected[num] = true result[index] = num @@ -243,7 +244,7 @@ func generateThreeUniqueNumbers() [3]int { // 从三个数字中移除一个,并从剩余6个数字中选一个补充 10s func replaceOneNumber(original [3]int) ([3]int, int, int) { // 随机选择要移除的索引(0-2) - removeIndex := rand.Intn(3) + removeIndex := grand.Intn(3) removedNum := original[removeIndex] // 找出所有不在原始数组中的数字(候选数字) @@ -260,7 +261,7 @@ func replaceOneNumber(original [3]int) ([3]int, int, int) { } // 从候选数字中随机选择一个 - newNum := candidates[rand.Intn(len(candidates))] + newNum := candidates[grand.Intn(len(candidates))] // 创建新数组并替换数字 newNumbers := original diff --git a/logic/service/space/in_out.go b/logic/service/space/in_out.go index 42cb874fb..2e30f720c 100644 --- a/logic/service/space/in_out.go +++ b/logic/service/space/in_out.go @@ -90,13 +90,3 @@ func (s *Space) Walk(c common.PlayerI, info *info.WalkOutInfo) { s.Broadcast(c, 2101, info) } - -func LastFourElements[T any](s []T) []T { - n := len(s) - if n <= 30 { - // 切片长度小于等于4时,返回整个切片 - return s - } - // 切片长度大于4时,返回最后4个元素(从n-4索引到末尾) - return s[n-30:] -} diff --git a/modules/blazing/model/pet.go b/modules/blazing/model/pet.go index ab0e1bd88..006d252d4 100644 --- a/modules/blazing/model/pet.go +++ b/modules/blazing/model/pet.go @@ -2,6 +2,7 @@ package model import ( "blazing/common/data/xmlres" + "blazing/common/utils" "blazing/cool" "errors" "fmt" @@ -401,7 +402,7 @@ func GenPetInfo( } // ---- 技能学习 ---- - skills := LastFourElements(p.GetLevelRangeCanLearningSkills(0, p.Level)) // 最后四个技能 + skills := utils.LastFourElements(p.GetLevelRangeCanLearningSkills(0, p.Level), 4) // 最后四个技能 for i := 0; i < len(skills) && i < 4; i++ { skillID := skills[i] @@ -422,16 +423,6 @@ func GenPetInfo( return p } -func LastFourElements[T any](s []T) []T { - n := len(s) - if n <= 4 { - // 切片长度小于等于4时,返回整个切片 - return s - } - // 切片长度大于4时,返回最后4个元素(从n-4索引到末尾) - return s[n-4:] -} - // 除数数组(放大100倍) // 数组按递增顺序排列,用于判断个体值等级 var divisors = []int{