1
0
mirror of https://github.com/gogf/gf.git synced 2025-04-05 03:05:05 +08:00

fix(os/gcache): memory leak for LRU when adding operations more faster than deleting (#3823)

This commit is contained in:
John Guo 2024-09-28 18:07:11 +08:00 committed by GitHub
parent ea09457d84
commit 1c97b7a982
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 256 additions and 237 deletions

View File

@ -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.

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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()

View File

@ -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()
}

View File

@ -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))
}
}
// 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)
}
// 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
}
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()
func (l *memoryLru) Remove(keys ...interface{}) {
if l == nil {
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
}
}
// Data cleaning up.
for clearLength := lru.Size() - lru.cache.cap; clearLength > 0; clearLength-- {
if topKey := lru.Pop(); topKey != nil {
lru.cache.clearByKey(topKey, true)
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))
}
}
}
// 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
}
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)
}
// 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
}
if evictedKey = l.list.PopBack(); evictedKey != nil {
l.data.Remove(evictedKey)
}
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()
}

View File

@ -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,
}

View File

@ -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
}

View File

@ -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) {

View File

@ -97,7 +97,7 @@ func ExampleCache_SetIfNotExist() {
// true <nil>
// false <nil>
// [k1]
// [<nil>]
// []
}
func ExampleCache_SetMap() {

View File

@ -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)