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  }