|
1 | 1 | package cache
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "errors" |
5 |
| - "fmt" |
6 | 4 | "sync"
|
7 |
| - "time" |
8 | 5 | )
|
9 | 6 |
|
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 | +// Cache is a common-cache interface. |
| 8 | +type Cache[K comparable, V any] interface { |
| 9 | + Get(key K) (value V, ok bool) |
| 10 | + Set(key K, val V) |
120 | 11 | }
|
121 | 12 |
|
122 | 13 | // NumberCache is a in-memory cache which is able to store only Number constraint.
|
123 | 14 | type NumberCache[K comparable, V Number] struct {
|
124 |
| - *Cache[K, V] |
| 15 | + Cache[K, V] |
125 | 16 | // nmu is used to do lock in Increment/Decrement process.
|
126 | 17 | // Note that this must be here as a separate mutex because mu in Cache struct is Locked in GetItem,
|
127 | 18 | // and if we call mu.Lock in Increment/Decrement, it will cause deadlock.
|
128 | 19 | nmu sync.Mutex
|
129 | 20 | }
|
130 | 21 |
|
131 | 22 | // NewNumber creates a new cache for Number constraint.
|
132 |
| -func NewNumber[K comparable, V Number]() *NumberCache[K, V] { |
| 23 | +func NewNumber[K comparable, V Number](baseCache Cache[K, V]) *NumberCache[K, V] { |
133 | 24 | return &NumberCache[K, V]{
|
134 |
| - Cache: New[K, V](), |
| 25 | + Cache: baseCache, |
135 | 26 | }
|
136 | 27 | }
|
137 | 28 |
|
138 | 29 | // 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) { |
| 30 | +// Returns the incremented value. |
| 31 | +func (nc *NumberCache[K, V]) Increment(key K, n V) V { |
142 | 32 | // In order to avoid lost update, we must lock whole Increment/Decrement process.
|
143 | 33 | nc.nmu.Lock()
|
144 | 34 | 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 |
| 35 | + got, _ := nc.Cache.Get(key) |
| 36 | + nv := got + n |
| 37 | + nc.Cache.Set(key, nv) |
| 38 | + return nv |
155 | 39 | }
|
156 | 40 |
|
157 | 41 | // 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) { |
| 42 | +// Returns the decremented value. |
| 43 | +func (nc *NumberCache[K, V]) Decrement(key K, n V) V { |
161 | 44 | nc.nmu.Lock()
|
162 | 45 | 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 |
| 46 | + got, _ := nc.Cache.Get(key) |
| 47 | + nv := got - n |
| 48 | + nc.Cache.Set(key, nv) |
| 49 | + return nv |
173 | 50 | }
|
0 commit comments