diff --git a/os/gcache/gcache.go b/os/gcache/gcache.go index 645508b786d..8aaf6652f20 100644 --- a/os/gcache/gcache.go +++ b/os/gcache/gcache.go @@ -17,7 +17,7 @@ import ( ) // Func is the cache function that calculates and returns the value. -type Func func(ctx context.Context) (value interface{}, err error) +type Func = func(ctx context.Context) (value interface{}, err error) const ( DurationNoExpire = time.Duration(0) // Expire duration that never expires. diff --git a/os/gcache/gcache_adapter_memory.go b/os/gcache/gcache_adapter_memory.go index 6499be829cb..4653c2df0ba 100644 --- a/os/gcache/gcache_adapter_memory.go +++ b/os/gcache/gcache_adapter_memory.go @@ -21,24 +21,12 @@ import ( // AdapterMemory is an adapter implements using memory. type AdapterMemory struct { - // cap limits the size of the cache pool. - // If the size of the cache exceeds the cap, - // the cache expiration process performs according to the LRU algorithm. - // It is 0 in default which means no limits. - cap int - data *adapterMemoryData // data is the underlying cache data which is stored in a hash table. - expireTimes *adapterMemoryExpireTimes // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting. - expireSets *adapterMemoryExpireSets // expireSets is the expiring timestamp to its key set mapping, which is used for quick indexing and deleting. - lru *adapterMemoryLru // lru is the LRU manager, which is enabled when attribute cap > 0. - lruGetList *glist.List // lruGetList is the LRU history according to Get function. - eventList *glist.List // eventList is the asynchronous event list for internal data synchronization. - closed *gtype.Bool // closed controls the cache closed or not. -} - -// Internal cache item. -type adapterMemoryItem struct { - v interface{} // Value. - e int64 // Expire timestamp in milliseconds. + data *memoryData // data is the underlying cache data which is stored in a hash table. + expireTimes *memoryExpireTimes // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting. + expireSets *memoryExpireSets // expireSets is the expiring timestamp to its key set mapping, which is used for quick indexing and deleting. + lru *memoryLru // lru is the LRU manager, which is enabled when attribute cap > 0. + eventList *glist.List // eventList is the asynchronous event list for internal data synchronization. + closed *gtype.Bool // closed controls the cache closed or not. } // Internal event item. @@ -53,21 +41,28 @@ const ( defaultMaxExpire = 9223372036854 ) -// NewAdapterMemory creates and returns a new memory cache object. -func NewAdapterMemory(lruCap ...int) Adapter { +// NewAdapterMemory creates and returns a new adapter_memory cache object. +func NewAdapterMemory() *AdapterMemory { + return doNewAdapterMemory() +} + +// NewAdapterMemoryLru creates and returns a new adapter_memory cache object with LRU. +func NewAdapterMemoryLru(cap int) *AdapterMemory { + c := doNewAdapterMemory() + c.lru = newMemoryLru(cap) + return c +} + +// doNewAdapterMemory creates and returns a new adapter_memory cache object. +func doNewAdapterMemory() *AdapterMemory { c := &AdapterMemory{ - data: newAdapterMemoryData(), - lruGetList: glist.New(true), - expireTimes: newAdapterMemoryExpireTimes(), - expireSets: newAdapterMemoryExpireSets(), + data: newMemoryData(), + expireTimes: newMemoryExpireTimes(), + expireSets: newMemoryExpireSets(), eventList: glist.New(true), closed: gtype.NewBool(), } - if len(lruCap) > 0 { - c.cap = lruCap[0] - c.lru = newMemCacheLru(c) - } - // Here may be a "timer leak" if adapter is manually changed from memory adapter. + // Here may be a "timer leak" if adapter is manually changed from adapter_memory adapter. // Do not worry about this, as adapter is less changed, and it does nothing if it's not used. gtimer.AddSingleton(context.Background(), time.Second, c.syncEventAndClearExpired) return c @@ -78,8 +73,9 @@ func NewAdapterMemory(lruCap ...int) Adapter { // It does not expire if `duration` == 0. // It deletes the keys of `data` if `duration` < 0 or given `value` is nil. func (c *AdapterMemory) Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error { + defer c.handleLruKey(ctx, key) expireTime := c.getInternalExpire(duration) - c.data.Set(key, adapterMemoryItem{ + c.data.Set(key, memoryDataItem{ v: value, e: expireTime, }) @@ -108,6 +104,11 @@ func (c *AdapterMemory) SetMap(ctx context.Context, data map[interface{}]interfa e: expireTime, }) } + if c.lru != nil { + for key := range data { + c.handleLruKey(ctx, key) + } + } return nil } @@ -118,6 +119,7 @@ func (c *AdapterMemory) SetMap(ctx context.Context, data map[interface{}]interfa // It does not expire if `duration` == 0. // It deletes the `key` if `duration` < 0 or given `value` is nil. func (c *AdapterMemory) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (bool, error) { + defer c.handleLruKey(ctx, key) isContained, err := c.Contains(ctx, key) if err != nil { return false, err @@ -140,6 +142,7 @@ func (c *AdapterMemory) SetIfNotExist(ctx context.Context, key interface{}, valu // It does not expire if `duration` == 0. // It deletes the `key` if `duration` < 0 or given `value` is nil. func (c *AdapterMemory) SetIfNotExistFunc(ctx context.Context, key interface{}, f Func, duration time.Duration) (bool, error) { + defer c.handleLruKey(ctx, key) isContained, err := c.Contains(ctx, key) if err != nil { return false, err @@ -166,6 +169,7 @@ func (c *AdapterMemory) SetIfNotExistFunc(ctx context.Context, key interface{}, // Note that it differs from function `SetIfNotExistFunc` is that the function `f` is executed within // writing mutex lock for concurrent safety purpose. func (c *AdapterMemory) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f Func, duration time.Duration) (bool, error) { + defer c.handleLruKey(ctx, key) isContained, err := c.Contains(ctx, key) if err != nil { return false, err @@ -185,10 +189,7 @@ func (c *AdapterMemory) SetIfNotExistFuncLock(ctx context.Context, key interface func (c *AdapterMemory) Get(ctx context.Context, key interface{}) (*gvar.Var, error) { item, ok := c.data.Get(key) if ok && !item.IsExpired() { - // Adding to LRU history if LRU feature is enabled. - if c.cap > 0 { - c.lruGetList.PushBack(key) - } + c.handleLruKey(ctx, key) return gvar.New(item.v), nil } return nil, nil @@ -202,6 +203,7 @@ func (c *AdapterMemory) Get(ctx context.Context, key interface{}) (*gvar.Var, er // It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing // if `value` is a function and the function result is nil. func (c *AdapterMemory) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (*gvar.Var, error) { + defer c.handleLruKey(ctx, key) v, err := c.Get(ctx, key) if err != nil { return nil, err @@ -220,6 +222,7 @@ func (c *AdapterMemory) GetOrSet(ctx context.Context, key interface{}, value int // It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing // if `value` is a function and the function result is nil. func (c *AdapterMemory) GetOrSetFunc(ctx context.Context, key interface{}, f Func, duration time.Duration) (*gvar.Var, error) { + defer c.handleLruKey(ctx, key) v, err := c.Get(ctx, key) if err != nil { return nil, err @@ -248,6 +251,7 @@ func (c *AdapterMemory) GetOrSetFunc(ctx context.Context, key interface{}, f Fun // Note that it differs from function `GetOrSetFunc` is that the function `f` is executed within // writing mutex lock for concurrent safety purpose. func (c *AdapterMemory) GetOrSetFuncLock(ctx context.Context, key interface{}, f Func, duration time.Duration) (*gvar.Var, error) { + defer c.handleLruKey(ctx, key) v, err := c.Get(ctx, key) if err != nil { return nil, err @@ -274,6 +278,7 @@ func (c *AdapterMemory) Contains(ctx context.Context, key interface{}) (bool, er // It returns -1 if the `key` does not exist in the cache. func (c *AdapterMemory) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { if item, ok := c.data.Get(key); ok { + c.handleLruKey(ctx, key) return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil } return -1, nil @@ -282,6 +287,15 @@ func (c *AdapterMemory) GetExpire(ctx context.Context, key interface{}) (time.Du // Remove deletes one or more keys from cache, and returns its value. // If multiple keys are given, it returns the value of the last deleted item. func (c *AdapterMemory) Remove(ctx context.Context, keys ...interface{}) (*gvar.Var, error) { + defer c.lru.Remove(keys...) + value, err := c.doRemove(ctx, keys...) + if err != nil { + return nil, err + } + return gvar.New(value), nil +} + +func (c *AdapterMemory) doRemove(_ context.Context, keys ...interface{}) (*gvar.Var, error) { var removedKeys []interface{} removedKeys, value, err := c.data.Remove(keys...) if err != nil { @@ -303,6 +317,9 @@ func (c *AdapterMemory) Remove(ctx context.Context, keys ...interface{}) (*gvar. // It does nothing if `key` does not exist in the cache. func (c *AdapterMemory) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) { v, exist, err := c.data.Update(key, value) + if exist { + c.handleLruKey(ctx, key) + } return gvar.New(v), exist, err } @@ -321,6 +338,7 @@ func (c *AdapterMemory) UpdateExpire(ctx context.Context, key interface{}, durat k: key, e: newExpireTime, }) + c.handleLruKey(ctx, key) } return } @@ -348,14 +366,13 @@ func (c *AdapterMemory) Values(ctx context.Context) ([]interface{}, error) { // Clear clears all data of the cache. // Note that this function is sensitive and should be carefully used. func (c *AdapterMemory) Clear(ctx context.Context) error { - return c.data.Clear() + c.data.Clear() + c.lru.Clear() + return nil } // Close closes the cache. func (c *AdapterMemory) Close(ctx context.Context) error { - if c.cap > 0 { - c.lru.Close() - } c.closed.Set(true) return nil } @@ -390,9 +407,9 @@ func (c *AdapterMemory) makeExpireKey(expire int64) int64 { } // syncEventAndClearExpired does the asynchronous task loop: -// 1. Asynchronously process the data in the event list, -// and synchronize the results to the `expireTimes` and `expireSets` properties. -// 2. Clean up the expired key-value pair data. +// 1. Asynchronously process the data in the event list, +// and synchronize the results to the `expireTimes` and `expireSets` properties. +// 2. Clean up the expired key-value pair data. func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) { if c.closed.Val() { gtimer.Exit() @@ -403,9 +420,9 @@ func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) { oldExpireTime int64 newExpireTime int64 ) - // ======================== - // Data Synchronization. - // ======================== + // ================================ + // Data expiration synchronization. + // ================================ for { v := c.eventList.PopFront() if v == nil { @@ -425,37 +442,24 @@ func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) { // Updating the expired time for `event.k`. c.expireTimes.Set(event.k, newExpireTime) } - // Adding the key the LRU history by writing operations. - if c.cap > 0 { - c.lru.Push(event.k) - } - } - // Processing expired keys from LRU. - if c.cap > 0 { - if c.lruGetList.Len() > 0 { - for { - if v := c.lruGetList.PopFront(); v != nil { - c.lru.Push(v) - } else { - break - } - } - } - c.lru.SyncAndClear(ctx) } - // ======================== - // Data Cleaning up. - // ======================== + // ================================= + // Data expiration auto cleaning up. + // ================================= var ( - expireSet *gset.Set - ek = c.makeExpireKey(gtime.TimestampMilli()) - eks = []int64{ek - 1000, ek - 2000, ek - 3000, ek - 4000, ek - 5000} + expireSet *gset.Set + expireTime int64 + currentEk = c.makeExpireKey(gtime.TimestampMilli()) ) - for _, expireTime := range eks { + // auto removing expiring key set for latest seconds. + for i := int64(1); i <= 5; i++ { + expireTime = currentEk - i*1000 if expireSet = c.expireSets.Get(expireTime); expireSet != nil { // Iterating the set to delete all keys in it. expireSet.Iterator(func(key interface{}) bool { - c.clearByKey(key) + c.deleteExpiredKey(key) + // remove auto expired key for lru. + c.lru.Remove(key) return true }) // Deleting the set after all of its keys are deleted. @@ -464,17 +468,22 @@ func (c *AdapterMemory) syncEventAndClearExpired(ctx context.Context) { } } +func (c *AdapterMemory) handleLruKey(ctx context.Context, keys ...interface{}) { + if c.lru == nil { + return + } + if evictedKeys := c.lru.SaveAndEvict(keys...); len(evictedKeys) > 0 { + _, _ = c.doRemove(ctx, evictedKeys...) + return + } + return +} + // clearByKey deletes the key-value pair with given `key`. // The parameter `force` specifies whether doing this deleting forcibly. -func (c *AdapterMemory) clearByKey(key interface{}, force ...bool) { +func (c *AdapterMemory) deleteExpiredKey(key interface{}) { // Doubly check before really deleting it from cache. - c.data.DeleteWithDoubleCheck(key, force...) - + c.data.Delete(key) // Deleting its expiration time from `expireTimes`. c.expireTimes.Delete(key) - - // Deleting it from LRU. - if c.cap > 0 { - c.lru.Remove(key) - } } diff --git a/os/gcache/gcache_adapter_memory_data.go b/os/gcache/gcache_adapter_memory_data.go index 941339d0f05..ddd0c5fae86 100644 --- a/os/gcache/gcache_adapter_memory_data.go +++ b/os/gcache/gcache_adapter_memory_data.go @@ -14,14 +14,20 @@ import ( "github.com/gogf/gf/v2/os/gtime" ) -type adapterMemoryData struct { - mu sync.RWMutex // dataMu ensures the concurrent safety of underlying data map. - data map[interface{}]adapterMemoryItem // data is the underlying cache data which is stored in a hash table. +type memoryData struct { + mu sync.RWMutex // dataMu ensures the concurrent safety of underlying data map. + data map[interface{}]memoryDataItem // data is the underlying cache data which is stored in a hash table. } -func newAdapterMemoryData() *adapterMemoryData { - return &adapterMemoryData{ - data: make(map[interface{}]adapterMemoryItem), +// memoryDataItem holds the internal cache item data. +type memoryDataItem struct { + v interface{} // Value. + e int64 // Expire timestamp in milliseconds. +} + +func newMemoryData() *memoryData { + return &memoryData{ + data: make(map[interface{}]memoryDataItem), } } @@ -30,11 +36,11 @@ func newAdapterMemoryData() *adapterMemoryData { // // It deletes the `key` if given `value` is nil. // It does nothing if `key` does not exist in the cache. -func (d *adapterMemoryData) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) { +func (d *memoryData) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) { d.mu.Lock() defer d.mu.Unlock() if item, ok := d.data[key]; ok { - d.data[key] = adapterMemoryItem{ + d.data[key] = memoryDataItem{ v: value, e: item.e, } @@ -47,11 +53,11 @@ func (d *adapterMemoryData) Update(key interface{}, value interface{}) (oldValue // // It returns -1 and does nothing if the `key` does not exist in the cache. // It deletes the `key` if `duration` < 0. -func (d *adapterMemoryData) UpdateExpire(key interface{}, expireTime int64) (oldDuration time.Duration, err error) { +func (d *memoryData) UpdateExpire(key interface{}, expireTime int64) (oldDuration time.Duration, err error) { d.mu.Lock() defer d.mu.Unlock() if item, ok := d.data[key]; ok { - d.data[key] = adapterMemoryItem{ + d.data[key] = memoryDataItem{ v: item.v, e: expireTime, } @@ -62,7 +68,7 @@ func (d *adapterMemoryData) UpdateExpire(key interface{}, expireTime int64) (old // Remove deletes the one or more keys from cache, and returns its value. // If multiple keys are given, it returns the value of the deleted last item. -func (d *adapterMemoryData) Remove(keys ...interface{}) (removedKeys []interface{}, value interface{}, err error) { +func (d *memoryData) Remove(keys ...interface{}) (removedKeys []interface{}, value interface{}, err error) { d.mu.Lock() defer d.mu.Unlock() removedKeys = make([]interface{}, 0) @@ -78,77 +84,82 @@ func (d *adapterMemoryData) Remove(keys ...interface{}) (removedKeys []interface } // Data returns a copy of all key-value pairs in the cache as map type. -func (d *adapterMemoryData) Data() (map[interface{}]interface{}, error) { +func (d *memoryData) Data() (map[interface{}]interface{}, error) { d.mu.RLock() - m := make(map[interface{}]interface{}, len(d.data)) + defer d.mu.RUnlock() + var ( + data = make(map[interface{}]interface{}, len(d.data)) + nowMilli = gtime.TimestampMilli() + ) for k, v := range d.data { - if !v.IsExpired() { - m[k] = v.v + if v.e > nowMilli { + data[k] = v.v } } - d.mu.RUnlock() - return m, nil + return data, nil } // Keys returns all keys in the cache as slice. -func (d *adapterMemoryData) Keys() ([]interface{}, error) { +func (d *memoryData) Keys() ([]interface{}, error) { d.mu.RLock() + defer d.mu.RUnlock() var ( - index = 0 - keys = make([]interface{}, len(d.data)) + keys = make([]interface{}, 0, len(d.data)) + nowMilli = gtime.TimestampMilli() ) for k, v := range d.data { - if !v.IsExpired() { - keys[index] = k - index++ + if v.e > nowMilli { + keys = append(keys, k) } } - d.mu.RUnlock() return keys, nil } // Values returns all values in the cache as slice. -func (d *adapterMemoryData) Values() ([]interface{}, error) { +func (d *memoryData) Values() ([]interface{}, error) { d.mu.RLock() + defer d.mu.RUnlock() var ( - index = 0 - values = make([]interface{}, len(d.data)) + values = make([]interface{}, 0, len(d.data)) + nowMilli = gtime.TimestampMilli() ) for _, v := range d.data { - if !v.IsExpired() { - values[index] = v.v - index++ + if v.e > nowMilli { + values = append(values, v.v) } } - d.mu.RUnlock() return values, nil } -// Size returns the size of the cache. -func (d *adapterMemoryData) Size() (size int, err error) { +// Size returns the size of the cache that not expired. +func (d *memoryData) Size() (size int, err error) { d.mu.RLock() - size = len(d.data) - d.mu.RUnlock() + defer d.mu.RUnlock() + var nowMilli = gtime.TimestampMilli() + for _, v := range d.data { + if v.e > nowMilli { + size++ + } + } return size, nil } // Clear clears all data of the cache. // Note that this function is sensitive and should be carefully used. -func (d *adapterMemoryData) Clear() error { +func (d *memoryData) Clear() { d.mu.Lock() defer d.mu.Unlock() - d.data = make(map[interface{}]adapterMemoryItem) - return nil + d.data = make(map[interface{}]memoryDataItem) } -func (d *adapterMemoryData) Get(key interface{}) (item adapterMemoryItem, ok bool) { +func (d *memoryData) Get(key interface{}) (item memoryDataItem, ok bool) { d.mu.RLock() item, ok = d.data[key] d.mu.RUnlock() return } -func (d *adapterMemoryData) Set(key interface{}, value adapterMemoryItem) { +func (d *memoryData) Set(key interface{}, value memoryDataItem) { d.mu.Lock() d.data[key] = value d.mu.Unlock() @@ -158,10 +169,10 @@ func (d *adapterMemoryData) Set(key interface{}, value adapterMemoryItem) { // // It does not expire if `duration` == 0. // It deletes the keys of `data` if `duration` < 0 or given `value` is nil. -func (d *adapterMemoryData) SetMap(data map[interface{}]interface{}, expireTime int64) error { +func (d *memoryData) SetMap(data map[interface{}]interface{}, expireTime int64) error { d.mu.Lock() for k, v := range data { - d.data[k] = adapterMemoryItem{ + d.data[k] = memoryDataItem{ v: v, e: expireTime, } @@ -170,7 +181,7 @@ func (d *adapterMemoryData) SetMap(data map[interface{}]interface{}, expireTime return nil } -func (d *adapterMemoryData) SetWithLock(ctx context.Context, key interface{}, value interface{}, expireTimestamp int64) (interface{}, error) { +func (d *memoryData) SetWithLock(ctx context.Context, key interface{}, value interface{}, expireTimestamp int64) (interface{}, error) { d.mu.Lock() defer d.mu.Unlock() var ( @@ -192,15 +203,12 @@ func (d *adapterMemoryData) SetWithLock(ctx context.Context, key interface{}, va return nil, nil } } - d.data[key] = adapterMemoryItem{v: value, e: expireTimestamp} + d.data[key] = memoryDataItem{v: value, e: expireTimestamp} return value, nil } -func (d *adapterMemoryData) DeleteWithDoubleCheck(key interface{}, force ...bool) { +func (d *memoryData) Delete(key interface{}) { d.mu.Lock() - // Doubly check before really deleting it from cache. - if item, ok := d.data[key]; (ok && item.IsExpired()) || (len(force) > 0 && force[0]) { - delete(d.data, key) - } - d.mu.Unlock() + defer d.mu.Unlock() + delete(d.data, key) } diff --git a/os/gcache/gcache_adapter_memory_expire_sets.go b/os/gcache/gcache_adapter_memory_expire_sets.go index 560138cc1fc..58ef3424e64 100644 --- a/os/gcache/gcache_adapter_memory_expire_sets.go +++ b/os/gcache/gcache_adapter_memory_expire_sets.go @@ -12,27 +12,27 @@ import ( "github.com/gogf/gf/v2/container/gset" ) -type adapterMemoryExpireSets struct { +type memoryExpireSets struct { // expireSetMu ensures the concurrent safety of expireSets map. mu sync.RWMutex // expireSets is the expiring timestamp in seconds to its key set mapping, which is used for quick indexing and deleting. expireSets map[int64]*gset.Set } -func newAdapterMemoryExpireSets() *adapterMemoryExpireSets { - return &adapterMemoryExpireSets{ +func newMemoryExpireSets() *memoryExpireSets { + return &memoryExpireSets{ expireSets: make(map[int64]*gset.Set), } } -func (d *adapterMemoryExpireSets) Get(key int64) (result *gset.Set) { +func (d *memoryExpireSets) Get(key int64) (result *gset.Set) { d.mu.RLock() result = d.expireSets[key] d.mu.RUnlock() return } -func (d *adapterMemoryExpireSets) GetOrNew(key int64) (result *gset.Set) { +func (d *memoryExpireSets) GetOrNew(key int64) (result *gset.Set) { if result = d.Get(key); result != nil { return } @@ -47,7 +47,7 @@ func (d *adapterMemoryExpireSets) GetOrNew(key int64) (result *gset.Set) { return } -func (d *adapterMemoryExpireSets) Delete(key int64) { +func (d *memoryExpireSets) Delete(key int64) { d.mu.Lock() delete(d.expireSets, key) d.mu.Unlock() diff --git a/os/gcache/gcache_adapter_memory_expire_times.go b/os/gcache/gcache_adapter_memory_expire_times.go index af3d4b41933..182b751142d 100644 --- a/os/gcache/gcache_adapter_memory_expire_times.go +++ b/os/gcache/gcache_adapter_memory_expire_times.go @@ -10,31 +10,31 @@ import ( "sync" ) -type adapterMemoryExpireTimes struct { +type memoryExpireTimes struct { mu sync.RWMutex // expireTimeMu ensures the concurrent safety of expireTimes map. expireTimes map[interface{}]int64 // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting. } -func newAdapterMemoryExpireTimes() *adapterMemoryExpireTimes { - return &adapterMemoryExpireTimes{ +func newMemoryExpireTimes() *memoryExpireTimes { + return &memoryExpireTimes{ expireTimes: make(map[interface{}]int64), } } -func (d *adapterMemoryExpireTimes) Get(key interface{}) (value int64) { +func (d *memoryExpireTimes) Get(key interface{}) (value int64) { d.mu.RLock() value = d.expireTimes[key] d.mu.RUnlock() return } -func (d *adapterMemoryExpireTimes) Set(key interface{}, value int64) { +func (d *memoryExpireTimes) Set(key interface{}, value int64) { d.mu.Lock() d.expireTimes[key] = value d.mu.Unlock() } -func (d *adapterMemoryExpireTimes) Delete(key interface{}) { +func (d *memoryExpireTimes) Delete(key interface{}) { d.mu.Lock() delete(d.expireTimes, key) d.mu.Unlock() diff --git a/os/gcache/gcache_adapter_memory_item.go b/os/gcache/gcache_adapter_memory_item.go index 5a7862cae7e..ede93f32cf1 100644 --- a/os/gcache/gcache_adapter_memory_item.go +++ b/os/gcache/gcache_adapter_memory_item.go @@ -11,9 +11,8 @@ import ( ) // IsExpired checks whether `item` is expired. -func (item *adapterMemoryItem) IsExpired() bool { +func (item *memoryDataItem) IsExpired() bool { // Note that it should use greater than or equal judgement here // imagining that the cache time is only 1 millisecond. - return item.e < gtime.TimestampMilli() } diff --git a/os/gcache/gcache_adapter_memory_lru.go b/os/gcache/gcache_adapter_memory_lru.go index 6583ec96866..318bb2171f3 100644 --- a/os/gcache/gcache_adapter_memory_lru.go +++ b/os/gcache/gcache_adapter_memory_lru.go @@ -7,94 +7,93 @@ package gcache import ( - "context" - "github.com/gogf/gf/v2/container/glist" "github.com/gogf/gf/v2/container/gmap" - "github.com/gogf/gf/v2/container/gtype" - "github.com/gogf/gf/v2/os/gtimer" + "sync" ) -// LRU cache object. +// memoryLru holds LRU info. // It uses list.List from stdlib for its underlying doubly linked list. -type adapterMemoryLru struct { - cache *AdapterMemory // Parent cache object. - data *gmap.Map // Key mapping to the item of the list. - list *glist.List // Key list. - rawList *glist.List // History for key adding. - closed *gtype.Bool // Closed or not. +type memoryLru struct { + mu sync.RWMutex // Mutex to guarantee concurrent safety. + cap int // LRU cap. + data *gmap.Map // Key mapping to the item of the list. + list *glist.List // Key list. } -// newMemCacheLru creates and returns a new LRU object. -func newMemCacheLru(cache *AdapterMemory) *adapterMemoryLru { - lru := &adapterMemoryLru{ - cache: cache, - data: gmap.New(true), - list: glist.New(true), - rawList: glist.New(true), - closed: gtype.NewBool(), +// newMemoryLru creates and returns a new LRU manager. +func newMemoryLru(cap int) *memoryLru { + lru := &memoryLru{ + cap: cap, + data: gmap.New(false), + list: glist.New(false), } return lru } -// Close closes the LRU object. -func (lru *adapterMemoryLru) Close() { - lru.closed.Set(true) -} - // Remove deletes the `key` FROM `lru`. -func (lru *adapterMemoryLru) Remove(key interface{}) { - if v := lru.data.Get(key); v != nil { - lru.data.Remove(key) - lru.list.Remove(v.(*glist.Element)) +func (l *memoryLru) Remove(keys ...interface{}) { + if l == nil { + return + } + l.mu.Lock() + defer l.mu.Unlock() + for _, key := range keys { + if v := l.data.Remove(key); v != nil { + l.list.Remove(v.(*glist.Element)) + } } } -// Size returns the size of `lru`. -func (lru *adapterMemoryLru) Size() int { - return lru.data.Size() -} - -// Push pushes `key` to the tail of `lru`. -func (lru *adapterMemoryLru) Push(key interface{}) { - lru.rawList.PushBack(key) +// SaveAndEvict saves the keys into LRU, evicts and returns the spare keys. +func (l *memoryLru) SaveAndEvict(keys ...interface{}) (evictedKeys []interface{}) { + if l == nil { + return + } + l.mu.Lock() + defer l.mu.Unlock() + evictedKeys = make([]interface{}, 0) + for _, key := range keys { + if evictedKey := l.doSaveAndEvict(key); evictedKey != nil { + evictedKeys = append(evictedKeys, evictedKey) + } + } + return } -// Pop deletes and returns the key from tail of `lru`. -func (lru *adapterMemoryLru) Pop() interface{} { - if v := lru.list.PopBack(); v != nil { - lru.data.Remove(v) - return v +func (l *memoryLru) doSaveAndEvict(key interface{}) (evictedKey interface{}) { + var element *glist.Element + if v := l.data.Get(key); v != nil { + element = v.(*glist.Element) + if element.Prev() == nil { + // It this element is already on top of list, + // it ignores the element moving. + return + } + l.list.Remove(element) } - return nil -} -// SyncAndClear synchronizes the keys from `rawList` to `list` and `data` -// using Least Recently Used algorithm. -func (lru *adapterMemoryLru) SyncAndClear(ctx context.Context) { - if lru.closed.Val() { - gtimer.Exit() + // pushes the active key to top of list. + element = l.list.PushFront(key) + l.data.Set(key, element) + // evict the spare key from list. + if l.data.Size() <= l.cap { return } - // Data synchronization. - var alreadyExistItem interface{} - for { - if rawListItem := lru.rawList.PopFront(); rawListItem != nil { - // Deleting the key from list. - if alreadyExistItem = lru.data.Get(rawListItem); alreadyExistItem != nil { - lru.list.Remove(alreadyExistItem.(*glist.Element)) - } - // Pushing key to the head of the list - // and setting its list item to hash table for quick indexing. - lru.data.Set(rawListItem, lru.list.PushFront(rawListItem)) - } else { - break - } + + if evictedKey = l.list.PopBack(); evictedKey != nil { + l.data.Remove(evictedKey) } - // Data cleaning up. - for clearLength := lru.Size() - lru.cache.cap; clearLength > 0; clearLength-- { - if topKey := lru.Pop(); topKey != nil { - lru.cache.clearByKey(topKey, true) - } + return +} + +// Clear deletes all keys. +func (l *memoryLru) Clear() { + if l == nil { + return } + l.mu.Lock() + defer l.mu.Unlock() + l.data.Clear() + l.list.Clear() } diff --git a/os/gcache/gcache_adapter_redis.go b/os/gcache/gcache_adapter_redis.go index 2a49fc919f1..794556a454c 100644 --- a/os/gcache/gcache_adapter_redis.go +++ b/os/gcache/gcache_adapter_redis.go @@ -21,7 +21,7 @@ type AdapterRedis struct { } // NewAdapterRedis creates and returns a new memory cache object. -func NewAdapterRedis(redis *gredis.Redis) Adapter { +func NewAdapterRedis(redis *gredis.Redis) *AdapterRedis { return &AdapterRedis{ redis: redis, } diff --git a/os/gcache/gcache_cache.go b/os/gcache/gcache_cache.go index bd3d2ac4b3e..7cae6c7c8c5 100644 --- a/os/gcache/gcache_cache.go +++ b/os/gcache/gcache_cache.go @@ -8,7 +8,6 @@ package gcache import ( "context" - "github.com/gogf/gf/v2/util/gconv" ) @@ -23,9 +22,14 @@ type localAdapter = Adapter // New creates and returns a new cache object using default memory adapter. // Note that the LRU feature is only available using memory adapter. func New(lruCap ...int) *Cache { - memAdapter := NewAdapterMemory(lruCap...) + var adapter Adapter + if len(lruCap) == 0 { + adapter = NewAdapterMemory() + } else { + adapter = NewAdapterMemoryLru(lruCap[0]) + } c := &Cache{ - localAdapter: memAdapter, + localAdapter: adapter, } return c } diff --git a/os/gcache/gcache_z_bench_test.go b/os/gcache/gcache_z_bench_test.go index ef41af49c50..3ab6b9b4ff0 100644 --- a/os/gcache/gcache_z_bench_test.go +++ b/os/gcache/gcache_z_bench_test.go @@ -17,7 +17,7 @@ import ( var ( localCache = gcache.New() - localCacheLru = gcache.New(10000) + localCacheLru = gcache.NewWithAdapter(gcache.NewAdapterMemoryLru(10000)) ) func Benchmark_CacheSet(b *testing.B) { diff --git a/os/gcache/gcache_z_example_cache_test.go b/os/gcache/gcache_z_example_cache_test.go index 5628d34d30e..64a5e012bec 100644 --- a/os/gcache/gcache_z_example_cache_test.go +++ b/os/gcache/gcache_z_example_cache_test.go @@ -97,7 +97,7 @@ func ExampleCache_SetIfNotExist() { // true // false // [k1] - // [] + // [] } func ExampleCache_SetMap() { diff --git a/os/gcache/gcache_z_unit_test.go b/os/gcache/gcache_z_unit_test.go index 23ba4dc0b80..d1554e8a2ed 100644 --- a/os/gcache/gcache_z_unit_test.go +++ b/os/gcache/gcache_z_unit_test.go @@ -168,30 +168,26 @@ func TestCache_LRU(t *testing.T) { t.AssertNil(cache.Set(ctx, i, i, 0)) } n, _ := cache.Size(ctx) - t.Assert(n, 10) - v, _ := cache.Get(ctx, 6) - t.Assert(v, 6) - time.Sleep(4 * time.Second) - g.Log().Debugf(ctx, `items after lru: %+v`, cache.MustData(ctx)) - n, _ = cache.Size(ctx) t.Assert(n, 2) - v, _ = cache.Get(ctx, 6) - t.Assert(v, 6) - v, _ = cache.Get(ctx, 1) - t.Assert(v, nil) - t.Assert(cache.Close(ctx), nil) + v, _ := cache.Get(ctx, 6) + t.AssertNil(v) + v, _ = cache.Get(ctx, 9) + t.Assert(v, 9) }) } func TestCache_LRU_expire(t *testing.T) { gtest.C(t, func(t *gtest.T) { cache := gcache.New(2) - t.Assert(cache.Set(ctx, 1, nil, 1000), nil) + t.Assert(cache.Set(ctx, 1, nil, time.Millisecond), nil) + n, _ := cache.Size(ctx) t.Assert(n, 1) - v, _ := cache.Get(ctx, 1) - t.Assert(v, nil) + time.Sleep(time.Millisecond * 10) + + n, _ = cache.Size(ctx) + t.Assert(n, 0) }) } @@ -480,7 +476,10 @@ func TestCache_Basic(t *testing.T) { gtest.C(t, func(t *gtest.T) { { cache := gcache.New() - cache.SetMap(ctx, g.MapAnyAny{1: 11, 2: 22}, 0) + cache.SetMap(ctx, g.MapAnyAny{ + 1: 11, + 2: 22, + }, 0) b, _ := cache.Contains(ctx, 1) t.Assert(b, true) v, _ := cache.Get(ctx, 1) @@ -520,6 +519,7 @@ func TestCache_Basic(t *testing.T) { t.Assert(data[3], nil) n, _ := gcache.Size(ctx) t.Assert(n, 2) + keys, _ := gcache.Keys(ctx) t.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true) keyStrs, _ := gcache.KeyStrings(ctx)