diff --git a/common/lru/basiclru.go b/common/lru/basiclru.go
new file mode 100644
index 0000000000..b3369cf1f2
--- /dev/null
+++ b/common/lru/basiclru.go
@@ -0,0 +1,223 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package lru implements generically-typed LRU caches.
+package lru
+
+// BasicLRU is a simple LRU cache.
+//
+// This type is not safe for concurrent use.
+// The zero value is not valid, instances must be created using NewCache.
+type BasicLRU[K comparable, V any] struct {
+ list *list[K]
+ items map[K]cacheItem[K, V]
+ cap int
+}
+
+type cacheItem[K any, V any] struct {
+ elem *listElem[K]
+ value V
+}
+
+// NewBasicLRU creates a new LRU cache.
+func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] {
+ if capacity < 0 {
+ capacity = 1
+ }
+ c := BasicLRU[K, V]{
+ items: make(map[K]cacheItem[K, V]),
+ list: newList[K](),
+ cap: capacity,
+ }
+ return c
+}
+
+// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
+func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) {
+ item, ok := c.items[key]
+ if ok {
+ // Already exists in cache.
+ item.value = value
+ c.items[key] = item
+ c.list.moveToFront(item.elem)
+ return false
+ }
+
+ var elem *listElem[K]
+ if c.Len() >= c.cap {
+ elem = c.list.removeLast()
+ delete(c.items, elem.v)
+ evicted = true
+ } else {
+ elem = new(listElem[K])
+ }
+
+ // Store the new item.
+ // Note that, if another item was evicted, we re-use its list element here.
+ elem.v = key
+ c.items[key] = cacheItem[K, V]{elem, value}
+ c.list.pushElem(elem)
+ return evicted
+}
+
+// Contains reports whether the given key exists in the cache.
+func (c *BasicLRU[K, V]) Contains(key K) bool {
+ _, ok := c.items[key]
+ return ok
+}
+
+// Get retrieves a value from the cache. This marks the key as recently used.
+func (c *BasicLRU[K, V]) Get(key K) (value V, ok bool) {
+ item, ok := c.items[key]
+ if !ok {
+ return value, false
+ }
+ c.list.moveToFront(item.elem)
+ return item.value, true
+}
+
+// GetOldest retrieves the least-recently-used item.
+// Note that this does not update the item's recency.
+func (c *BasicLRU[K, V]) GetOldest() (key K, value V, ok bool) {
+ lastElem := c.list.last()
+ if lastElem == nil {
+ return key, value, false
+ }
+ key = lastElem.v
+ item := c.items[key]
+ return key, item.value, true
+}
+
+// Len returns the current number of items in the cache.
+func (c *BasicLRU[K, V]) Len() int {
+ return len(c.items)
+}
+
+// Peek retrieves a value from the cache, but does not mark the key as recently used.
+func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) {
+ item, ok := c.items[key]
+ return item.value, ok
+}
+
+// Purge empties the cache.
+func (c *BasicLRU[K, V]) Purge() {
+ c.list.init()
+ for k := range c.items {
+ delete(c.items, k)
+ }
+}
+
+// Remove drops an item from the cache. Returns true if the key was present in cache.
+func (c *BasicLRU[K, V]) Remove(key K) bool {
+ item, ok := c.items[key]
+ if ok {
+ delete(c.items, key)
+ c.list.remove(item.elem)
+ }
+ return ok
+}
+
+// RemoveOldest drops the least recently used item.
+func (c *BasicLRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
+ lastElem := c.list.last()
+ if lastElem == nil {
+ return key, value, false
+ }
+
+ key = lastElem.v
+ item := c.items[key]
+ delete(c.items, key)
+ c.list.remove(lastElem)
+ return key, item.value, true
+}
+
+// Keys returns all keys in the cache.
+func (c *BasicLRU[K, V]) Keys() []K {
+ keys := make([]K, 0, len(c.items))
+ return c.list.appendTo(keys)
+}
+
+// list is a doubly-linked list holding items of type he.
+// The zero value is not valid, use newList to create lists.
+type list[T any] struct {
+ root listElem[T]
+}
+
+type listElem[T any] struct {
+ next *listElem[T]
+ prev *listElem[T]
+ v T
+}
+
+func newList[T any]() *list[T] {
+ l := new(list[T])
+ l.init()
+ return l
+}
+
+// init reinitializes the list, making it empty.
+func (l *list[T]) init() {
+ l.root.next = &l.root
+ l.root.prev = &l.root
+}
+
+// push adds an element to the front of the list.
+func (l *list[T]) pushElem(e *listElem[T]) {
+ e.prev = &l.root
+ e.next = l.root.next
+ l.root.next = e
+ e.next.prev = e
+}
+
+// moveToFront makes 'node' the head of the list.
+func (l *list[T]) moveToFront(e *listElem[T]) {
+ e.prev.next = e.next
+ e.next.prev = e.prev
+ l.pushElem(e)
+}
+
+// remove removes an element from the list.
+func (l *list[T]) remove(e *listElem[T]) {
+ e.prev.next = e.next
+ e.next.prev = e.prev
+ e.next, e.prev = nil, nil
+}
+
+// removeLast removes the last element of the list.
+func (l *list[T]) removeLast() *listElem[T] {
+ last := l.last()
+ if last != nil {
+ l.remove(last)
+ }
+ return last
+}
+
+// last returns the last element of the list, or nil if the list is empty.
+func (l *list[T]) last() *listElem[T] {
+ e := l.root.prev
+ if e == &l.root {
+ return nil
+ }
+ return e
+}
+
+// appendTo appends all list elements to a slice.
+func (l *list[T]) appendTo(slice []T) []T {
+ for e := l.root.prev; e != &l.root; e = e.prev {
+ slice = append(slice, e.v)
+ }
+ return slice
+}
diff --git a/common/lru/basiclru_test.go b/common/lru/basiclru_test.go
new file mode 100644
index 0000000000..68e13bfc92
--- /dev/null
+++ b/common/lru/basiclru_test.go
@@ -0,0 +1,240 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package lru
+
+import (
+ "fmt"
+ "io"
+ "math/rand"
+ "testing"
+)
+
+// Some of these test cases were adapted
+// from https://github.com/hashicorp/golang-lru/blob/master/simplelru/lru_test.go
+
+func TestBasicLRU(t *testing.T) {
+ cache := NewBasicLRU[int, int](128)
+
+ for i := 0; i < 256; i++ {
+ cache.Add(i, i)
+ }
+ if cache.Len() != 128 {
+ t.Fatalf("bad len: %v", cache.Len())
+ }
+
+ // Check that Keys returns least-recent key first.
+ keys := cache.Keys()
+ if len(keys) != 128 {
+ t.Fatal("wrong Keys() length", len(keys))
+ }
+ for i, k := range keys {
+ v, ok := cache.Peek(k)
+ if !ok {
+ t.Fatalf("expected key %d be present", i)
+ }
+ if v != k {
+ t.Fatalf("expected %d == %d", k, v)
+ }
+ if v != i+128 {
+ t.Fatalf("wrong value at key %d: %d, want %d", i, v, i+128)
+ }
+ }
+
+ for i := 0; i < 128; i++ {
+ _, ok := cache.Get(i)
+ if ok {
+ t.Fatalf("%d should be evicted", i)
+ }
+ }
+ for i := 128; i < 256; i++ {
+ _, ok := cache.Get(i)
+ if !ok {
+ t.Fatalf("%d should not be evicted", i)
+ }
+ }
+
+ for i := 128; i < 192; i++ {
+ ok := cache.Remove(i)
+ if !ok {
+ t.Fatalf("%d should be in cache", i)
+ }
+ ok = cache.Remove(i)
+ if ok {
+ t.Fatalf("%d should not be in cache", i)
+ }
+ _, ok = cache.Get(i)
+ if ok {
+ t.Fatalf("%d should be deleted", i)
+ }
+ }
+
+ // Request item 192.
+ cache.Get(192)
+ // It should be the last item returned by Keys().
+ for i, k := range cache.Keys() {
+ if (i < 63 && k != i+193) || (i == 63 && k != 192) {
+ t.Fatalf("out of order key: %v", k)
+ }
+ }
+
+ cache.Purge()
+ if cache.Len() != 0 {
+ t.Fatalf("bad len: %v", cache.Len())
+ }
+ if _, ok := cache.Get(200); ok {
+ t.Fatalf("should contain nothing")
+ }
+}
+
+func TestBasicLRUAddExistingKey(t *testing.T) {
+ cache := NewBasicLRU[int, int](1)
+
+ cache.Add(1, 1)
+ cache.Add(1, 2)
+
+ v, _ := cache.Get(1)
+ if v != 2 {
+ t.Fatal("wrong value:", v)
+ }
+}
+
+// This test checks GetOldest and RemoveOldest.
+func TestBasicLRUGetOldest(t *testing.T) {
+ cache := NewBasicLRU[int, int](128)
+ for i := 0; i < 256; i++ {
+ cache.Add(i, i)
+ }
+
+ k, _, ok := cache.GetOldest()
+ if !ok {
+ t.Fatalf("missing")
+ }
+ if k != 128 {
+ t.Fatalf("bad: %v", k)
+ }
+
+ k, _, ok = cache.RemoveOldest()
+ if !ok {
+ t.Fatalf("missing")
+ }
+ if k != 128 {
+ t.Fatalf("bad: %v", k)
+ }
+
+ k, _, ok = cache.RemoveOldest()
+ if !ok {
+ t.Fatalf("missing oldest item")
+ }
+ if k != 129 {
+ t.Fatalf("wrong oldest item: %v", k)
+ }
+}
+
+// Test that Add returns true/false if an eviction occurred
+func TestBasicLRUAddReturnValue(t *testing.T) {
+ cache := NewBasicLRU[int, int](1)
+ if cache.Add(1, 1) {
+ t.Errorf("first add shouldn't have evicted")
+ }
+ if !cache.Add(2, 2) {
+ t.Errorf("second add should have evicted")
+ }
+}
+
+// This test verifies that Contains doesn't change item recency.
+func TestBasicLRUContains(t *testing.T) {
+ cache := NewBasicLRU[int, int](2)
+ cache.Add(1, 1)
+ cache.Add(2, 2)
+ if !cache.Contains(1) {
+ t.Errorf("1 should be in the cache")
+ }
+ cache.Add(3, 3)
+ if cache.Contains(1) {
+ t.Errorf("Contains should not have updated recency of 1")
+ }
+}
+
+func BenchmarkLRU(b *testing.B) {
+ var (
+ capacity = 1000
+ indexes = make([]int, capacity*20)
+ keys = make([]string, capacity)
+ values = make([][]byte, capacity)
+ )
+ for i := range indexes {
+ indexes[i] = rand.Intn(capacity)
+ }
+ for i := range keys {
+ b := make([]byte, 32)
+ rand.Read(b)
+ keys[i] = string(b)
+ rand.Read(b)
+ values[i] = b
+ }
+
+ var sink []byte
+
+ b.Run("Add/BasicLRU", func(b *testing.B) {
+ cache := NewBasicLRU[int, int](capacity)
+ for i := 0; i < b.N; i++ {
+ cache.Add(i, i)
+ }
+ })
+ b.Run("Get/BasicLRU", func(b *testing.B) {
+ cache := NewBasicLRU[string, []byte](capacity)
+ for i := 0; i < capacity; i++ {
+ index := indexes[i]
+ cache.Add(keys[index], values[index])
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ k := keys[indexes[i%len(indexes)]]
+ v, ok := cache.Get(k)
+ if ok {
+ sink = v
+ }
+ }
+ })
+
+ // // vs. github.com/hashicorp/golang-lru/simplelru
+ // b.Run("Add/simplelru.LRU", func(b *testing.B) {
+ // cache, _ := simplelru.NewLRU(capacity, nil)
+ // for i := 0; i < b.N; i++ {
+ // cache.Add(i, i)
+ // }
+ // })
+ // b.Run("Get/simplelru.LRU", func(b *testing.B) {
+ // cache, _ := simplelru.NewLRU(capacity, nil)
+ // for i := 0; i < capacity; i++ {
+ // index := indexes[i]
+ // cache.Add(keys[index], values[index])
+ // }
+ //
+ // b.ResetTimer()
+ // for i := 0; i < b.N; i++ {
+ // k := keys[indexes[i%len(indexes)]]
+ // v, ok := cache.Get(k)
+ // if ok {
+ // sink = v.([]byte)
+ // }
+ // }
+ // })
+
+ fmt.Fprintln(io.Discard, sink)
+}
diff --git a/common/lru/blob_lru.go b/common/lru/blob_lru.go
index b24684256c..c9b3398503 100644
--- a/common/lru/blob_lru.go
+++ b/common/lru/blob_lru.go
@@ -19,33 +19,32 @@ package lru
import (
"math"
"sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/hashicorp/golang-lru/simplelru"
)
-// SizeConstrainedLRU is a wrapper around simplelru.LRU. The simplelru.LRU is capable
-// of item-count constraints, but is not capable of enforcing a byte-size constraint,
-// hence this wrapper.
+// blobType is the type constraint for values stored in SizeConstrainedCache.
+type blobType interface {
+ ~[]byte | ~string
+}
+
+// SizeConstrainedCache is a cache where capacity is in bytes (instead of item count). When the cache
+// is at capacity, and a new item is added, older items are evicted until the size
+// constraint is met.
+//
// OBS: This cache assumes that items are content-addressed: keys are unique per content.
// In other words: two Add(..) with the same key K, will always have the same value V.
-type SizeConstrainedLRU struct {
+type SizeConstrainedCache[K comparable, V blobType] struct {
size uint64
maxSize uint64
- lru *simplelru.LRU
+ lru BasicLRU[K, V]
lock sync.Mutex
}
-// NewSizeConstrainedLRU creates a new SizeConstrainedLRU.
-func NewSizeConstrainedLRU(max uint64) *SizeConstrainedLRU {
- lru, err := simplelru.NewLRU(math.MaxInt, nil)
- if err != nil {
- panic(err)
- }
- return &SizeConstrainedLRU{
+// NewSizeConstrainedCache creates a new size-constrained LRU cache.
+func NewSizeConstrainedCache[K comparable, V blobType](maxSize uint64) *SizeConstrainedCache[K, V] {
+ return &SizeConstrainedCache[K, V]{
size: 0,
- maxSize: max,
- lru: lru,
+ maxSize: maxSize,
+ lru: NewBasicLRU[K, V](math.MaxInt),
}
}
@@ -53,7 +52,7 @@ func NewSizeConstrainedLRU(max uint64) *SizeConstrainedLRU {
// OBS: This cache assumes that items are content-addressed: keys are unique per content.
// In other words: two Add(..) with the same key K, will always have the same value V.
// OBS: The value is _not_ copied on Add, so the caller must not modify it afterwards.
-func (c *SizeConstrainedLRU) Add(key common.Hash, value []byte) (evicted bool) {
+func (c *SizeConstrainedCache[K, V]) Add(key K, value V) (evicted bool) {
c.lock.Lock()
defer c.lock.Unlock()
@@ -68,7 +67,7 @@ func (c *SizeConstrainedLRU) Add(key common.Hash, value []byte) (evicted bool) {
// list is now empty. Break
break
}
- targetSize -= uint64(len(v.([]byte)))
+ targetSize -= uint64(len(v))
}
c.size = targetSize
}
@@ -77,12 +76,9 @@ func (c *SizeConstrainedLRU) Add(key common.Hash, value []byte) (evicted bool) {
}
// Get looks up a key's value from the cache.
-func (c *SizeConstrainedLRU) Get(key common.Hash) []byte {
+func (c *SizeConstrainedCache[K, V]) Get(key K) (V, bool) {
c.lock.Lock()
defer c.lock.Unlock()
- if v, ok := c.lru.Get(key); ok {
- return v.([]byte)
- }
- return nil
+ return c.lru.Get(key)
}
diff --git a/common/lru/blob_lru_test.go b/common/lru/blob_lru_test.go
index 4b5e693402..ca1b0ddd74 100644
--- a/common/lru/blob_lru_test.go
+++ b/common/lru/blob_lru_test.go
@@ -20,22 +20,21 @@ import (
"encoding/binary"
"fmt"
"testing"
-
- "github.com/ethereum/go-ethereum/common"
)
-func mkHash(i int) common.Hash {
- h := make([]byte, 32)
- binary.LittleEndian.PutUint64(h, uint64(i))
- return common.BytesToHash(h)
+type testKey [8]byte
+
+func mkKey(i int) (key testKey) {
+ binary.LittleEndian.PutUint64(key[:], uint64(i))
+ return key
}
-func TestBlobLru(t *testing.T) {
- lru := NewSizeConstrainedLRU(100)
+func TestSizeConstrainedCache(t *testing.T) {
+ lru := NewSizeConstrainedCache[testKey, []byte](100)
var want uint64
// Add 11 items of 10 byte each. First item should be swapped out
for i := 0; i < 11; i++ {
- k := mkHash(i)
+ k := mkKey(i)
v := fmt.Sprintf("value-%04d", i)
lru.Add(k, []byte(v))
want += uint64(len(v))
@@ -48,17 +47,17 @@ func TestBlobLru(t *testing.T) {
}
// Zero:th should be evicted
{
- k := mkHash(0)
- if val := lru.Get(k); val != nil {
+ k := mkKey(0)
+ if _, ok := lru.Get(k); ok {
t.Fatalf("should be evicted: %v", k)
}
}
// Elems 1-11 should be present
for i := 1; i < 11; i++ {
- k := mkHash(i)
+ k := mkKey(i)
want := fmt.Sprintf("value-%04d", i)
- have := lru.Get(k)
- if have == nil {
+ have, ok := lru.Get(k)
+ if !ok {
t.Fatalf("missing key %v", k)
}
if string(have) != want {
@@ -67,26 +66,26 @@ func TestBlobLru(t *testing.T) {
}
}
-// TestBlobLruOverflow tests what happens when inserting an element exceeding
-// the max size
-func TestBlobLruOverflow(t *testing.T) {
- lru := NewSizeConstrainedLRU(100)
+// This test adds inserting an element exceeding the max size.
+func TestSizeConstrainedCacheOverflow(t *testing.T) {
+ lru := NewSizeConstrainedCache[testKey, []byte](100)
+
// Add 10 items of 10 byte each, filling the cache
for i := 0; i < 10; i++ {
- k := mkHash(i)
+ k := mkKey(i)
v := fmt.Sprintf("value-%04d", i)
lru.Add(k, []byte(v))
}
// Add one single large elem. We expect it to swap out all entries.
{
- k := mkHash(1337)
+ k := mkKey(1337)
v := make([]byte, 200)
lru.Add(k, v)
}
// Elems 0-9 should be missing
for i := 1; i < 10; i++ {
- k := mkHash(i)
- if val := lru.Get(k); val != nil {
+ k := mkKey(i)
+ if _, ok := lru.Get(k); ok {
t.Fatalf("should be evicted: %v", k)
}
}
@@ -97,7 +96,7 @@ func TestBlobLruOverflow(t *testing.T) {
// Adding one small item should swap out the large one
{
i := 0
- k := mkHash(i)
+ k := mkKey(i)
v := fmt.Sprintf("value-%04d", i)
lru.Add(k, []byte(v))
if have, want := lru.size, uint64(10); have != want {
@@ -106,17 +105,51 @@ func TestBlobLruOverflow(t *testing.T) {
}
}
-// TestBlobLruSameItem tests what happens when inserting the same k/v multiple times.
-func TestBlobLruSameItem(t *testing.T) {
- lru := NewSizeConstrainedLRU(100)
- // Add one 10 byte-item 10 times
- k := mkHash(0)
+// This checks what happens when inserting the same k/v multiple times.
+func TestSizeConstrainedCacheSameItem(t *testing.T) {
+ lru := NewSizeConstrainedCache[testKey, []byte](100)
+
+ // Add one 10 byte-item 10 times.
+ k := mkKey(0)
v := fmt.Sprintf("value-%04d", 0)
for i := 0; i < 10; i++ {
lru.Add(k, []byte(v))
}
- // The size should be accurate
+
+ // The size should be accurate.
if have, want := lru.size, uint64(10); have != want {
t.Fatalf("size wrong, have %d want %d", have, want)
}
}
+
+// This tests that empty/nil values are handled correctly.
+func TestSizeConstrainedCacheEmpties(t *testing.T) {
+ lru := NewSizeConstrainedCache[testKey, []byte](100)
+
+ // This test abuses the lru a bit, using different keys for identical value(s).
+ for i := 0; i < 10; i++ {
+ lru.Add(testKey{byte(i)}, []byte{})
+ lru.Add(testKey{byte(255 - i)}, nil)
+ }
+
+ // The size should not count, only the values count. So this could be a DoS
+ // since it basically has no cap, and it is intentionally overloaded with
+ // different-keyed 0-length values.
+ if have, want := lru.size, uint64(0); have != want {
+ t.Fatalf("size wrong, have %d want %d", have, want)
+ }
+
+ for i := 0; i < 10; i++ {
+ if v, ok := lru.Get(testKey{byte(i)}); !ok {
+ t.Fatalf("test %d: expected presence", i)
+ } else if v == nil {
+ t.Fatalf("test %d, v is nil", i)
+ }
+
+ if v, ok := lru.Get(testKey{byte(255 - i)}); !ok {
+ t.Fatalf("test %d: expected presence", i)
+ } else if v != nil {
+ t.Fatalf("test %d, v is not nil", i)
+ }
+ }
+}
diff --git a/common/lru/lru.go b/common/lru/lru.go
new file mode 100644
index 0000000000..45965adb0d
--- /dev/null
+++ b/common/lru/lru.go
@@ -0,0 +1,95 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package lru
+
+import "sync"
+
+// Cache is a LRU cache.
+// This type is safe for concurrent use.
+type Cache[K comparable, V any] struct {
+ cache BasicLRU[K, V]
+ mu sync.Mutex
+}
+
+// NewCache creates an LRU cache.
+func NewCache[K comparable, V any](capacity int) *Cache[K, V] {
+ return &Cache[K, V]{cache: NewBasicLRU[K, V](capacity)}
+}
+
+// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
+func (c *Cache[K, V]) Add(key K, value V) (evicted bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Add(key, value)
+}
+
+// Contains reports whether the given key exists in the cache.
+func (c *Cache[K, V]) Contains(key K) bool {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Contains(key)
+}
+
+// Get retrieves a value from the cache. This marks the key as recently used.
+func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Get(key)
+}
+
+// Len returns the current number of items in the cache.
+func (c *Cache[K, V]) Len() int {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Len()
+}
+
+// Peek retrieves a value from the cache, but does not mark the key as recently used.
+func (c *Cache[K, V]) Peek(key K) (value V, ok bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Peek(key)
+}
+
+// Purge empties the cache.
+func (c *Cache[K, V]) Purge() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.cache.Purge()
+}
+
+// Remove drops an item from the cache. Returns true if the key was present in cache.
+func (c *Cache[K, V]) Remove(key K) bool {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Remove(key)
+}
+
+// Keys returns all keys of items currently in the LRU.
+func (c *Cache[K, V]) Keys() []K {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.cache.Keys()
+}
diff --git a/core/blockchain.go b/core/blockchain.go
index 80f8ba76f1..863e942425 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -30,6 +30,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus"
@@ -45,8 +46,8 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
- lru "github.com/hashicorp/golang-lru"
)
var (
@@ -200,12 +201,14 @@ type BlockChain struct {
currentSafeBlock atomic.Value // Current safe head
stateCache state.Database // State database to reuse between imports (contains state cache)
- bodyCache *lru.Cache // Cache for the most recent block bodies
- bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
- receiptsCache *lru.Cache // Cache for the most recent receipts per block
- blockCache *lru.Cache // Cache for the most recent entire blocks
- txLookupCache *lru.Cache // Cache for the most recent transaction lookup data.
- futureBlocks *lru.Cache // future blocks are blocks added for later processing
+ bodyCache *lru.Cache[common.Hash, *types.Body]
+ bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
+ receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
+ blockCache *lru.Cache[common.Hash, *types.Block]
+ txLookupCache *lru.Cache[common.Hash, *rawdb.LegacyTxLookupEntry]
+
+ // future blocks are blocks added for later processing
+ futureBlocks *lru.Cache[common.Hash, *types.Block]
wg sync.WaitGroup //
quit chan struct{} // shutdown signal, closed in Stop.
@@ -227,12 +230,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
if cacheConfig == nil {
cacheConfig = defaultCacheConfig
}
- bodyCache, _ := lru.New(bodyCacheLimit)
- bodyRLPCache, _ := lru.New(bodyCacheLimit)
- receiptsCache, _ := lru.New(receiptsCacheLimit)
- blockCache, _ := lru.New(blockCacheLimit)
- txLookupCache, _ := lru.New(txLookupCacheLimit)
- futureBlocks, _ := lru.New(maxFutureBlocks)
// Setup the genesis block, commit the provided genesis specification
// to database if the genesis block is not present yet, or load the
@@ -261,12 +258,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
}),
quit: make(chan struct{}),
chainmu: syncx.NewClosableMutex(),
- bodyCache: bodyCache,
- bodyRLPCache: bodyRLPCache,
- receiptsCache: receiptsCache,
- blockCache: blockCache,
- txLookupCache: txLookupCache,
- futureBlocks: futureBlocks,
+ bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
+ bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
+ receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
+ blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
+ txLookupCache: lru.NewCache[common.Hash, *rawdb.LegacyTxLookupEntry](txLookupCacheLimit),
+ futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
engine: engine,
vmConfig: vmConfig,
}
@@ -957,7 +954,7 @@ func (bc *BlockChain) procFutureBlocks() {
blocks := make([]*types.Block, 0, bc.futureBlocks.Len())
for _, hash := range bc.futureBlocks.Keys() {
if block, exist := bc.futureBlocks.Peek(hash); exist {
- blocks = append(blocks, block.(*types.Block))
+ blocks = append(blocks, block)
}
}
if len(blocks) > 0 {
diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go
index 5814c8a0da..da948029a1 100644
--- a/core/blockchain_reader.go
+++ b/core/blockchain_reader.go
@@ -96,8 +96,7 @@ func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
// Short circuit if the body's already in the cache, retrieve otherwise
if cached, ok := bc.bodyCache.Get(hash); ok {
- body := cached.(*types.Body)
- return body
+ return cached
}
number := bc.hc.GetBlockNumber(hash)
if number == nil {
@@ -117,7 +116,7 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
// Short circuit if the body's already in the cache, retrieve otherwise
if cached, ok := bc.bodyRLPCache.Get(hash); ok {
- return cached.(rlp.RawValue)
+ return cached
}
number := bc.hc.GetBlockNumber(hash)
if number == nil {
@@ -159,7 +158,7 @@ func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool {
func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
// Short circuit if the block's already in the cache, retrieve otherwise
if block, ok := bc.blockCache.Get(hash); ok {
- return block.(*types.Block)
+ return block
}
block := rawdb.ReadBlock(bc.db, hash, number)
if block == nil {
@@ -211,7 +210,7 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok {
- return receipts.(types.Receipts)
+ return receipts
}
number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil {
@@ -255,7 +254,7 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) *rawdb.LegacyTxLookupEntry {
// Short circuit if the txlookup already in the cache, retrieve otherwise
if lookup, exist := bc.txLookupCache.Get(hash); exist {
- return lookup.(*rawdb.LegacyTxLookupEntry)
+ return lookup
}
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
if tx == nil {
diff --git a/core/headerchain.go b/core/headerchain.go
index d8c415f336..482b5f6fbe 100644
--- a/core/headerchain.go
+++ b/core/headerchain.go
@@ -27,6 +27,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
@@ -34,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
- lru "github.com/hashicorp/golang-lru"
)
const (
@@ -64,9 +64,9 @@ type HeaderChain struct {
currentHeader atomic.Value // Current head of the header chain (may be above the block chain!)
currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
- headerCache *lru.Cache // Cache for the most recent block headers
- tdCache *lru.Cache // Cache for the most recent block total difficulties
- numberCache *lru.Cache // Cache for the most recent block numbers
+ headerCache *lru.Cache[common.Hash, *types.Header]
+ tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties
+ numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers
procInterrupt func() bool
@@ -77,10 +77,6 @@ type HeaderChain struct {
// NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points
// to the parent's interrupt semaphore.
func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) {
- headerCache, _ := lru.New(headerCacheLimit)
- tdCache, _ := lru.New(tdCacheLimit)
- numberCache, _ := lru.New(numberCacheLimit)
-
// Seed a fast but crypto originating random generator
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
@@ -89,9 +85,9 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
hc := &HeaderChain{
config: config,
chainDb: chainDb,
- headerCache: headerCache,
- tdCache: tdCache,
- numberCache: numberCache,
+ headerCache: lru.NewCache[common.Hash, *types.Header](headerCacheLimit),
+ tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit),
+ numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit),
procInterrupt: procInterrupt,
rand: mrand.New(mrand.NewSource(seed.Int64())),
engine: engine,
@@ -115,8 +111,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
// from the cache or database
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
if cached, ok := hc.numberCache.Get(hash); ok {
- number := cached.(uint64)
- return &number
+ return &cached
}
number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
if number != nil {
@@ -442,7 +437,7 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma
func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
// Short circuit if the td's already in the cache, retrieve otherwise
if cached, ok := hc.tdCache.Get(hash); ok {
- return cached.(*big.Int)
+ return cached
}
td := rawdb.ReadTd(hc.chainDb, hash, number)
if td == nil {
@@ -458,7 +453,7 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header {
// Short circuit if the header's already in the cache, retrieve otherwise
if header, ok := hc.headerCache.Get(hash); ok {
- return header.(*types.Header)
+ return header
}
header := rawdb.ReadHeader(hc.chainDb, hash, number)
if header == nil {
@@ -525,10 +520,9 @@ func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
if !ok {
break
}
- h := header.(*types.Header)
- rlpData, _ := rlp.EncodeToBytes(h)
+ rlpData, _ := rlp.EncodeToBytes(header)
headers = append(headers, rlpData)
- hash = h.ParentHash
+ hash = header.ParentHash
count--
number--
}
diff --git a/core/state/database.go b/core/state/database.go
index 9f270bf0f9..2de0650df8 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -21,12 +21,11 @@ import (
"fmt"
"github.com/ethereum/go-ethereum/common"
- lru2 "github.com/ethereum/go-ethereum/common/lru"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
- lru "github.com/hashicorp/golang-lru"
)
const (
@@ -130,20 +129,19 @@ func NewDatabase(db ethdb.Database) Database {
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
// large memory cache.
func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
- csc, _ := lru.New(codeSizeCacheSize)
return &cachingDB{
db: trie.NewDatabaseWithConfig(db, config),
disk: db,
- codeSizeCache: csc,
- codeCache: lru2.NewSizeConstrainedLRU(codeCacheSize),
+ codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
+ codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
}
}
type cachingDB struct {
db *trie.Database
disk ethdb.KeyValueStore
- codeSizeCache *lru.Cache
- codeCache *lru2.SizeConstrainedLRU
+ codeSizeCache *lru.Cache[common.Hash, int]
+ codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
}
// OpenTrie opens the main account trie at a specific root hash.
@@ -176,10 +174,11 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
// ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
- if code := db.codeCache.Get(codeHash); len(code) > 0 {
+ code, _ := db.codeCache.Get(codeHash)
+ if len(code) > 0 {
return code, nil
}
- code := rawdb.ReadCode(db.disk, codeHash)
+ code = rawdb.ReadCode(db.disk, codeHash)
if len(code) > 0 {
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
@@ -192,10 +191,11 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error
// code can't be found in the cache, then check the existence with **new**
// db scheme.
func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) {
- if code := db.codeCache.Get(codeHash); len(code) > 0 {
+ code, _ := db.codeCache.Get(codeHash)
+ if len(code) > 0 {
return code, nil
}
- code := rawdb.ReadCodeWithPrefix(db.disk, codeHash)
+ code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
if len(code) > 0 {
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
@@ -207,7 +207,7 @@ func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]b
// ContractCodeSize retrieves a particular contracts code's size.
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
- return cached.(int), nil
+ return cached, nil
}
code, err := db.ContractCode(addrHash, codeHash)
return len(code), err
diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go
index ea0233dd08..c424dd2946 100644
--- a/eth/filters/filter_system.go
+++ b/eth/filters/filter_system.go
@@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -35,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
- lru "github.com/hashicorp/golang-lru"
)
// Config represents the configuration of the filter system.
@@ -77,21 +77,16 @@ type Backend interface {
// FilterSystem holds resources shared by all filters.
type FilterSystem struct {
backend Backend
- logsCache *lru.Cache
+ logsCache *lru.Cache[common.Hash, [][]*types.Log]
cfg *Config
}
// NewFilterSystem creates a filter system.
func NewFilterSystem(backend Backend, config Config) *FilterSystem {
config = config.withDefaults()
-
- cache, err := lru.New(config.LogCacheSize)
- if err != nil {
- panic(err)
- }
return &FilterSystem{
backend: backend,
- logsCache: cache,
+ logsCache: lru.NewCache[common.Hash, [][]*types.Log](config.LogCacheSize),
cfg: &config,
}
}
@@ -100,7 +95,7 @@ func NewFilterSystem(backend Backend, config Config) *FilterSystem {
func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
cached, ok := sys.logsCache.Get(blockHash)
if ok {
- return cached.([][]*types.Log), nil
+ return cached, nil
}
logs, err := sys.backend.GetLogs(ctx, blockHash, number)
diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go
index 6028ef03cf..47cc31999e 100644
--- a/eth/gasprice/feehistory.go
+++ b/eth/gasprice/feehistory.go
@@ -56,7 +56,12 @@ type blockFees struct {
err error
}
-// processedFees contains the results of a processed block and is also used for caching
+type cacheKey struct {
+ number uint64
+ percentiles string
+}
+
+// processedFees contains the results of a processed block.
type processedFees struct {
reward []*big.Int
baseFee, nextBaseFee *big.Int
@@ -270,13 +275,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
oracle.processBlock(fees, rewardPercentiles)
results <- fees
} else {
- cacheKey := struct {
- number uint64
- percentiles string
- }{blockNumber, string(percentileKey)}
+ cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)}
if p, ok := oracle.historyCache.Get(cacheKey); ok {
- fees.results = p.(processedFees)
+ fees.results = p
results <- fees
} else {
if len(rewardPercentiles) != 0 {
diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go
index 00128a5dc8..604ad5e104 100644
--- a/eth/gasprice/gasprice.go
+++ b/eth/gasprice/gasprice.go
@@ -23,13 +23,13 @@ import (
"sync"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
- lru "github.com/hashicorp/golang-lru"
)
const sampleNumber = 3 // Number of transactions sampled in a block
@@ -72,7 +72,8 @@ type Oracle struct {
checkBlocks, percentile int
maxHeaderHistory, maxBlockHistory int
- historyCache *lru.Cache
+
+ historyCache *lru.Cache[cacheKey, processedFees]
}
// NewOracle returns a new gasprice oracle which can recommend suitable
@@ -114,7 +115,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
}
- cache, _ := lru.New(2048)
+ cache := lru.NewCache[cacheKey, processedFees](2048)
headEvent := make(chan core.ChainHeadEvent, 1)
backend.SubscribeChainHeadEvent(headEvent)
go func() {
diff --git a/p2p/discover/v5wire/session.go b/p2p/discover/v5wire/session.go
index d52b5c1181..862c21fcee 100644
--- a/p2p/discover/v5wire/session.go
+++ b/p2p/discover/v5wire/session.go
@@ -22,10 +22,10 @@ import (
"encoding/binary"
"time"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/hashicorp/golang-lru/simplelru"
)
const handshakeTimeout = time.Second
@@ -33,7 +33,7 @@ const handshakeTimeout = time.Second
// The SessionCache keeps negotiated encryption keys and
// state for in-progress handshakes in the Discovery v5 wire protocol.
type SessionCache struct {
- sessions *simplelru.LRU
+ sessions lru.BasicLRU[sessionID, *session]
handshakes map[sessionID]*Whoareyou
clock mclock.Clock
@@ -62,12 +62,8 @@ func (s *session) keysFlipped() *session {
}
func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
- cache, err := simplelru.NewLRU(maxItems, nil)
- if err != nil {
- panic("can't create session cache")
- }
return &SessionCache{
- sessions: cache,
+ sessions: lru.NewBasicLRU[sessionID, *session](maxItems),
handshakes: make(map[sessionID]*Whoareyou),
clock: clock,
nonceGen: generateNonce,
@@ -95,11 +91,8 @@ func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
// session returns the current session for the given node, if any.
func (sc *SessionCache) session(id enode.ID, addr string) *session {
- item, ok := sc.sessions.Get(sessionID{id, addr})
- if !ok {
- return nil
- }
- return item.(*session)
+ item, _ := sc.sessions.Get(sessionID{id, addr})
+ return item
}
// readKey returns the current read key for the given node.
diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go
index 3f914d6e94..8f1c221b80 100644
--- a/p2p/dnsdisc/client.go
+++ b/p2p/dnsdisc/client.go
@@ -27,12 +27,12 @@ import (
"sync"
"time"
+ "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
- lru "github.com/hashicorp/golang-lru"
"golang.org/x/sync/singleflight"
"golang.org/x/time/rate"
)
@@ -41,7 +41,7 @@ import (
type Client struct {
cfg Config
clock mclock.Clock
- entries *lru.Cache
+ entries *lru.Cache[string, entry]
ratelimit *rate.Limiter
singleflight singleflight.Group
}
@@ -96,14 +96,10 @@ func (cfg Config) withDefaults() Config {
// NewClient creates a client.
func NewClient(cfg Config) *Client {
cfg = cfg.withDefaults()
- cache, err := lru.New(cfg.CacheLimit)
- if err != nil {
- panic(err)
- }
rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
return &Client{
cfg: cfg,
- entries: cache,
+ entries: lru.NewCache[string, entry](cfg.CacheLimit),
clock: mclock.System{},
ratelimit: rlimit,
}
@@ -176,7 +172,7 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry,
}
cacheKey := truncateHash(hash)
if e, ok := c.entries.Get(cacheKey); ok {
- return e.(entry), nil
+ return e, nil
}
ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {