github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/framework/cache/memory.go (about)

     1  // The package is migrated from beego, you can get from following link:
     2  // import(
     3  //   "github.com/beego/beego/v2/client/cache"
     4  // )
     5  // Copyright 2023. All Rights Reserved.
     6  //
     7  // Licensed under the Apache License, Version 2.0 (the "License");
     8  // you may not use this file except in compliance with the License.
     9  // You may obtain a copy of the License at
    10  //
    11  //      http://www.apache.org/licenses/LICENSE-2.0
    12  //
    13  // Unless required by applicable law or agreed to in writing, software
    14  // distributed under the License is distributed on an "AS IS" BASIS,
    15  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  // See the License for the specific language governing permissions and
    17  // limitations under the License.
    18  
    19  package cache
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/mdaxf/iac/framework/berror"
    30  )
    31  
    32  // DefaultEvery sets a timer for how often to recycle the expired cache items in memory (in seconds)
    33  var DefaultEvery = 60 // 1 minute
    34  
    35  // MemoryItem stores memory cache item.
    36  type MemoryItem struct {
    37  	val         interface{}
    38  	createdTime time.Time
    39  	lifespan    time.Duration
    40  }
    41  
    42  func (mi *MemoryItem) isExpire() bool {
    43  	// 0 means forever
    44  	if mi.lifespan == 0 {
    45  		return false
    46  	}
    47  	return time.Since(mi.createdTime) > mi.lifespan
    48  }
    49  
    50  // MemoryCache is a memory cache adapter.
    51  // Contains a RW locker for safe map storage.
    52  type MemoryCache struct {
    53  	sync.RWMutex
    54  	dur   time.Duration
    55  	items map[string]*MemoryItem
    56  	Every int // run an expiration check Every clock time
    57  }
    58  
    59  // NewMemoryCache returns a new MemoryCache.
    60  func NewMemoryCache() Cache {
    61  	cache := MemoryCache{items: make(map[string]*MemoryItem)}
    62  	return &cache
    63  }
    64  
    65  // Get returns cache from memory.
    66  // If non-existent or expired, return nil.
    67  func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) {
    68  	bc.RLock()
    69  	defer bc.RUnlock()
    70  	if itm, ok := bc.items[key]; ok {
    71  		if itm.isExpire() {
    72  			return nil, ErrKeyExpired
    73  		}
    74  		return itm.val, nil
    75  	}
    76  	return nil, ErrKeyNotExist
    77  }
    78  
    79  // GetMulti gets caches from memory.
    80  // If non-existent or expired, return nil.
    81  func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
    82  	rc := make([]interface{}, len(keys))
    83  	keysErr := make([]string, 0)
    84  
    85  	for i, ki := range keys {
    86  		val, err := bc.Get(context.Background(), ki)
    87  		if err != nil {
    88  			keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, err.Error()))
    89  			continue
    90  		}
    91  		rc[i] = val
    92  	}
    93  
    94  	if len(keysErr) == 0 {
    95  		return rc, nil
    96  	}
    97  	return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; "))
    98  }
    99  
   100  // Put puts cache into memory.
   101  // If lifespan is 0, it will never overwrite this value unless restarted
   102  func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
   103  	bc.Lock()
   104  	defer bc.Unlock()
   105  	bc.items[key] = &MemoryItem{
   106  		val:         val,
   107  		createdTime: time.Now(),
   108  		lifespan:    timeout,
   109  	}
   110  	return nil
   111  }
   112  
   113  // Delete cache in memory.
   114  // If the key is not found, it will not return error
   115  func (bc *MemoryCache) Delete(ctx context.Context, key string) error {
   116  	bc.Lock()
   117  	defer bc.Unlock()
   118  	delete(bc.items, key)
   119  	return nil
   120  }
   121  
   122  // Incr increases cache counter in memory.
   123  // Supports int,int32,int64,uint,uint32,uint64.
   124  func (bc *MemoryCache) Incr(ctx context.Context, key string) error {
   125  	bc.Lock()
   126  	defer bc.Unlock()
   127  	itm, ok := bc.items[key]
   128  	if !ok {
   129  		return ErrKeyNotExist
   130  	}
   131  
   132  	val, err := incr(itm.val)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	itm.val = val
   137  	return nil
   138  }
   139  
   140  // Decr decreases counter in memory.
   141  func (bc *MemoryCache) Decr(ctx context.Context, key string) error {
   142  	bc.Lock()
   143  	defer bc.Unlock()
   144  	itm, ok := bc.items[key]
   145  	if !ok {
   146  		return ErrKeyNotExist
   147  	}
   148  
   149  	val, err := decr(itm.val)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	itm.val = val
   154  	return nil
   155  }
   156  
   157  // IsExist checks if cache exists in memory.
   158  func (bc *MemoryCache) IsExist(ctx context.Context, key string) (bool, error) {
   159  	bc.RLock()
   160  	defer bc.RUnlock()
   161  	if v, ok := bc.items[key]; ok {
   162  		return !v.isExpire(), nil
   163  	}
   164  	return false, nil
   165  }
   166  
   167  // ClearAll deletes all cache in memory.
   168  func (bc *MemoryCache) ClearAll(context.Context) error {
   169  	bc.Lock()
   170  	defer bc.Unlock()
   171  	bc.items = make(map[string]*MemoryItem)
   172  	return nil
   173  }
   174  
   175  // StartAndGC starts memory cache. Checks expiration in every clock time.
   176  func (bc *MemoryCache) StartAndGC(config string) error {
   177  	var cf map[string]int
   178  	if err := json.Unmarshal([]byte(config), &cf); err != nil {
   179  		return berror.Wrapf(err, InvalidMemoryCacheCfg, "invalid config, please check your input: %s", config)
   180  	}
   181  	if _, ok := cf["interval"]; !ok {
   182  		cf = make(map[string]int)
   183  		cf["interval"] = DefaultEvery
   184  	}
   185  	dur := time.Duration(cf["interval"]) * time.Second
   186  	bc.Every = cf["interval"]
   187  	bc.dur = dur
   188  	go bc.vacuum()
   189  	return nil
   190  }
   191  
   192  // check expiration.
   193  func (bc *MemoryCache) vacuum() {
   194  	bc.RLock()
   195  	every := bc.Every
   196  	bc.RUnlock()
   197  
   198  	if every < 1 {
   199  		return
   200  	}
   201  	for {
   202  		<-time.After(bc.dur)
   203  		bc.RLock()
   204  		if bc.items == nil {
   205  			bc.RUnlock()
   206  			return
   207  		}
   208  		bc.RUnlock()
   209  		if keys := bc.expiredKeys(); len(keys) != 0 {
   210  			bc.clearItems(keys)
   211  		}
   212  	}
   213  }
   214  
   215  // expiredKeys returns keys list which are expired.
   216  func (bc *MemoryCache) expiredKeys() (keys []string) {
   217  	bc.RLock()
   218  	defer bc.RUnlock()
   219  	for key, itm := range bc.items {
   220  		if itm.isExpire() {
   221  			keys = append(keys, key)
   222  		}
   223  	}
   224  	return
   225  }
   226  
   227  // ClearItems removes all items who's key is in keys
   228  func (bc *MemoryCache) clearItems(keys []string) {
   229  	bc.Lock()
   230  	defer bc.Unlock()
   231  	for _, key := range keys {
   232  		delete(bc.items, key)
   233  	}
   234  }
   235  
   236  func init() {
   237  	Register("memory", NewMemoryCache)
   238  }