github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/cache/memory.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package cache
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  )
    30  
    31  var (
    32  	// Timer for how often to recycle the expired cache items in memory (in seconds)
    33  	DefaultEvery = 60 // 1 minute
    34  )
    35  
    36  // MemoryItem stores memory cache item.
    37  type MemoryItem struct {
    38  	val         interface{}
    39  	createdTime time.Time
    40  	lifespan    time.Duration
    41  }
    42  
    43  func (mi *MemoryItem) isExpire() bool {
    44  	// 0 means forever
    45  	if mi.lifespan == 0 {
    46  		return false
    47  	}
    48  	return time.Since(mi.createdTime) > mi.lifespan
    49  }
    50  
    51  // MemoryCache is a memory cache adapter.
    52  // Contains a RW locker for safe map storage.
    53  type MemoryCache struct {
    54  	sync.RWMutex
    55  	dur   time.Duration
    56  	items map[string]*MemoryItem
    57  	Every int // run an expiration check Every clock time
    58  }
    59  
    60  // NewMemoryCache returns a new MemoryCache.
    61  func NewMemoryCache() Cache {
    62  	cache := MemoryCache{items: make(map[string]*MemoryItem)}
    63  	return &cache
    64  }
    65  
    66  // Get returns cache from memory.
    67  // If non-existent or expired, return nil.
    68  func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) {
    69  	bc.RLock()
    70  	defer bc.RUnlock()
    71  	if itm, ok := bc.items[key]; ok {
    72  		if itm.isExpire() {
    73  			return nil, errors.New("the key is expired")
    74  		}
    75  		return itm.val, nil
    76  	}
    77  	return nil, errors.New("the key isn't exist")
    78  }
    79  
    80  // GetMulti gets caches from memory.
    81  // If non-existent or expired, return nil.
    82  func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
    83  	rc := make([]interface{}, len(keys))
    84  	keysErr := make([]string, 0)
    85  
    86  	for i, ki := range keys {
    87  		val, err := bc.Get(context.Background(), ki)
    88  		if err != nil {
    89  			keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, err.Error()))
    90  			continue
    91  		}
    92  		rc[i] = val
    93  	}
    94  
    95  	if len(keysErr) == 0 {
    96  		return rc, nil
    97  	}
    98  	return rc, errors.New(strings.Join(keysErr, "; "))
    99  }
   100  
   101  // Put puts cache into memory.
   102  // If lifespan is 0, it will never overwrite this value unless restarted
   103  func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
   104  	bc.Lock()
   105  	defer bc.Unlock()
   106  	bc.items[key] = &MemoryItem{
   107  		val:         val,
   108  		createdTime: time.Now(),
   109  		lifespan:    timeout,
   110  	}
   111  	return nil
   112  }
   113  
   114  // Delete cache in memory.
   115  func (bc *MemoryCache) Delete(ctx context.Context, key string) error {
   116  	bc.Lock()
   117  	defer bc.Unlock()
   118  	if _, ok := bc.items[key]; !ok {
   119  		return errors.New("key not exist")
   120  	}
   121  	delete(bc.items, key)
   122  	if _, ok := bc.items[key]; ok {
   123  		return errors.New("delete key error")
   124  	}
   125  	return nil
   126  }
   127  
   128  // Incr increases cache counter in memory.
   129  // Supports int,int32,int64,uint,uint32,uint64.
   130  func (bc *MemoryCache) Incr(ctx context.Context, key string) error {
   131  	bc.Lock()
   132  	defer bc.Unlock()
   133  	itm, ok := bc.items[key]
   134  	if !ok {
   135  		return errors.New("key not exist")
   136  	}
   137  	switch val := itm.val.(type) {
   138  	case int:
   139  		itm.val = val + 1
   140  	case int32:
   141  		itm.val = val + 1
   142  	case int64:
   143  		itm.val = val + 1
   144  	case uint:
   145  		itm.val = val + 1
   146  	case uint32:
   147  		itm.val = val + 1
   148  	case uint64:
   149  		itm.val = val + 1
   150  	default:
   151  		return errors.New("item val is not (u)int (u)int32 (u)int64")
   152  	}
   153  	return nil
   154  }
   155  
   156  // Decr decreases counter in memory.
   157  func (bc *MemoryCache) Decr(ctx context.Context, key string) error {
   158  	bc.Lock()
   159  	defer bc.Unlock()
   160  	itm, ok := bc.items[key]
   161  	if !ok {
   162  		return errors.New("key not exist")
   163  	}
   164  	switch val := itm.val.(type) {
   165  	case int:
   166  		itm.val = val - 1
   167  	case int64:
   168  		itm.val = val - 1
   169  	case int32:
   170  		itm.val = val - 1
   171  	case uint:
   172  		if val > 0 {
   173  			itm.val = val - 1
   174  		} else {
   175  			return errors.New("item val is less than 0")
   176  		}
   177  	case uint32:
   178  		if val > 0 {
   179  			itm.val = val - 1
   180  		} else {
   181  			return errors.New("item val is less than 0")
   182  		}
   183  	case uint64:
   184  		if val > 0 {
   185  			itm.val = val - 1
   186  		} else {
   187  			return errors.New("item val is less than 0")
   188  		}
   189  	default:
   190  		return errors.New("item val is not int int64 int32")
   191  	}
   192  	return nil
   193  }
   194  
   195  // IsExist checks if cache exists in memory.
   196  func (bc *MemoryCache) IsExist(ctx context.Context, key string) (bool, error) {
   197  	bc.RLock()
   198  	defer bc.RUnlock()
   199  	if v, ok := bc.items[key]; ok {
   200  		return !v.isExpire(), nil
   201  	}
   202  	return false, nil
   203  }
   204  
   205  // ClearAll deletes all cache in memory.
   206  func (bc *MemoryCache) ClearAll(context.Context) error {
   207  	bc.Lock()
   208  	defer bc.Unlock()
   209  	bc.items = make(map[string]*MemoryItem)
   210  	return nil
   211  }
   212  
   213  // StartAndGC starts memory cache. Checks expiration in every clock time.
   214  func (bc *MemoryCache) StartAndGC(config string) error {
   215  	var cf map[string]int
   216  	json.Unmarshal([]byte(config), &cf)
   217  	if _, ok := cf["interval"]; !ok {
   218  		cf = make(map[string]int)
   219  		cf["interval"] = DefaultEvery
   220  	}
   221  	dur := time.Duration(cf["interval"]) * time.Second
   222  	bc.Every = cf["interval"]
   223  	bc.dur = dur
   224  	go bc.vacuum()
   225  	return nil
   226  }
   227  
   228  // check expiration.
   229  func (bc *MemoryCache) vacuum() {
   230  	bc.RLock()
   231  	every := bc.Every
   232  	bc.RUnlock()
   233  
   234  	if every < 1 {
   235  		return
   236  	}
   237  	for {
   238  		<-time.After(bc.dur)
   239  		bc.RLock()
   240  		if bc.items == nil {
   241  			bc.RUnlock()
   242  			return
   243  		}
   244  		bc.RUnlock()
   245  		if keys := bc.expiredKeys(); len(keys) != 0 {
   246  			bc.clearItems(keys)
   247  		}
   248  	}
   249  }
   250  
   251  // expiredKeys returns keys list which are expired.
   252  func (bc *MemoryCache) expiredKeys() (keys []string) {
   253  	bc.RLock()
   254  	defer bc.RUnlock()
   255  	for key, itm := range bc.items {
   256  		if itm.isExpire() {
   257  			keys = append(keys, key)
   258  		}
   259  	}
   260  	return
   261  }
   262  
   263  // ClearItems removes all items who's key is in keys
   264  func (bc *MemoryCache) clearItems(keys []string) {
   265  	bc.Lock()
   266  	defer bc.Unlock()
   267  	for _, key := range keys {
   268  		delete(bc.items, key)
   269  	}
   270  }
   271  
   272  func init() {
   273  	Register("memory", NewMemoryCache)
   274  }