Skip to content

Commit 0cefe16

Browse files
authored
Merge pull request #38 from Code-Hex/add/with-ref-count
added WithReferenceCount option
2 parents 8c94293 + 9c36caf commit 0cefe16

File tree

9 files changed

+166
-10
lines changed

9 files changed

+166
-10
lines changed

cache.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ var (
3838

3939
// Item is an item
4040
type Item[K comparable, V any] struct {
41-
Key K
42-
Value V
43-
Expiration time.Time
41+
Key K
42+
Value V
43+
Expiration time.Time
44+
InitialReferenceCount int
4445
}
4546

4647
// Expired returns true if the item has expired.
@@ -51,13 +52,20 @@ func (item *Item[K, V]) Expired() bool {
5152
return nowFunc().After(item.Expiration)
5253
}
5354

55+
// GetReferenceCount returns reference count to be used when setting
56+
// the cache item for the first time.
57+
func (item *Item[K, V]) GetReferenceCount() int {
58+
return item.InitialReferenceCount
59+
}
60+
5461
var nowFunc = time.Now
5562

5663
// ItemOption is an option for cache item.
5764
type ItemOption func(*itemOptions)
5865

5966
type itemOptions struct {
60-
expiration time.Time // default none
67+
expiration time.Time // default none
68+
referenceCount int
6169
}
6270

6371
// WithExpiration is an option to set expiration time for any items.
@@ -68,23 +76,34 @@ func WithExpiration(exp time.Duration) ItemOption {
6876
}
6977
}
7078

79+
// WithReferenceCount is an option to set reference count for any items.
80+
// This option is only applicable to cache policies that have a reference count (e.g., Clock, LFU).
81+
// referenceCount specifies the reference count value to set for the cache item.
82+
//
83+
// the default is 1.
84+
func WithReferenceCount(referenceCount int) ItemOption {
85+
return func(o *itemOptions) {
86+
o.referenceCount = referenceCount
87+
}
88+
}
89+
7190
// newItem creates a new item with specified any options.
7291
func newItem[K comparable, V any](key K, val V, opts ...ItemOption) *Item[K, V] {
7392
o := new(itemOptions)
7493
for _, optFunc := range opts {
7594
optFunc(o)
7695
}
7796
return &Item[K, V]{
78-
Key: key,
79-
Value: val,
80-
Expiration: o.expiration,
97+
Key: key,
98+
Value: val,
99+
Expiration: o.expiration,
100+
InitialReferenceCount: o.referenceCount,
81101
}
82102
}
83103

84104
// Cache is a thread safe cache.
85105
type Cache[K comparable, V any] struct {
86106
cache Interface[K, *Item[K, V]]
87-
//expirations map[K]chan struct{}
88107
// mu is used to do lock in some method process.
89108
mu sync.Mutex
90109
janitor *janitor

example_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
cache "github.com/Code-Hex/go-generics-cache"
9+
"github.com/Code-Hex/go-generics-cache/policy/lfu"
910
)
1011

