github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/infra/acache/cache.go (about) 1 package acache 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "time" 9 "unsafe" 10 11 "golang.org/x/sync/singleflight" 12 13 "github.com/jxskiss/gopkg/v2/collection/heapx" 14 "github.com/jxskiss/gopkg/v2/internal/functicker" 15 ) 16 17 // ErrFetchTimeout indicates a timeout error when refresh a cached value 18 // if Options.FetchTimeout is specified. 19 var ErrFetchTimeout = errors.New("fetch timeout") 20 21 // Options configures the behavior of Cache. 22 type Options struct { 23 24 // Fetcher fetches data from upstream system for a given key. 25 // Result value or error will be cached till next refresh execution. 26 // 27 // The provided Fetcher implementation must return consistently 28 // typed values, else it panics when storing a value of different 29 // type into the underlying sync/atomic.Value. 30 // 31 // The returned value from this function should not be changed after 32 // retrieved from the Cache, else data race happens since there may be 33 // many goroutines access the same value concurrently. 34 Fetcher Fetcher 35 36 // FetchTimeout is used to timeout the fetch request if given, 37 // default is zero (no timeout). 38 // 39 // NOTE: properly configured timeout will prevent task which take very long 40 // time that don't fail fast, which may further block many requests, and 41 // consume huge amount of resources, cause system overload or out of memory. 42 FetchTimeout time.Duration 43 44 // RefreshInterval specifies the interval to refresh the cache values, 45 // default is zero which means don't refresh the cached values. 46 // 47 // If there is valid cache value and the subsequent fetch requests 48 // failed, the existing cache value will be kept untouched. 49 RefreshInterval time.Duration 50 51 // ExpireInterval optionally enables purging unused cached values, 52 // default is zero which means no expiration. 53 // 54 // Note this is mainly used to purge unused data to prevent the cache 55 // growing endlessly, the timing is inaccurate. Also note that 56 // it may delete unused default values set by SetDefault. 57 // 58 // Cached values are deleted using a mark-then-delete strategy. 59 // In each tick of expire interval, an active value will be marked as inactive, 60 // if it's not accessed within the next expire interval, it will be 61 // deleted from the Cache by the next expire execution. 62 // 63 // Each access of the cache value will touch it and mark it as active, which 64 // prevents it being deleted from the cache. 65 ExpireInterval time.Duration 66 67 // ErrorCallback is an optional callback which will be called when 68 // an error is returned by Fetcher during refresh. 69 ErrorCallback func(err error, keys []string) 70 71 // ChangeCallback is an optional callback which will be called when 72 // new value is returned by Fetcher during refresh. 73 ChangeCallback func(key string, oldData, newData any) 74 75 // DeleteCallback is an optional callback which will be called when 76 // a value is deleted from the cache. 77 DeleteCallback func(key string, data any) 78 } 79 80 func (p *Options) validate() { 81 if p.Fetcher == nil { 82 panic("acache: Options.Fetcher must not be nil") 83 } 84 } 85 86 // Cache is an asynchronous cache which prevents duplicate functions calls 87 // that is massive or maybe expensive, or some data which rarely change, 88 // and we want to get it quickly. 89 // 90 // Zero value of Cache is not ready to use. Use the function NewCache to 91 // make a new Cache instance. A Cache value shall not be copied after 92 // initialized. 93 type Cache struct { 94 opt Options 95 sfGroup singleflight.Group 96 data sync.Map 97 98 mu sync.Mutex 99 refreshQueue *heapx.PriorityQueue[int64, string] 100 101 ticker *functicker.Ticker 102 preExpireAt atomic.Int64 103 doingExpire int32 104 doingRefresh int32 105 closed int32 106 } 107 108 // NewCache returns a new Cache instance using the given options. 109 func NewCache(opt Options) *Cache { 110 tickInterval := time.Second 111 return newCacheWithTickInterval(opt, tickInterval) 112 } 113 114 func newCacheWithTickInterval(opt Options, tickInterval time.Duration) *Cache { 115 opt.validate() 116 c := &Cache{ 117 opt: opt, 118 refreshQueue: heapx.NewMinPriorityQueue[int64, string](), 119 } 120 if opt.ExpireInterval > 0 || opt.RefreshInterval > 0 { 121 c.ticker = functicker.New(tickInterval, c.runBackgroundTasks) 122 } 123 return c 124 } 125 126 // Close closes the Cache. 127 // It signals the background goroutines to shut down. 128 // 129 // It should be called when the Cache is no longer needed, 130 // or may lead resource leaks. 131 func (c *Cache) Close() { 132 if !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { 133 return 134 } 135 if c.ticker != nil { 136 c.ticker.Stop() 137 c.ticker = nil 138 } 139 } 140 141 // SetDefault sets the default value of a given key if it is new to the cache. 142 // The param val should not be nil, else it panics. 143 // The returned bool value indicates whether the key already exists in the cache, 144 // if it already exists, this is a no-op. 145 // 146 // It's useful to warm up the cache. 147 func (c *Cache) SetDefault(key string, value any) (exists bool) { 148 if value == nil { 149 panic("acache: value must not be nil") 150 } 151 nowNano := time.Now().UnixNano() 152 ent := allocEntry(value, errDefaultVal, nowNano) 153 actual, loaded := c.data.LoadOrStore(key, ent) 154 if loaded { 155 actual.(*entry).MarkActive() 156 } else { 157 c.addToRefreshQueue(nowNano, key) 158 } 159 return loaded 160 } 161 162 // Update sets a value for key into the cache. 163 // If key is not cached in the cache, it adds the given key value to the cache. 164 // The param value should not be nil, else it panics. 165 func (c *Cache) Update(key string, value any) { 166 if value == nil { 167 panic("acache: value must not be nil") 168 } 169 nowNano := time.Now().UnixNano() 170 val, ok := c.data.Load(key) 171 if ok { 172 ent := val.(*entry) 173 ent.Store(value, nil, nowNano) 174 } else { 175 ent := allocEntry(value, nil, nowNano) 176 c.data.Store(key, ent) 177 } 178 c.addToRefreshQueue(nowNano, key) 179 } 180 181 // Contains tells whether the cache contains the specified key. 182 // It returns false if key is never accessed from the cache, 183 // true means that a value or an error for key exists in the cache. 184 func (c *Cache) Contains(key string) bool { 185 _, ok := c.data.Load(key) 186 return ok 187 } 188 189 // Get tries to fetch a value corresponding to the given key from the cache. 190 // If it's not cached, a calling to Fetcher.Fetch will be fired 191 // and the result will be cached. 192 // 193 // If error occurs during the first fetching, the error will be cached until 194 // the subsequent fetching requests triggered by refreshing succeed. 195 // The cached error will be returned, it does not trigger a calling to 196 // Options.Fetcher again. 197 // 198 // If a default value is set by SetDefault, the default value will be used, 199 // it does not trigger a calling to Options.Fetcher. 200 func (c *Cache) Get(key string) (any, error) { 201 val, ok := c.data.Load(key) 202 if ok { 203 ent := val.(*entry) 204 ent.MarkActive() 205 value, err := ent.Load() 206 if err == errDefaultVal { 207 err = nil 208 } 209 return value, err 210 } 211 212 // Wait the fetch function to get result. 213 return c.doFetch(key, nil) 214 } 215 216 // GetOrDefault tries to fetch a value corresponding to the given key from 217 // the cache. If it's not cached, a calling to Options.Fetcher 218 // will be fired and the result will be cached. 219 // 220 // If error occurs during the first fetching, defaultVal will be set into 221 // the cache and returned, the default value will also be used for 222 // further calling of Get and GetOrDefault. 223 func (c *Cache) GetOrDefault(key string, defaultVal any) any { 224 val, ok := c.data.Load(key) 225 if ok { 226 ent := val.(*entry) 227 ent.MarkActive() 228 value, err := ent.Load() 229 if err != nil { 230 value = defaultVal 231 } 232 return value 233 } 234 235 // Fetch result from upstream or use the default value 236 val, _ = c.doFetch(key, defaultVal) 237 return val 238 } 239 240 func (c *Cache) doFetch(key string, defaultVal any) (any, error) { 241 if c.opt.FetchTimeout == 0 { 242 return c.fetchNoTimeout(key, defaultVal) 243 } 244 return c.fetchWithTimeout(key, defaultVal) 245 } 246 247 func (c *Cache) fetchNoTimeout(key string, defaultVal any) (any, error) { 248 val, err, _ := c.sfGroup.Do(key, func() (any, error) { 249 val, err := c.opt.Fetcher.Fetch(key) 250 if err != nil && defaultVal != nil { 251 val, err = defaultVal, errDefaultVal 252 } 253 nowNano := time.Now().UnixNano() 254 ent := allocEntry(val, err, nowNano) 255 c.data.Store(key, ent) 256 c.addToRefreshQueue(nowNano, key) 257 return val, err 258 }) 259 return val, err 260 } 261 262 func (c *Cache) fetchWithTimeout(key string, defaultVal any) (any, error) { 263 timeout := time.NewTimer(c.opt.FetchTimeout) 264 ch := c.sfGroup.DoChan(key, func() (any, error) { 265 val, err := c.opt.Fetcher.Fetch(key) 266 if err != nil && defaultVal != nil { 267 val, err = defaultVal, errDefaultVal 268 } 269 nowNano := time.Now().UnixNano() 270 ent := allocEntry(val, err, nowNano) 271 c.data.Store(key, ent) 272 c.addToRefreshQueue(nowNano, key) 273 return val, err 274 }) 275 select { 276 case <-timeout.C: 277 return nil, ErrFetchTimeout 278 case result := <-ch: 279 timeout.Stop() 280 return result.Val, result.Err 281 } 282 } 283 284 // Delete deletes the entry of key from the cache if it exists. 285 func (c *Cache) Delete(key string) { 286 val, ok := c.data.Load(key) 287 if ok { 288 ent := val.(*entry) 289 value, _ := ent.Load() 290 if value != nil && c.opt.DeleteCallback != nil { 291 c.opt.DeleteCallback(key, value) 292 } 293 c.data.Delete(key) 294 } 295 } 296 297 // DeleteFunc iterates the cache and deletes entries that the key matches 298 // the given function. 299 func (c *Cache) DeleteFunc(match func(key string) bool) { 300 hasDeleteCallback := c.opt.DeleteCallback != nil 301 c.data.Range(func(key, val any) bool { 302 keystr := key.(string) 303 if match(keystr) { 304 if hasDeleteCallback { 305 ent := val.(*entry) 306 value, _ := ent.Load() 307 if value != nil { 308 c.opt.DeleteCallback(keystr, value) 309 } 310 } 311 c.data.Delete(key) 312 } 313 return true 314 }) 315 } 316 317 func (c *Cache) addToRefreshQueue(updateAtNano int64, key string) { 318 c.mu.Lock() 319 c.refreshQueue.Push(updateAtNano, key) 320 c.mu.Unlock() 321 } 322 323 func (c *Cache) runBackgroundTasks() { 324 if atomic.LoadInt32(&c.closed) > 0 { 325 return 326 } 327 if c.opt.ExpireInterval > 0 { 328 c.doExpire(false) 329 } 330 if c.opt.RefreshInterval > 0 { 331 if atomic.CompareAndSwapInt32(&c.doingRefresh, 0, 1) { 332 c.doRefresh() 333 atomic.StoreInt32(&c.doingRefresh, 0) 334 } 335 } 336 } 337 338 func (c *Cache) doExpire(force bool) { 339 nowUnix := time.Now().Unix() 340 preExpireAt := c.preExpireAt.Load() 341 342 // "force" helps to do unittest. 343 if !force { 344 if preExpireAt == 0 { 345 c.preExpireAt.Store(nowUnix) 346 return 347 } 348 if time.Duration(nowUnix-preExpireAt)*time.Second < c.opt.ExpireInterval { 349 return 350 } 351 } 352 353 hasDeleteCallback := c.opt.DeleteCallback != nil 354 if atomic.CompareAndSwapInt32(&c.doingExpire, 0, 1) { 355 defer atomic.StoreInt32(&c.doingExpire, 0) 356 c.preExpireAt.Store(nowUnix) 357 c.data.Range(func(key, val any) bool { 358 keystr := key.(string) 359 ent := val.(*entry) 360 361 // If entry.expire is "active", we mark it as "inactive" here. 362 // Then during the next execution, "inactive" entries will be deleted. 363 isActive := atomic.CompareAndSwapInt32(&ent.expire, active, inactive) 364 if !isActive { 365 if hasDeleteCallback { 366 value, _ := ent.Load() 367 if value != nil { 368 c.opt.DeleteCallback(keystr, val) 369 } 370 } 371 c.data.Delete(key) 372 } 373 return true 374 }) 375 } 376 } 377 378 func (c *Cache) needRefresh(nowNano, updateAtNano int64) bool { 379 return time.Duration(nowNano-updateAtNano) >= c.opt.RefreshInterval 380 } 381 382 func (c *Cache) checkEntryNeedRefresh(nowNano, updateAtNano int64, key string) (ent *entry, refresh bool) { 383 val, _ := c.data.Load(key) 384 if val == nil { 385 // The data has already been deleted. 386 return nil, false 387 } 388 ent = val.(*entry) 389 if ent.GetUpdateAt() != updateAtNano { 390 // The data has already been changed. 391 return ent, false 392 } 393 if !c.needRefresh(nowNano, updateAtNano) { 394 return ent, false 395 } 396 return ent, true 397 } 398 399 func (c *Cache) doRefresh() { 400 if _, ok := c.opt.Fetcher.(BatchFetcher); ok { 401 c.doBatchRefresh() 402 return 403 } 404 405 hasErrorCallback := c.opt.ErrorCallback != nil 406 hasChangeCallback := c.opt.ChangeCallback != nil 407 for { 408 nowNano := time.Now().UnixNano() 409 needRefresh := false 410 411 c.mu.Lock() 412 updateAt, key, ok := c.refreshQueue.Peek() 413 if ok && c.needRefresh(nowNano, updateAt) { 414 c.refreshQueue.Pop() 415 needRefresh = true 416 } 417 c.mu.Unlock() 418 if !needRefresh { 419 break 420 } 421 422 var ent *entry 423 ent, needRefresh = c.checkEntryNeedRefresh(nowNano, updateAt, key) 424 if !needRefresh { 425 continue 426 } 427 newVal, err := c.opt.Fetcher.Fetch(key) 428 if err != nil { 429 if hasErrorCallback { 430 c.opt.ErrorCallback(err, []string{key}) 431 } 432 _, oldErr := ent.Load() 433 if oldErr != nil { 434 ent.SetError(err) 435 } 436 } else { 437 // Save the new value from upstream. 438 if hasChangeCallback { 439 oldVal, _ := ent.Load() 440 c.opt.ChangeCallback(key, oldVal, newVal) 441 } 442 ent.Store(newVal, nil, nowNano) 443 } 444 c.addToRefreshQueue(nowNano, key) 445 } 446 } 447 448 type refreshQueueItem struct { 449 key string 450 tNano int64 451 } 452 453 func (c *Cache) doBatchRefresh() { 454 fetcher := c.opt.Fetcher.(BatchFetcher) 455 batchSize := fetcher.BatchSize() 456 expiredItems := make([]refreshQueueItem, 0, batchSize) 457 keys := make([]string, 0, batchSize) 458 for { 459 nowNano := time.Now().UnixNano() 460 expiredItems = expiredItems[:0] 461 keys = keys[:0] 462 463 c.mu.Lock() 464 for len(expiredItems) < batchSize { 465 updateAt, key, ok := c.refreshQueue.Peek() 466 if !ok || !c.needRefresh(nowNano, updateAt) { 467 break 468 } 469 c.refreshQueue.Pop() 470 expiredItems = append(expiredItems, refreshQueueItem{key, updateAt}) 471 } 472 c.mu.Unlock() 473 474 for _, item := range expiredItems { 475 _, needRefresh := c.checkEntryNeedRefresh(nowNano, item.tNano, item.key) 476 if !needRefresh { 477 continue 478 } 479 keys = append(keys, item.key) 480 } 481 if len(keys) > 0 { 482 c.batchRefreshKeys(keys) 483 } 484 485 // No more expired data to refresh. 486 if len(expiredItems) < batchSize { 487 break 488 } 489 } 490 } 491 492 func (c *Cache) batchRefreshKeys(keys []string) { 493 nowNano := time.Now().UnixNano() 494 fetcher := c.opt.Fetcher.(BatchFetcher) 495 newValMap, err := fetcher.BatchFetch(keys) 496 if err != nil { 497 hasErrorCallback := c.opt.ErrorCallback != nil 498 if hasErrorCallback { 499 c.opt.ErrorCallback(err, keys) 500 } 501 for _, key := range keys { 502 val, _ := c.data.Load(key) 503 if val == nil { 504 continue 505 } 506 ent := val.(*entry) 507 _, oldErr := ent.Load() 508 if oldErr != nil { 509 ent.SetError(err) 510 } 511 } 512 return 513 } 514 hasChangeCallback := c.opt.ChangeCallback != nil 515 for key, newVal := range newValMap { 516 entVal, _ := c.data.Load(key) 517 if entVal == nil { 518 continue 519 } 520 ent := entVal.(*entry) 521 if hasChangeCallback { 522 oldVal, _ := ent.Load() 523 c.opt.ChangeCallback(key, oldVal, newVal) 524 } 525 ent.Store(newVal, nil, nowNano) 526 c.addToRefreshQueue(nowNano, key) 527 } 528 } 529 530 func allocEntry(val any, err error, updateAtNano int64) *entry { 531 ent := &entry{} 532 ent.Store(val, err, updateAtNano) 533 return ent 534 } 535 536 type entry struct { 537 val atomic.Value 538 errp unsafe.Pointer // *error 539 updateAt int64 540 expire int32 541 } 542 543 func (e *entry) Load() (any, error) { 544 val := e.val.Load() 545 errp := atomic.LoadPointer(&e.errp) 546 if errp == nil { 547 return val, nil 548 } 549 return val, *(*error)(errp) 550 } 551 552 func (e *entry) Store(val any, err error, updateAtNano int64) { 553 if err != nil { 554 atomic.StorePointer(&e.errp, unsafe.Pointer(&err)) 555 if val != nil { 556 e.val.Store(val) 557 } 558 } else if val != nil { 559 e.val.Store(val) 560 atomic.StorePointer(&e.errp, nil) 561 } 562 e.SetUpdateAt(updateAtNano) 563 } 564 565 func (e *entry) SetError(err error) { 566 atomic.StorePointer(&e.errp, unsafe.Pointer(&err)) 567 } 568 569 func (e *entry) GetUpdateAt() int64 { 570 return atomic.LoadInt64(&e.updateAt) 571 } 572 573 func (e *entry) SetUpdateAt(updateAtNano int64) { 574 atomic.StoreInt64(&e.updateAt, updateAtNano) 575 } 576 577 func (e *entry) MarkActive() { 578 atomic.StoreInt32(&e.expire, active) 579 } 580 581 const ( 582 active = 0 583 inactive = 1 584 ) 585 586 var errDefaultVal = tombError(1) 587 588 type tombError int 589 590 func (e tombError) Error() string { 591 return fmt.Sprintf("tombError(%d)", e) 592 }