Skip to content

Commit 1c97b7a

Browse files
authored
fix(os/gcache): memory leak for LRU when adding operations more faster than deleting (#3823)
1 parent ea09457 commit 1c97b7a

12 files changed

+249
-230
lines changed

os/gcache/gcache.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
)
1818

1919
// Func is the cache function that calculates and returns the value.
20-
type Func func(ctx context.Context) (value interface{}, err error)
20+
type Func = func(ctx context.Context) (value interface{}, err error)
2121

2222
const (
2323
DurationNoExpire = time.Duration(0) // Expire duration that never expires.

os/gcache/gcache_adapter_memory.go

+86-77
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,12 @@ import (
2121

2222
// AdapterMemory is an adapter implements using memory.
2323
type AdapterMemory struct {
24-
// cap limits the size of the cache pool.
25-
// If the size of the cache exceeds the cap,
26-
// the cache expiration process performs according to the LRU algorithm.
27-
// It is 0 in default which means no limits.
28-
cap int
29-
data *adapterMemoryData // data is the underlying cache data which is stored in a hash table.
30-
expireTimes *adapterMemoryExpireTimes // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting.
31-
expireSets *adapterMemoryExpireSets // expireSets is the expiring timestamp to its key set mapping, which is used for quick indexing and deleting.
32-
lru *adapterMemoryLru // lru is the LRU manager, which is enabled when attribute cap > 0.
33-
lruGetList *glist.List // lruGetList is the LRU history according to Get function.
34-
eventList *glist.List // eventList is the asynchronous event list for internal data synchronization.
35-
closed *gtype.Bool // closed controls the cache closed or not.
36-
}
37-
38-
// Internal cache item.
39-
type adapterMemoryItem struct {
40-
v interface{} // Value.
41-
e int64 // Expire timestamp in milliseconds.
24+
data *memoryData // data is the underlying cache data which is stored in a hash table.
25+
expireTimes *memoryExpireTimes // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting.
26+
expireSets *memoryExpireSets // expireSets is the expiring timestamp to its key set mapping, which is used for quick indexing and deleting.
27+
lru *memoryLru // lru is the LRU manager, which is enabled when attribute cap > 0.
28+
eventList *glist.List // eventList is the asynchronous event list for internal data synchronization.
29+
closed *gtype.Bool // closed controls the cache closed or not.
4230
}
4331

4432
// Internal event item.
@@ -53,21 +41,28 @@ const (
5341
defaultMaxExpire = 9223372036854
5442
)
5543

56-
// NewAdapterMemory creates and returns a new memory cache object.
57-
func NewAdapterMemory(lruCap ...int) Adapter {
44+
// NewAdapterMemory creates and returns a new adapter_memory cache object.
45+
func NewAdapterMemory() *AdapterMemory {
46+
return doNewAdapterMemory()
47+
}
48+
49+
// NewAdapterMemoryLru creates and returns a new adapter_memory cache object with LRU.
50+
func NewAdapterMemoryLru(cap int) *AdapterMemory {
51+
c := doNewAdapterMemory()
52+
c.lru = newMemoryLru(cap)
53+
return c
54+
}
55+
56+
// doNewAdapterMemory creates and returns a new adapter_memory cache object.
57+
func doNewAdapterMemory() *AdapterMemory {
5858
c := &AdapterMemory{
59-
data: newAdapterMemoryData(),
60-
lruGetList: glist.New(true),
61-
expireTimes: newAdapterMemoryExpireTimes(),
62-
expireSets: newAdapterMemoryExpireSets(),
59+
data: newMemoryData(),
60+
expireTimes: newMemoryExpireTimes(),
61+
expireSets: newMemoryExpireSets(),
6362
eventList: glist.New(true),
6463
closed: gtype.NewBool(),
6564
}
66-
if len(lruCap) > 0 {
67-
c.cap = lruCap[0]
68-
c.lru = newMemCacheLru(c)
69-
}
70-
// Here may be a "timer leak" if adapter is manually changed from memory adapter.
65+
// Here may be a "timer leak" if adapter is manually changed from adapter_memory adapter.
7166
// Do not worry about this, as adapter is less changed, and it does nothing if it's not used.
7267
gtimer.AddSingleton(context.Background(), time.Second, c.syncEventAndClearExpired)
7368
return c
@@ -78,8 +73,9 @@ func NewAdapterMemory(lruCap ...int) Adapter {
7873
// It does not expire if `duration` == 0.
7974
// It deletes the keys of `data` if `duration` < 0 or given `value` is nil.
8075
func (c *AdapterMemory) Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error {
76+
defer c.handleLruKey(ctx, key)
8177
expireTime := c.getInternalExpire(duration)
82-
c.data.Set(key, adapterMemoryItem{
78+
c.data.Set(key, memoryDataItem{
8379
v: value,
8480
e: expireTime,
8581
})
@@ -108,6 +104,11 @@ func (c *AdapterMemory) SetMap(ctx context.Context, data map[interface{}]interfa
108104
e: expireTime,
109105
})
110106
}
107+
if c.lru != nil {
108+
for key := range data {
109+
c.handleLruKey(ctx, key)
110+
}
111+
}
111112
return nil
112113
}
113114

@@ -118,6 +119,7 @@ func (c *AdapterMemory) SetMap(ctx context.Context, data map[interface{}]interfa
118119
// It does not expire if `duration` == 0.
119120
// It deletes the `key` if `duration` < 0 or given `value` is nil.
120121
func (c *AdapterMemory) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (bool, error) {
122+
defer c.handleLruKey(ctx, key)
121123
isContained, err := c.Contains(ctx, key)
122124
if err != nil {
123125
return false, err
@@ -140,6 +142,7 @@ func (c *AdapterMemory) SetIfNotExist(ctx context.Context, key interface{}, valu
140142
// It does not expire if `duration` == 0.
141143
// It deletes the `key` if `duration` < 0 or given `value` is nil.
142144
func (c *AdapterMemory) SetIfNotExistFunc(ctx context.Context, key interface{}, f Func, duration time.Duration) (bool, error) {
145+
defer c.handleLruKey(ctx, key)
143146
isContained, err := c.Contains(ctx, key)
144147
if err != nil {
145148
return false, err
@@ -166,6 +169,7 @@ func (c *AdapterMemory) SetIfNotExistFunc(ctx context.Context, key interface{},
166169
// Note that it differs from function `SetIfNotExistFunc` is that the function `f` is executed within
167170
// writing mutex lock for concurrent safety purpose.
168171
func (c *AdapterMemory) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f Func, duration time.Duration) (bool, error) {
172+
defer c.handleLruKey(ctx, key)
169173
isContained, err := c.Contains(ctx, key)
170174
if err != nil {
171175
return false, err
@@ -185,10 +189,7 @@ func (c *AdapterMemory) SetIfNotExistFuncLock(ctx context.Context, key interface
185189
func (c *AdapterMemory) Get(ctx context.Context, key interface{}) (*gvar.Var, error) {
186190
item, ok := c.data.Get(key)
187191
if ok && !item.IsExpired() {
188-
// Adding to LRU history if LRU feature is enabled.
189-
if c.cap > 0 {
190-
c.lruGetList.PushBack(key)
191-
}
192+
c.handleLruKey(ctx, key)
192193
return gvar.New(item.v), nil
193194
}
194195
return nil, nil
@@ -202,6 +203,7 @@ func (c *AdapterMemory) Get(ctx context.Context, key interface{}) (*gvar.Var, er
202203
// It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing
203204
// if `value` is a function and the function result is nil.
204205
func (c *AdapterMemory) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (*gvar.Var, error) {
206+
defer c.handleLruKey(ctx, key)
205207
v, err := c.Get(ctx, key)
206208
if err != nil {
207209
return nil, err
@@ -220,6 +222,7 @@ func (c *AdapterMemory) GetOrSet(ctx context.Context, key interface{}, value int
220222
// It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing
221223
// if `value` is a function and the function result is nil.
222224
func (c *AdapterMemory) GetOrSetFunc(ctx context.Context, key interface{}, f Func, duration time.Duration) (*gvar.Var, error) {
225+
defer c.handleLruKey(ctx, key)
223226
v, err := c.Get(ctx, key)
224227
if err != nil {
225228
return nil, err
@@ -248,6 +251,7 @@ func (c *AdapterMemory) GetOrSetFunc(ctx context.Context, key interface{}, f Fun
248251
// Note that it differs from function `GetOrSetFunc` is that the function `f` is executed within
249252
// writing mutex lock for concurrent safety purpose.
250253
func (c *AdapterMemory) GetOrSetFuncLock(ctx context.Context, key interface{}, f Func, duration time.Duration) (*gvar.Var, error) {
254+
defer c.handleLruKey(ctx, key)
251255
v, err := c.Get(ctx, key)
252256
if err != nil {
253257
return nil, err
@@ -274,6 +278,7 @@ func (c *AdapterMemory) Contains(ctx context.Context, key interface{}) (bool, er
274278
// It returns -1 if the `key` does not exist in the cache.
275279
func (c *AdapterMemory) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) {
276280
if item, ok := c.data.Get(key); ok {
281+
c.handleLruKey(ctx, key)
277282
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil
278283
}
279284
return -1, nil
@@ -282,6 +287,15 @@ func (c *AdapterMemory) GetExpire(ctx context.Context, key interface{}) (time.Du
282287
// Remove deletes one or more keys from cache, and returns its value.
283288
// If multiple keys are given, it returns the value of the last deleted item.
284289
func (c *AdapterMemory) Remove(ctx context.Context, keys ...interface{}) (*gvar.Var, error) {
290+
defer c.lru.Remove(keys...)
291+
value, err := c.doRemove(ctx, keys...)
292+
if err != nil {
293+
return nil, err
294+
}
295+
return gvar.New(value), nil
296+
}
297+
298+
func (c *AdapterMemory) doRemove(_ context.Context, keys ...interface{}) (*gvar.Var, error) {
285299
var removedKeys []interface{}
286300
removedKeys, value, err := c.data.Remove(keys...)
287301
if err != nil {
@@ -303,6 +317,9 @@ func (c *AdapterMemory) Remove(ctx context.Context, keys ...interface{}) (*gvar.
303317
// It does nothing if `key` does not exist in the cache.
304318
func (c *AdapterMemory) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) {
305319
v, exist, err := c.data.Update(key, value)
320+
if exist {
321+
c.handleLruKey(ctx, key)
322+
}
306323
return gvar.New(v), exist, err
307324
}
308325

@@ -321,6 +338,7 @@ func (c *AdapterMemory) UpdateExpire(ctx context.Context, key interface{}, durat
321338
k: key,
322339
e: newExpireTime,
323340
})
341+
c.handleLruKey(ctx, key)
324342
}
325343
return
326344
}
@@ -348,14 +366,13 @@ func (c *AdapterMemory) Values(ctx context.Context) ([]interface{}, error) {
348366
// Clear clears all data of the cache.
349367
// Note that this function is sensitive and should be carefully used.
350368
func (c *AdapterMemory) Clear(ctx context.Context) error {
351-
return c.data.Clear()
369+
c.data.Clear()
370+
c.lru.Clear()
371+
return nil
352372
}
353373

354374
// Close closes the cache.
355375
func (c *AdapterMemory) Close(ctx context.Context) error {
356-
if c.cap > 0 {
357-
c.lru.Close()
358-
}
359376
c.closed.Set(true)
360377
return nil
361378
}
@@ -390,9 +407,9 @@ func (c *AdapterMemory) makeExpireKey(expire int64) int64 {
390407
}
391408

392409
// syncEventAndClearExpired does the asynchronous task loop:
393-
// 1. Asynchronously process the data in the event list,
394-
// and synchronize the results to the `expireTimes` and `expireSets` properties.
395-
// 2. Clean up the expired key-value pair data.
410+
// 1. Asynchronously process the data in the event list,
411+
// and synchronize the results to the `expireTimes` and `expireSets` properties.
412+
// 2. Clean up the expired key-value pair data.
396413
func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) {
397414
if c.closed.Val() {
398415
gtimer.Exit()
@@ -403,9 +420,9 @@ func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) {
403420
oldExpireTime int64
404421
newExpireTime int64
405422
)
406-
// ========================
407-
// Data Synchronization.
408-
// ========================
423+
// ================================
424+
// Data expiration synchronization.
425+
// ================================
409426
for {
410427
v := c.eventList.PopFront()
411428
if v == nil {
@@ -425,37 +442,24 @@ func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) {
425442
// Updating the expired time for `event.k`.
426443
c.expireTimes.Set(event.k, newExpireTime)
427444
}
428-
// Adding the key the LRU history by writing operations.
429-
if c.cap > 0 {
430-
c.lru.Push(event.k)
431-
}
432-
}
433-
// Processing expired keys from LRU.
434-
if c.cap > 0 {
435-
if c.lruGetList.Len() > 0 {
436-
for {
437-
if v := c.lruGetList.PopFront(); v != nil {
438-
c.lru.Push(v)
439-
} else {
440-
break
441-
}
442-
}
443-
}
444-
c.lru.SyncAndClear(ctx)
445445
}
446-
// ========================
447-
// Data Cleaning up.
448-
// ========================
446+
// =================================
447+
// Data expiration auto cleaning up.
448+
// =================================
449449
var (
450-
expireSet *gset.Set
451-
ek = c.makeExpireKey(gtime.TimestampMilli())
452-
eks = []int64{ek - 1000, ek - 2000, ek - 3000, ek - 4000, ek - 5000}
450+
expireSet *gset.Set
451+
expireTime int64
452+
currentEk = c.makeExpireKey(gtime.TimestampMilli())
453453
)
454-
for _, expireTime := range eks {
454+
// auto removing expiring key set for latest seconds.
455+
for i := int64(1); i <= 5; i++ {
456+
expireTime = currentEk - i*1000
455457
if expireSet = c.expireSets.Get(expireTime); expireSet != nil {
456458
// Iterating the set to delete all keys in it.
457459
expireSet.Iterator(func(key interface{}) bool {
458-
c.clearByKey(key)
460+
c.deleteExpiredKey(key)
461+
// remove auto expired key for lru.
462+
c.lru.Remove(key)
459463
return true
460464
})
461465
// Deleting the set after all of its keys are deleted.
@@ -464,17 +468,22 @@ func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) {
464468
}
465469
}
466470

471+
func (c *AdapterMemory) handleLruKey(ctx context.Context, keys ...interface{}) {
472+
if c.lru == nil {
473+
return
474+
}
475+
if evictedKeys := c.lru.SaveAndEvict(keys...); len(evictedKeys) > 0 {
476+
_, _ = c.doRemove(ctx, evictedKeys...)
477+
return
478+
}
479+
return
480+
}
481+
467482
// clearByKey deletes the key-value pair with given `key`.
468483
// The parameter `force` specifies whether doing this deleting forcibly.
469-
func (c *AdapterMemory) clearByKey(key interface{}, force ...bool) {
484+
func (c *AdapterMemory) deleteExpiredKey(key interface{}) {
470485
// Doubly check before really deleting it from cache.
471-
c.data.DeleteWithDoubleCheck(key, force...)
472-
486+
c.data.Delete(key)
473487
// Deleting its expiration time from `expireTimes`.
474488
c.expireTimes.Delete(key)
475-
476-
// Deleting it from LRU.
477-
if c.cap > 0 {
478-
c.lru.Remove(key)
479-
}
480489
}

0 commit comments

Comments
 (0)