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 }