github.com/aaabigfish/gopkg@v1.1.0/cache/asynccache/asynccache.go (about)

     1  // Copyright 2021 ByteDance Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package asynccache
    16  
    17  import (
    18  	"fmt"
    19  	"log"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	sf "golang.org/x/sync/singleflight"
    25  )
    26  
    27  // Options controls the behavior of AsyncCache.
    28  type Options struct {
    29  	RefreshDuration time.Duration
    30  	Fetcher         func(key string) (interface{}, error)
    31  
    32  	// If EnableExpire is true, ExpireDuration MUST be set.
    33  	EnableExpire   bool
    34  	ExpireDuration time.Duration
    35  
    36  	ErrorHandler  func(key string, err error)
    37  	ChangeHandler func(key string, oldData, newData interface{})
    38  	DeleteHandler func(key string, oldData interface{})
    39  
    40  	IsSame     func(key string, oldData, newData interface{}) bool
    41  	ErrLogFunc func(str string)
    42  }
    43  
    44  // AsyncCache .
    45  type AsyncCache interface {
    46  	// SetDefault sets the default value of given key if it is new to the cache.
    47  	// It is useful for cache warming up.
    48  	// Param val should not be nil.
    49  	SetDefault(key string, val interface{}) (exist bool)
    50  
    51  	// Get tries to fetch a value corresponding to the given key from the cache.
    52  	// If error occurs during the first time fetching, it will be cached until the
    53  	// sequential fetching triggered by the refresh goroutine succeed.
    54  	Get(key string) (val interface{}, err error)
    55  
    56  	// GetOrSet tries to fetch a value corresponding to the given key from the cache.
    57  	// If the key is not yet cached or error occurs, the default value will be set.
    58  	GetOrSet(key string, defaultVal interface{}) (val interface{})
    59  
    60  	// Dump dumps all cache entries.
    61  	// This will not cause expire to refresh.
    62  	Dump() map[string]interface{}
    63  
    64  	// DeleteIf deletes cached entries that match the `shouldDelete` predicate.
    65  	DeleteIf(shouldDelete func(key string) bool)
    66  
    67  	// Close closes the async cache.
    68  	// This should be called when the cache is no longer needed, or may lead to resource leak.
    69  	Close()
    70  }
    71  
    72  // asyncCache .
    73  type asyncCache struct {
    74  	sfg  sf.Group
    75  	opt  Options
    76  	data sync.Map
    77  }
    78  
    79  type tickerType int
    80  
    81  const (
    82  	refreshTicker tickerType = iota
    83  	expireTicker
    84  )
    85  
    86  type sharedTicker struct {
    87  	sync.Mutex
    88  	started  bool
    89  	stopChan chan bool
    90  	ticker   *time.Ticker
    91  	caches   map[*asyncCache]struct{}
    92  }
    93  
    94  var (
    95  	// 共用 ticker
    96  	refreshTickerMap, expireTickerMap sync.Map
    97  )
    98  
    99  type entry struct {
   100  	val    atomic.Value
   101  	expire int32 // 0 means useful, 1 will expire
   102  	err    Error
   103  }
   104  
   105  func (e *entry) Store(x interface{}, err error) {
   106  	if x != nil {
   107  		e.val.Store(x)
   108  	} else {
   109  		e.val = atomic.Value{}
   110  	}
   111  	e.err.Store(err)
   112  }
   113  
   114  func (e *entry) Touch() {
   115  	atomic.StoreInt32(&e.expire, 0)
   116  }
   117  
   118  // NewAsyncCache creates an AsyncCache.
   119  func NewAsyncCache(opt Options) AsyncCache {
   120  	c := &asyncCache{
   121  		sfg: sf.Group{},
   122  		opt: opt,
   123  	}
   124  	if c.opt.ErrLogFunc == nil {
   125  		c.opt.ErrLogFunc = func(str string) {
   126  			log.Println(str)
   127  		}
   128  	}
   129  	if c.opt.EnableExpire {
   130  		if c.opt.ExpireDuration == 0 {
   131  			panic("asynccache: invalid ExpireDuration")
   132  		}
   133  		ti, _ := expireTickerMap.LoadOrStore(c.opt.ExpireDuration,
   134  			&sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)})
   135  		et := ti.(*sharedTicker)
   136  		et.Lock()
   137  		et.caches[c] = struct{}{}
   138  		if !et.started {
   139  			et.started = true
   140  			et.ticker = time.NewTicker(c.opt.ExpireDuration)
   141  			go et.tick(et.ticker, expireTicker)
   142  		}
   143  		et.Unlock()
   144  	}
   145  
   146  	ti, _ := refreshTickerMap.LoadOrStore(c.opt.RefreshDuration,
   147  		&sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)})
   148  	rt := ti.(*sharedTicker)
   149  	rt.Lock()
   150  	rt.caches[c] = struct{}{}
   151  	if !rt.started {
   152  		rt.started = true
   153  		rt.ticker = time.NewTicker(c.opt.RefreshDuration)
   154  		go rt.tick(rt.ticker, refreshTicker)
   155  	}
   156  	rt.Unlock()
   157  	return c
   158  }
   159  
   160  // SetDefault sets the default value of given key if it is new to the cache.
   161  func (c *asyncCache) SetDefault(key string, val interface{}) bool {
   162  	ety := &entry{}
   163  	ety.Store(val, nil)
   164  	actual, exist := c.data.LoadOrStore(key, ety)
   165  	if exist {
   166  		actual.(*entry).Touch()
   167  	}
   168  	return exist
   169  }
   170  
   171  // Get tries to fetch a value corresponding to the given key from the cache.
   172  // If error occurs during in the first time fetching, it will be cached until the
   173  // sequential fetchings triggered by the refresh goroutine succeed.
   174  func (c *asyncCache) Get(key string) (val interface{}, err error) {
   175  	var ok bool
   176  	val, ok = c.data.Load(key)
   177  	if ok {
   178  		e := val.(*entry)
   179  		e.Touch()
   180  		return e.val.Load(), e.err.Load()
   181  	}
   182  
   183  	val, err, _ = c.sfg.Do(key, func() (v interface{}, e error) {
   184  		v, e = c.opt.Fetcher(key)
   185  		ety := &entry{}
   186  		ety.Store(v, e)
   187  		c.data.Store(key, ety)
   188  		return
   189  	})
   190  	return
   191  }
   192  
   193  // GetOrSet tries to fetch a value corresponding to the given key from the cache.
   194  // If the key is not yet cached or fetching failed, the default value will be set.
   195  func (c *asyncCache) GetOrSet(key string, def interface{}) (val interface{}) {
   196  	if v, ok := c.data.Load(key); ok {
   197  		e := v.(*entry)
   198  		if e.err.Load() != nil {
   199  			ety := &entry{}
   200  			ety.Store(def, nil)
   201  			c.data.Store(key, ety)
   202  			return def
   203  		}
   204  		e.Touch()
   205  		return e.val.Load()
   206  	}
   207  
   208  	val, _, _ = c.sfg.Do(key, func() (interface{}, error) {
   209  		v, e := c.opt.Fetcher(key)
   210  		if e != nil {
   211  			v = def
   212  		}
   213  		ety := &entry{}
   214  		ety.Store(v, nil)
   215  		c.data.Store(key, ety)
   216  		return v, nil
   217  	})
   218  	return
   219  }
   220  
   221  // Dump dumps all cached entries.
   222  func (c *asyncCache) Dump() map[string]interface{} {
   223  	data := make(map[string]interface{})
   224  	c.data.Range(func(key, val interface{}) bool {
   225  		k, ok := key.(string)
   226  		if !ok {
   227  			c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k))
   228  			c.data.Delete(key)
   229  			return true
   230  		}
   231  		data[k] = val.(*entry).val.Load()
   232  		return true
   233  	})
   234  	return data
   235  }
   236  
   237  // DeleteIf deletes cached entries that match the `shouldDelete` predicate.
   238  func (c *asyncCache) DeleteIf(shouldDelete func(key string) bool) {
   239  	c.data.Range(func(key, value interface{}) bool {
   240  		s := key.(string)
   241  		if shouldDelete(s) {
   242  			if c.opt.DeleteHandler != nil {
   243  				go c.opt.DeleteHandler(s, value)
   244  			}
   245  			c.data.Delete(key)
   246  		}
   247  		return true
   248  	})
   249  }
   250  
   251  // Close stops the background goroutine.
   252  func (c *asyncCache) Close() {
   253  	// close refresh ticker
   254  	ti, _ := refreshTickerMap.Load(c.opt.RefreshDuration)
   255  	rt := ti.(*sharedTicker)
   256  	rt.Lock()
   257  	delete(rt.caches, c)
   258  	if len(rt.caches) == 0 {
   259  		rt.stopChan <- true
   260  		rt.started = false
   261  	}
   262  	rt.Unlock()
   263  
   264  	if c.opt.EnableExpire {
   265  		// close expire ticker
   266  		ti, _ := expireTickerMap.Load(c.opt.ExpireDuration)
   267  		et := ti.(*sharedTicker)
   268  		et.Lock()
   269  		delete(et.caches, c)
   270  		if len(et.caches) == 0 {
   271  			et.stopChan <- true
   272  			et.started = false
   273  		}
   274  		et.Unlock()
   275  	}
   276  }
   277  
   278  // tick .
   279  // pass ticker but not use t.ticker directly is to ignore race.
   280  func (t *sharedTicker) tick(ticker *time.Ticker, tt tickerType) {
   281  	var wg sync.WaitGroup
   282  	defer ticker.Stop()
   283  	for {
   284  		select {
   285  		case <-ticker.C:
   286  			t.Lock()
   287  			for c := range t.caches {
   288  				wg.Add(1)
   289  				go func(c *asyncCache) {
   290  					defer wg.Done()
   291  					if tt == expireTicker {
   292  						c.expire()
   293  					} else {
   294  						c.refresh()
   295  					}
   296  				}(c)
   297  			}
   298  			wg.Wait()
   299  			t.Unlock()
   300  		case stop := <-t.stopChan:
   301  			if stop {
   302  				return
   303  			}
   304  		}
   305  	}
   306  }
   307  
   308  func (c *asyncCache) expire() {
   309  	c.data.Range(func(key, value interface{}) bool {
   310  		k, ok := key.(string)
   311  		if !ok {
   312  			c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k))
   313  			c.data.Delete(key)
   314  			return true
   315  		}
   316  		e, ok := value.(*entry)
   317  		if !ok {
   318  			c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value))
   319  			c.data.Delete(key)
   320  			return true
   321  		}
   322  		if !atomic.CompareAndSwapInt32(&e.expire, 0, 1) {
   323  			if c.opt.DeleteHandler != nil {
   324  				go c.opt.DeleteHandler(k, value)
   325  			}
   326  			c.data.Delete(key)
   327  		}
   328  
   329  		return true
   330  	})
   331  }
   332  
   333  func (c *asyncCache) refresh() {
   334  	c.data.Range(func(key, value interface{}) bool {
   335  		k, ok := key.(string)
   336  		if !ok {
   337  			c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k))
   338  			c.data.Delete(key)
   339  			return true
   340  		}
   341  		e, ok := value.(*entry)
   342  		if !ok {
   343  			c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value))
   344  			c.data.Delete(key)
   345  			return true
   346  		}
   347  
   348  		newVal, err := c.opt.Fetcher(k)
   349  		if err != nil {
   350  			if c.opt.ErrorHandler != nil {
   351  				go c.opt.ErrorHandler(k, err)
   352  			}
   353  			if e.err.Load() != nil {
   354  				e.err.Store(err)
   355  			}
   356  			return true
   357  		}
   358  
   359  		if c.opt.IsSame != nil && !c.opt.IsSame(k, e.val.Load(), newVal) {
   360  			if c.opt.ChangeHandler != nil {
   361  				go c.opt.ChangeHandler(k, e.val.Load(), newVal)
   362  			}
   363  		}
   364  
   365  		e.Store(newVal, err)
   366  		return true
   367  	})
   368  }