1112
func ExampleCache() {
@@ -76,6 +77,32 @@ func ExampleWithExpiration() {
7677
// 0 false
7778
}
7879

80+
func ExampleWithReferenceCount() {
81+
c := cache.New(cache.AsLFU[string, int](lfu.WithCapacity(2)))
82+
83+
// set item with reference count
84+
c.Set("a", 1, cache.WithReferenceCount(5))
85+
86+
// check item is set.
87+
gota, aok := c.Get("a")
88+
fmt.Println(gota, aok)
89+
90+
c.Set("b", 2)
91+
c.Set("c", 3)
92+
93+
// evicted becauce the lowest reference count.
94+
gotb, bok := c.Get("b")
95+
fmt.Println(gotb, bok)
96+
97+
gotc, cok := c.Get("c")
98+
fmt.Println(gotc, cok)
99+
100+
// Output:
101+
// 1 true
102+
// 0 false
103+
// 3 true
104+
}
105+
79106
func ExampleCache_Delete() {
80107
c := cache.New(cache.AsMRU[string, int]())
81108
c.Set("a", 1)

policy/clock/clock.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package clock
22

33
import (
44
"container/ring"
5+
6+
"github.com/Code-Hex/go-generics-cache/policy/internal/policyutil"
57
)
68

79
// Cache is used The clock cache replacement policy.
@@ -62,6 +64,9 @@ func NewCache[K comparable, V any](opts ...Option) *Cache[K, V] {
6264
}
6365

6466
// Set sets any item to the cache. replacing any existing item.
67+
//
68+
// If value satisfies "interface{ GetReferenceCount() int }", the value of
69+
// the GetReferenceCount() method is used to set the initial value of reference count.
6570
func (c *Cache[K, V]) Set(key K, val V) {
6671
if e, ok := c.items[key]; ok {
6772
entry := e.Value.(*entry[K, V])
@@ -73,7 +78,7 @@ func (c *Cache[K, V]) Set(key K, val V) {
7378
c.hand.Value = &entry[K, V]{
7479
key: key,
7580
val: val,
76-
referenceCount: 1,
81+
referenceCount: policyutil.GetReferenceCount(val),
7782
}
7883
c.items[key] = c.hand
7984
c.hand = c.hand.Next()

policy/clock/clock_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import (
88
"github.com/Code-Hex/go-generics-cache/policy/clock"
99
)
1010

11+
type tmp struct {
12+
i int
13+
}
14+
15+
func (t *tmp) GetReferenceCount() int { return t.i }
16+
1117
func TestSet(t *testing.T) {
1218
// set capacity is 1
1319
cache := clock.NewCache[string, int](clock.WithCapacity(1))
@@ -43,6 +49,25 @@ func TestSet(t *testing.T) {
4349
if bar != 100 || !ok {
4450
t.Fatalf("invalid replacing value bar %d, cachehit %v", bar, ok)
4551
}
52+
53+
t.Run("with initilal reference count", func(t *testing.T) {
54+
cache := clock.NewCache[string, *tmp](clock.WithCapacity(2))
55+
cache.Set("foo", &tmp{i: 10}) // the highest reference count
56+
cache.Set("foo2", &tmp{i: 2}) // expected eviction
57+
if got := cache.Len(); got != 2 {
58+
t.Fatalf("invalid length: %d", got)
59+
}
60+
61+
cache.Set("foo3", &tmp{i: 3})
62+
63+
// checks deleted the lowest reference count
64+
if _, ok := cache.Get("foo2"); ok {
65+
t.Fatalf("invalid delete oldest value foo2 %v", ok)
66+
}
67+
if _, ok := cache.Get("foo"); !ok {
68+
t.Fatalf("invalid value foo is not found")
69+
}
70+
})
4671
}
4772

4873
func TestDelete(t *testing.T) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package policyutil
2+
3+
// GetReferenceCount gets reference count from cache value.
4+
func GetReferenceCount(v any) int {
5+
if getter, ok := v.(interface{ GetReferenceCount() int }); ok {
6+
return getter.GetReferenceCount()
7+
}
8+
return 1
9+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package policyutil
2+
3+
import (
4+
"testing"
5+
)
6+
7+
type refCounter struct {
8+
count int
9+
}
10+
11+
func (r refCounter) GetReferenceCount() int {
12+
return r.count
13+
}
14+
15+
func TestGetReferenceCount(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
input any
19+
want int
20+
}{
21+
{
22+
name: "with GetReferenceCount() method",
23+
input: refCounter{count: 5},
24+
want: 5,
25+
},
26+
{
27+
name: "without GetReferenceCount() method",
28+
input: "sample string",
29+
want: 1,
30+
},
31+
}
32+
33+
for _, test := range tests {
34+
t.Run(test.name, func(t *testing.T) {
35+
output := GetReferenceCount(test.input)
36+
if output != test.want {
37+
t.Errorf("want %d, got %d", test.want, output)
38+
}
39+
})
40+
}
41+
}

policy/lfu/lfu.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ func (c *Cache[K, V]) Get(key K) (zero V, _ bool) {
6161
}
6262

6363
// Set sets a value to the cache with key. replacing any existing value.
64+
//
65+
// If value satisfies "interface{ GetReferenceCount() int }", the value of
66+
// the GetReferenceCount() method is used to set the initial value of reference count.
6467
func (c *Cache[K, V]) Set(key K, val V) {
6568
if e, ok := c.items[key]; ok {
6669
c.queue.update(e, val)

policy/lfu/lfu_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import (
66
"github.com/Code-Hex/go-generics-cache/policy/lfu"
77
)
88

9+
type tmp struct {
10+
i int
11+
}
12+
13+
func (t *tmp) GetReferenceCount() int { return t.i }
14+
915
func TestSet(t *testing.T) {
1016
// set capacity is 1
1117
cache := lfu.NewCache[string, int](lfu.WithCapacity(1))
@@ -41,6 +47,25 @@ func TestSet(t *testing.T) {
4147
if bar != 100 || !ok {
4248
t.Fatalf("invalid replacing value bar %d, cachehit %v", bar, ok)
4349
}
50+
51+
t.Run("with initilal reference count", func(t *testing.T) {
52+
cache := lfu.NewCache[string, *tmp](lfu.WithCapacity(2))
53+
cache.Set("foo", &tmp{i: 10}) // the highest reference count
54+
cache.Set("foo2", &tmp{i: 2}) // expected eviction
55+
if got := cache.Len(); got != 2 {
56+
t.Fatalf("invalid length: %d", got)
57+
}
58+
59+
cache.Set("foo3", &tmp{i: 3})
60+
61+
// checks deleted the lowest reference count
62+
if _, ok := cache.Get("foo2"); ok {
63+
t.Fatalf("invalid delete oldest value foo2 %v", ok)
64+
}
65+
if _, ok := cache.Get("foo"); !ok {
66+
t.Fatalf("invalid value foo is not found")
67+
}
68+
})
4469
}
4570

4671
func TestDelete(t *testing.T) {

policy/lfu/priotiry_queue.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package lfu
33
import (
44
"container/heap"
55
"time"
6+
7+
"github.com/Code-Hex/go-generics-cache/policy/internal/policyutil"
68
)
79

810
type entry[K comparable, V any] struct {
@@ -18,7 +20,7 @@ func newEntry[K comparable, V any](key K, val V) *entry[K, V] {
1820
index: 0,
1921
key: key,
2022
val: val,
21-
referenceCount: 1,
23+
referenceCount: policyutil.GetReferenceCount(val),
2224
referencedAt: time.Now(),
2325
}
2426
}

0 commit comments

Comments
 (0)