Skip to content

Commit 5c0a125

Browse files
committed
added simple pkg and move some code to it
1 parent 8ade14b commit 5c0a125

File tree

7 files changed

+294
-279
lines changed

7 files changed

+294
-279
lines changed

cache.go

Lines changed: 18 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,49 @@
11
package cache
22

33
import (
4-
"errors"
5-
"fmt"
64
"sync"
7-
"time"
85
)
96

10-
var (
11-
// ErrNotFound is an error which indicate an item is not found.
12-
ErrNotFound = errors.New("not found item")
13-
14-
// ErrExpired is an error which indicate an item is expired.
15-
ErrExpired = errors.New("expired item")
16-
)
17-
18-
// Item is an item
19-
type Item[T any] struct {
20-
Value T
21-
Expiration time.Duration
22-
CreatedAt time.Time
23-
}
24-
25-
var nowFunc = time.Now
26-
27-
// HasExpired returns true if the item has expired.
28-
// If the item's expiration is zero value, returns false.
29-
func (i Item[T]) HasExpired() bool {
30-
if i.Expiration <= 0 {
31-
return false
32-
}
33-
return i.CreatedAt.Add(i.Expiration).Before(nowFunc())
34-
}
35-
36-
// Cache is a base struct for creating in-memory cache.
37-
type Cache[K comparable, V any] struct {
38-
items map[K]Item[V]
39-
mu sync.RWMutex
40-
}
41-
42-
// New creates a new cache.
43-
func New[K comparable, V any]() *Cache[K, V] {
44-
return &Cache[K, V]{
45-
items: make(map[K]Item[V]),
46-
}
47-
}
48-
49-
// ItemOption is an option for cache item.
50-
type ItemOption func(o *options)
51-
52-
type options struct {
53-
expiration time.Duration // default none
54-
}
55-
56-
// WithExpiration is an option to set expiration time for any items.
57-
func WithExpiration(exp time.Duration) ItemOption {
58-
return func(o *options) {
59-
o.expiration = exp
60-
}
61-
}
62-
63-
// Set sets any item to the cache. replacing any existing item.
64-
// The default item never expires.
65-
func (c *Cache[K, V]) Set(k K, v V, opts ...ItemOption) {
66-
o := new(options)
67-
for _, optFunc := range opts {
68-
optFunc(o)
69-
}
70-
item := Item[V]{
71-
Value: v,
72-
Expiration: o.expiration,
73-
CreatedAt: nowFunc(),
74-
}
75-
c.SetItem(k, item)
76-
}
77-
78-
// SetItem sets any item to the cache. replacing any existing item.
79-
// The default item never expires.
80-
func (c *Cache[K, V]) SetItem(k K, v Item[V]) {
81-
c.mu.Lock()
82-
c.items[k] = v
83-
c.mu.Unlock()
84-
}
85-
86-
// Get gets an item from the cache.
87-
// Returns the item or zero value, and a bool indicating whether the key was found.
88-
func (c *Cache[K, V]) Get(k K) (val V, ok bool) {
89-
item, err := c.GetItem(k)
90-
if err != nil {
91-
return
92-
}
93-
return item.Value, true
94-
}
95-
96-
// GetItem gets an item from the cache.
97-
// Returns an error if the item was not found or expired. If there is no error, the
98-
// incremented value is returned.
99-
func (c *Cache[K, V]) GetItem(k K) (val Item[V], _ error) {
100-
c.mu.RLock()
101-
defer c.mu.RUnlock()
102-
103-
got, found := c.items[k]
104-
if !found {
105-
return val, fmt.Errorf("key[%v]: %w", k, ErrNotFound)
106-
}
107-
if got.HasExpired() {
108-
return val, fmt.Errorf("key[%v]: %w", k, ErrExpired)
109-
}
110-
return got, nil
111-
}
112-
113-
// Keys returns cache keys. the order is random.
114-
func (c *Cache[K, _]) Keys() []K {
115-
ret := make([]K, 0, len(c.items))
116-
for key := range c.items {
117-
ret = append(ret, key)
118-
}
119-
return ret
7+
type Cache[K comparable, V any] interface {
8+
Get(key K) (value V, ok bool)
9+
Set(key K, val V)
12010
}
12111

12212
// NumberCache is a in-memory cache which is able to store only Number constraint.
12313
type NumberCache[K comparable, V Number] struct {
124-
*Cache[K, V]
14+
Cache[K, V]
12515
// nmu is used to do lock in Increment/Decrement process.
12616
// Note that this must be here as a separate mutex because mu in Cache struct is Locked in GetItem,
12717
// and if we call mu.Lock in Increment/Decrement, it will cause deadlock.
12818
nmu sync.Mutex
12919
}
13020

13121
// NewNumber creates a new cache for Number constraint.
132-
func NewNumber[K comparable, V Number]() *NumberCache[K, V] {
22+
func NewNumber[K comparable, V Number](baseCache Cache[K, V]) *NumberCache[K, V] {
13323
return &NumberCache[K, V]{
134-
Cache: New[K, V](),
24+
Cache: baseCache,
13525
}
13626
}
13727

13828
// Increment an item of type Number constraint by n.
139-
// Returns an error if the item was not found or expired. If there is no error, the
140-
// incremented value is returned.
141-
func (nc *NumberCache[K, V]) Increment(k K, n V) (val V, err error) {
29+
// Returns the incremented value.
30+
func (nc *NumberCache[K, V]) Increment(key K, n V) V {
14231
// In order to avoid lost update, we must lock whole Increment/Decrement process.
14332
nc.nmu.Lock()
14433
defer nc.nmu.Unlock()
145-
got, err := nc.Cache.GetItem(k)
146-
if err != nil {
147-
return val, err
148-
}
149-
150-
nv := got.Value + n
151-
got.Value = nv
152-
nc.Cache.SetItem(k, got)
153-
154-
return nv, nil
34+
got, _ := nc.Cache.Get(key)
35+
nv := got + n
36+
nc.Cache.Set(key, nv)
37+
return nv
15538
}
15639

15740
// Decrement an item of type Number constraint by n.
158-
// Returns an error if the item was not found or expired. If there is no error, the
159-
// decremented value is returned.
160-
func (nc *NumberCache[K, V]) Decrement(k K, n V) (val V, err error) {
41+
// Returns the decremented value.
42+
func (nc *NumberCache[K, V]) Decrement(key K, n V) V {
16143
nc.nmu.Lock()
16244
defer nc.nmu.Unlock()
163-
got, err := nc.Cache.GetItem(k)
164-
if err != nil {
165-
return val, err
166-
}
167-
168-
nv := got.Value - n
169-
got.Value = nv
170-
nc.Cache.SetItem(k, got)
171-
172-
return nv, nil
45+
got, _ := nc.Cache.Get(key)
46+
nv := got - n
47+
nc.Cache.Set(key, nv)
48+
return nv
17349
}

cache_test.go

Lines changed: 8 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,23 @@
1-
package cache
1+
package cache_test
22

33
import (
4-
"errors"
54
"sync"
65
"testing"
7-
"time"
8-
)
9-
10-
func TestHasExpired(t *testing.T) {
11-
cases := []struct {
12-
name string
13-
exp time.Duration
14-
createdAt time.Time
15-
current time.Time
16-
want bool
17-
}{
18-
// expiration == createdAt + exp
19-
{
20-
name: "item expiration is zero",
21-
want: false,
22-
},
23-
{
24-
name: "item expiration > current time",
25-
exp: time.Hour * 24,
26-
createdAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
27-
current: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
28-
want: false,
29-
},
30-
{
31-
name: "item expiration < current time",
32-
exp: time.Hour * 24,
33-
createdAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
34-
current: time.Date(2009, time.November, 12, 23, 0, 0, 0, time.UTC),
35-
want: true,
36-
},
37-
{
38-
name: "item expiration == current time",
39-
exp: time.Second,
40-
createdAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
41-
current: time.Date(2009, time.November, 10, 23, 0, 1, 0, time.UTC),
42-
want: false,
43-
},
44-
}
45-
for _, tc := range cases {
46-
t.Run(tc.name, func(t *testing.T) {
47-
backup := nowFunc
48-
nowFunc = func() time.Time { return tc.current }
49-
defer func() { nowFunc = backup }()
50-
51-
it := Item[int]{
52-
Expiration: tc.exp,
53-
CreatedAt: tc.createdAt,
54-
}
55-
if got := it.HasExpired(); tc.want != got {
56-
t.Fatalf("want %v, but got %v", tc.want, got)
57-
}
58-
})
59-
}
60-
}
61-
62-
func TestGetItemExpired(t *testing.T) {
63-
c := New[struct{}, int]()
64-
c.SetItem(struct{}{}, Item[int]{
65-
Value: 1,
66-
Expiration: time.Hour * 24,
67-
CreatedAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
68-
})
696

70-
backup := nowFunc
71-
nowFunc = func() time.Time {
72-
return time.Date(2009, time.November, 12, 23, 0, 0, 0, time.UTC)
73-
}
74-
defer func() { nowFunc = backup }()
75-
76-
v, err := c.GetItem(struct{}{})
77-
if !errors.Is(err, ErrExpired) {
78-
t.Errorf("want error %v but got %v", ErrExpired, err)
79-
}
80-
zeroValItem := Item[int]{}
81-
if zeroValItem != v {
82-
t.Errorf("want %v but got %v", zeroValItem, v)
83-
}
84-
85-
}
7+
cache "github.com/Code-Hex/go-generics-cache"
8+
"github.com/Code-Hex/go-generics-cache/simple"
9+
)
8610

8711
func TestMultiThreadIncr(t *testing.T) {
88-
nc := NewNumber[string, int]()
12+
nc := cache.NewNumber[string, int](simple.New[string, int]())
8913
nc.Set("counter", 0)
9014

9115
var wg sync.WaitGroup
9216

9317
for i := 0; i < 100; i++ {
9418
wg.Add(1)
9519
go func() {
96-
_, err := nc.Increment("counter", 1)
97-
if err != nil {
98-
t.Logf("err: %v", err)
99-
}
20+
_ = nc.Increment("counter", 1)
10021
wg.Done()
10122
}()
10223
}
@@ -109,18 +30,15 @@ func TestMultiThreadIncr(t *testing.T) {
10930
}
11031

11132
func TestMultiThreadDecr(t *testing.T) {
112-
nc := NewNumber[string, int]()
33+
nc := cache.NewNumber[string, int](simple.New[string, int]())
11334
nc.Set("counter", 100)
11435

11536
var wg sync.WaitGroup
11637

11738
for i := 0; i < 100; i++ {
11839
wg.Add(1)
11940
go func() {
120-
_, err := nc.Decrement("counter", 1)
121-
if err != nil {
122-
t.Logf("err: %v", err)
123-
}
41+
_ = nc.Decrement("counter", 1)
12442
wg.Done()
12543
}()
12644
}

0 commit comments

Comments
 (0)