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:
parent
ea09457d84
commit
1c97b7a982
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -97,7 +97,7 @@ func ExampleCache_SetIfNotExist() {
|
||||
// true <nil>
|
||||
// false <nil>
|
||||
// [k1]
|
||||
// [<nil>]
|
||||
// []
|
||||
}
|
||||
|
||||
func ExampleCache_SetMap() {
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user