Skip to content

Commit 8dd9c88

Browse files
committed
freelru: make cache growable
1 parent 39b4bbf commit 8dd9c88

10 files changed

Lines changed: 464 additions & 417 deletions

File tree

common/udpnat2/conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var (
3030
)
3131

3232
type natConn struct {
33-
cache *freelru.ConcurrentLRU[netip.AddrPort, *natConn]
33+
cache *freelru.Cache[netip.AddrPort, *natConn]
3434
writer N.PacketWriter
3535
localAddr M.Socksaddr
3636
handlerAccess sync.RWMutex

common/udpnat2/service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
)
1616

1717
type Service struct {
18-
cache *freelru.ConcurrentLRU[netip.AddrPort, *natConn]
18+
cache *freelru.Cache[netip.AddrPort, *natConn]
1919
handler N.UDPConnectionHandlerEx
2020
prepare PrepareFunc
2121
}
@@ -26,7 +26,7 @@ func New(handler N.UDPConnectionHandlerEx, prepare PrepareFunc, timeout time.Dur
2626
if timeout == 0 {
2727
panic("invalid timeout")
2828
}
29-
cache := common.Must1(freelru.NewConcurrent[netip.AddrPort, *natConn](1024, maphash.NewHasher[netip.AddrPort]().Hash32, shared))
29+
cache := common.Must1(freelru.New[netip.AddrPort, *natConn](1024, maphash.NewHasher[netip.AddrPort]().Hash32, shared))
3030
cache.SetLifetime(timeout)
3131
cache.SetHealthCheck(func(port netip.AddrPort, conn *natConn) bool {
3232
select {

contrab/freelru/cache.go

Lines changed: 178 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,204 @@
1-
// Licensed to Elasticsearch B.V. under one or more contributor
2-
// license agreements. See the NOTICE file distributed with
3-
// this work for additional information regarding copyright
4-
// ownership. Elasticsearch B.V. licenses this file to you under
5-
// the Apache License, Version 2.0 (the "License"); you may
6-
// not use this file except in compliance with the License.
7-
// You may obtain a copy of the License at
8-
//
9-
// http://www.apache.org/licenses/LICENSE-2.0
10-
//
11-
// Unless required by applicable law or agreed to in writing,
12-
// software distributed under the License is distributed on an
13-
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14-
// KIND, either express or implied. See the License for the
15-
// specific language governing permissions and limitations
16-
// under the License.
17-
181
package freelru
192

203
import "time"
214

22-
type Cache[K comparable, V comparable] interface {
23-
// SetLifetime sets the default lifetime of LRU elements.
24-
// Lifetime 0 means "forever".
25-
SetLifetime(lifetime time.Duration)
5+
// Cache is a thread-safe LRU cache that can use either a single lock or
6+
// multiple shards.
7+
type Cache[K comparable, V comparable] struct {
8+
synced *SyncedLRU[K, V]
9+
sharded *ShardedLRU[K, V]
10+
}
11+
12+
// New creates a thread-safe, growable LRU cache with the given maximum capacity.
13+
// When sharded is false, the cache preserves exact LRU behavior with a single
14+
// lock. When sharded is true, it reduces lock contention at the cost of exact
15+
// global LRU behavior.
16+
func New[K comparable, V comparable](capacity uint32, hash HashKeyCallback[K], sharded bool) (*Cache[K, V], error) {
17+
if sharded {
18+
cache, err := newSharded[K, V](capacity, hash)
19+
if err != nil {
20+
return nil, err
21+
}
22+
return &Cache[K, V]{sharded: cache}, nil
23+
}
24+
cache, err := newSynced[K, V](capacity, hash)
25+
if err != nil {
26+
return nil, err
27+
}
28+
return &Cache[K, V]{synced: cache}, nil
29+
}
2630

27-
// SetOnEvict sets the OnEvict callback function.
28-
// The onEvict function is called for each evicted lru entry.
29-
SetOnEvict(onEvict OnEvictCallback[K, V])
31+
func (lru *Cache[K, V]) SetLifetime(lifetime time.Duration) {
32+
if lru.sharded != nil {
33+
lru.sharded.SetLifetime(lifetime)
34+
} else {
35+
lru.synced.SetLifetime(lifetime)
36+
}
37+
}
3038

31-
SetHealthCheck(healthCheck HealthCheckCallback[K, V])
39+
func (lru *Cache[K, V]) SetOnEvict(onEvict OnEvictCallback[K, V]) {
40+
if lru.sharded != nil {
41+
lru.sharded.SetOnEvict(onEvict)
42+
} else {
43+
lru.synced.SetOnEvict(onEvict)
44+
}
45+
}
3246

33-
// Len returns the number of elements stored in the cache.
34-
Len() int
47+
func (lru *Cache[K, V]) SetHealthCheck(healthCheck HealthCheckCallback[K, V]) {
48+
if lru.sharded != nil {
49+
lru.sharded.SetHealthCheck(healthCheck)
50+
} else {
51+
lru.synced.SetHealthCheck(healthCheck)
52+
}
53+
}
3554

36-
// AddWithLifetime adds a key:value to the cache with a lifetime.
37-
// Returns true, true if key was updated and eviction occurred.
38-
AddWithLifetime(key K, value V, lifetime time.Duration) (evicted bool)
55+
func (lru *Cache[K, V]) Len() int {
56+
if lru.sharded != nil {
57+
return lru.sharded.Len()
58+
}
59+
return lru.synced.Len()
60+
}
3961

40-
// Add adds a key:value to the cache.
41-
// Returns true, true if key was updated and eviction occurred.
42-
Add(key K, value V) (evicted bool)
62+
func (lru *Cache[K, V]) AddWithLifetime(key K, value V, lifetime time.Duration) bool {
63+
if lru.sharded != nil {
64+
return lru.sharded.AddWithLifetime(key, value, lifetime)
65+
}
66+
return lru.synced.AddWithLifetime(key, value, lifetime)
67+
}
68+
69+
func (lru *Cache[K, V]) Add(key K, value V) bool {
70+
if lru.sharded != nil {
71+
return lru.sharded.Add(key, value)
72+
}
73+
return lru.synced.Add(key, value)
74+
}
75+
76+
func (lru *Cache[K, V]) Get(key K) (V, bool) {
77+
if lru.sharded != nil {
78+
return lru.sharded.Get(key)
79+
}
80+
return lru.synced.Get(key)
81+
}
82+
83+
func (lru *Cache[K, V]) GetWithLifetime(key K) (V, time.Time, bool) {
84+
if lru.sharded != nil {
85+
return lru.sharded.GetWithLifetime(key)
86+
}
87+
return lru.synced.GetWithLifetime(key)
88+
}
4389

44-
// Get returns the value associated with the key, setting it as the most
45-
// recently used item.
46-
// If the found cache item is already expired, the evict function is called
47-
// and the return value indicates that the key was not found.
48-
Get(key K) (V, bool)
90+
func (lru *Cache[K, V]) GetWithLifetimeNoExpire(key K) (V, time.Time, bool) {
91+
if lru.sharded != nil {
92+
return lru.sharded.GetWithLifetimeNoExpire(key)
93+
}
94+
return lru.synced.GetWithLifetimeNoExpire(key)
95+
}
4996

50-
GetWithLifetime(key K) (V, time.Time, bool)
97+
func (lru *Cache[K, V]) GetAndRefresh(key K) (V, bool) {
98+
if lru.sharded != nil {
99+
return lru.sharded.GetAndRefresh(key)
100+
}
101+
return lru.synced.GetAndRefresh(key)
102+
}
51103

52-
GetWithLifetimeNoExpire(key K) (V, time.Time, bool)
104+
func (lru *Cache[K, V]) GetAndRefreshOrAdd(key K, constructor func() (V, bool)) (V, bool, bool) {
105+
if lru.sharded != nil {
106+
return lru.sharded.GetAndRefreshOrAdd(key, constructor)
107+
}
108+
return lru.synced.GetAndRefreshOrAdd(key, constructor)
109+
}
53110

54-
// GetAndRefresh returns the value associated with the key, setting it as the most
55-
// recently used item.
56-
// The lifetime of the found cache item is refreshed, even if it was already expired.
57-
GetAndRefresh(key K) (V, bool)
111+
func (lru *Cache[K, V]) Peek(key K) (V, bool) {
112+
if lru.sharded != nil {
113+
return lru.sharded.Peek(key)
114+
}
115+
return lru.synced.Peek(key)
116+
}
58117

59-
GetAndRefreshOrAdd(key K, constructor func() (V, bool)) (V, bool, bool)
118+
func (lru *Cache[K, V]) PeekWithLifetime(key K) (V, time.Time, bool) {
119+
if lru.sharded != nil {
120+
return lru.sharded.PeekWithLifetime(key)
121+
}
122+
return lru.synced.PeekWithLifetime(key)
123+
}
60124

61-
// Peek looks up a key's value from the cache, without changing its recent-ness.
62-
// If the found entry is already expired, the evict function is called.
63-
Peek(key K) (V, bool)
125+
func (lru *Cache[K, V]) UpdateLifetime(key K, value V, lifetime time.Duration) bool {
126+
if lru.sharded != nil {
127+
return lru.sharded.UpdateLifetime(key, value, lifetime)
128+
}
129+
return lru.synced.UpdateLifetime(key, value, lifetime)
130+
}
64131

65-
PeekWithLifetime(key K) (V, time.Time, bool)
132+
func (lru *Cache[K, V]) Contains(key K) bool {
133+
if lru.sharded != nil {
134+
return lru.sharded.Contains(key)
135+
}
136+
return lru.synced.Contains(key)
137+
}
66138

67-
UpdateLifetime(key K, value V, lifetime time.Duration) bool
139+
func (lru *Cache[K, V]) Remove(key K) bool {
140+
if lru.sharded != nil {
141+
return lru.sharded.Remove(key)
142+
}
143+
return lru.synced.Remove(key)
144+
}
68145

69-
// Contains checks for the existence of a key, without changing its recent-ness.
70-
// If the found entry is already expired, the evict function is called.
71-
Contains(key K) bool
146+
func (lru *Cache[K, V]) RemoveOldest() (K, V, bool) {
147+
if lru.sharded != nil {
148+
return lru.sharded.RemoveOldest()
149+
}
150+
return lru.synced.RemoveOldest()
151+
}
72152

73-
// Remove removes the key from the cache.
74-
// The return value indicates whether the key existed or not.
75-
// The evict function is called for the removed entry.
76-
Remove(key K) bool
153+
func (lru *Cache[K, V]) Keys() []K {
154+
if lru.sharded != nil {
155+
return lru.sharded.Keys()
156+
}
157+
return lru.synced.Keys()
158+
}
77159

78-
// RemoveOldest removes the oldest entry from the cache.
79-
// Key, value and an indicator of whether the entry has been removed is returned.
80-
// The evict function is called for the removed entry.
81-
RemoveOldest() (key K, value V, removed bool)
160+
func (lru *Cache[K, V]) Purge() {
161+
if lru.sharded != nil {
162+
lru.sharded.Purge()
163+
} else {
164+
lru.synced.Purge()
165+
}
166+
}
82167

83-
// Keys returns a slice of the keys in the cache, from oldest to newest.
84-
// Expired entries are not included.
85-
// The evict function is called for each expired item.
86-
Keys() []K
168+
func (lru *Cache[K, V]) PurgeExpired() {
169+
if lru.sharded != nil {
170+
lru.sharded.PurgeExpired()
171+
} else {
172+
lru.synced.PurgeExpired()
173+
}
174+
}
87175

88-
// Purge purges all data (key and value) from the LRU.
89-
// The evict function is called for each expired item.
90-
// The LRU metrics are reset.
91-
Purge()
176+
func (lru *Cache[K, V]) Metrics() Metrics {
177+
if lru.sharded != nil {
178+
return lru.sharded.Metrics()
179+
}
180+
return lru.synced.Metrics()
181+
}
92182

93-
// PurgeExpired purges all expired items from the LRU.
94-
// The evict function is called for each expired item.
95-
PurgeExpired()
183+
func (lru *Cache[K, V]) ResetMetrics() Metrics {
184+
if lru.sharded != nil {
185+
return lru.sharded.ResetMetrics()
186+
}
187+
return lru.synced.ResetMetrics()
188+
}
96189

97-
// Metrics returns the metrics of the cache.
98-
Metrics() Metrics
190+
func (lru *Cache[K, V]) dump() {
191+
if lru.sharded != nil {
192+
lru.sharded.dump()
193+
} else {
194+
lru.synced.dump()
195+
}
196+
}
99197

100-
// ResetMetrics resets the metrics of the cache and returns the previous state.
101-
ResetMetrics() Metrics
198+
func (lru *Cache[K, V]) PrintStats() {
199+
if lru.sharded != nil {
200+
lru.sharded.PrintStats()
201+
} else {
202+
lru.synced.PrintStats()
203+
}
102204
}

0 commit comments

Comments
 (0)