code.gitea.io/gitea@v1.19.3/modules/cache/cache_twoqueue.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package cache
     5  
     6  import (
     7  	"strconv"
     8  	"sync"
     9  	"time"
    10  
    11  	"code.gitea.io/gitea/modules/json"
    12  
    13  	mc "gitea.com/go-chi/cache"
    14  	lru "github.com/hashicorp/golang-lru"
    15  )
    16  
    17  // TwoQueueCache represents a LRU 2Q cache adapter implementation
    18  type TwoQueueCache struct {
    19  	lock     sync.Mutex
    20  	cache    *lru.TwoQueueCache
    21  	interval int
    22  }
    23  
    24  // TwoQueueCacheConfig describes the configuration for TwoQueueCache
    25  type TwoQueueCacheConfig struct {
    26  	Size        int     `ini:"SIZE" json:"size"`
    27  	RecentRatio float64 `ini:"RECENT_RATIO" json:"recent_ratio"`
    28  	GhostRatio  float64 `ini:"GHOST_RATIO" json:"ghost_ratio"`
    29  }
    30  
    31  // MemoryItem represents a memory cache item.
    32  type MemoryItem struct {
    33  	Val     interface{}
    34  	Created int64
    35  	Timeout int64
    36  }
    37  
    38  func (item *MemoryItem) hasExpired() bool {
    39  	return item.Timeout > 0 &&
    40  		(time.Now().Unix()-item.Created) >= item.Timeout
    41  }
    42  
    43  var _ mc.Cache = &TwoQueueCache{}
    44  
    45  // Put puts value into cache with key and expire time.
    46  func (c *TwoQueueCache) Put(key string, val interface{}, timeout int64) error {
    47  	item := &MemoryItem{
    48  		Val:     val,
    49  		Created: time.Now().Unix(),
    50  		Timeout: timeout,
    51  	}
    52  	c.lock.Lock()
    53  	defer c.lock.Unlock()
    54  	c.cache.Add(key, item)
    55  	return nil
    56  }
    57  
    58  // Get gets cached value by given key.
    59  func (c *TwoQueueCache) Get(key string) interface{} {
    60  	c.lock.Lock()
    61  	defer c.lock.Unlock()
    62  	cached, ok := c.cache.Get(key)
    63  	if !ok {
    64  		return nil
    65  	}
    66  	item, ok := cached.(*MemoryItem)
    67  
    68  	if !ok || item.hasExpired() {
    69  		c.cache.Remove(key)
    70  		return nil
    71  	}
    72  
    73  	return item.Val
    74  }
    75  
    76  // Delete deletes cached value by given key.
    77  func (c *TwoQueueCache) Delete(key string) error {
    78  	c.lock.Lock()
    79  	defer c.lock.Unlock()
    80  	c.cache.Remove(key)
    81  	return nil
    82  }
    83  
    84  // Incr increases cached int-type value by given key as a counter.
    85  func (c *TwoQueueCache) Incr(key string) error {
    86  	c.lock.Lock()
    87  	defer c.lock.Unlock()
    88  	cached, ok := c.cache.Get(key)
    89  	if !ok {
    90  		return nil
    91  	}
    92  	item, ok := cached.(*MemoryItem)
    93  
    94  	if !ok || item.hasExpired() {
    95  		c.cache.Remove(key)
    96  		return nil
    97  	}
    98  
    99  	var err error
   100  	item.Val, err = mc.Incr(item.Val)
   101  	return err
   102  }
   103  
   104  // Decr decreases cached int-type value by given key as a counter.
   105  func (c *TwoQueueCache) Decr(key string) error {
   106  	c.lock.Lock()
   107  	defer c.lock.Unlock()
   108  	cached, ok := c.cache.Get(key)
   109  	if !ok {
   110  		return nil
   111  	}
   112  	item, ok := cached.(*MemoryItem)
   113  
   114  	if !ok || item.hasExpired() {
   115  		c.cache.Remove(key)
   116  		return nil
   117  	}
   118  
   119  	var err error
   120  	item.Val, err = mc.Decr(item.Val)
   121  	return err
   122  }
   123  
   124  // IsExist returns true if cached value exists.
   125  func (c *TwoQueueCache) IsExist(key string) bool {
   126  	c.lock.Lock()
   127  	defer c.lock.Unlock()
   128  	cached, ok := c.cache.Peek(key)
   129  	if !ok {
   130  		return false
   131  	}
   132  	item, ok := cached.(*MemoryItem)
   133  	if !ok || item.hasExpired() {
   134  		c.cache.Remove(key)
   135  		return false
   136  	}
   137  
   138  	return true
   139  }
   140  
   141  // Flush deletes all cached data.
   142  func (c *TwoQueueCache) Flush() error {
   143  	c.lock.Lock()
   144  	defer c.lock.Unlock()
   145  	c.cache.Purge()
   146  	return nil
   147  }
   148  
   149  func (c *TwoQueueCache) checkAndInvalidate(key interface{}) {
   150  	c.lock.Lock()
   151  	defer c.lock.Unlock()
   152  	cached, ok := c.cache.Peek(key)
   153  	if !ok {
   154  		return
   155  	}
   156  	item, ok := cached.(*MemoryItem)
   157  	if !ok || item.hasExpired() {
   158  		c.cache.Remove(item)
   159  	}
   160  }
   161  
   162  func (c *TwoQueueCache) startGC() {
   163  	if c.interval < 0 {
   164  		return
   165  	}
   166  	for _, key := range c.cache.Keys() {
   167  		c.checkAndInvalidate(key)
   168  	}
   169  	time.AfterFunc(time.Duration(c.interval)*time.Second, c.startGC)
   170  }
   171  
   172  // StartAndGC starts GC routine based on config string settings.
   173  func (c *TwoQueueCache) StartAndGC(opts mc.Options) error {
   174  	var err error
   175  	size := 50000
   176  	if opts.AdapterConfig != "" {
   177  		size, err = strconv.Atoi(opts.AdapterConfig)
   178  	}
   179  	if err != nil {
   180  		if !json.Valid([]byte(opts.AdapterConfig)) {
   181  			return err
   182  		}
   183  
   184  		cfg := &TwoQueueCacheConfig{
   185  			Size:        50000,
   186  			RecentRatio: lru.Default2QRecentRatio,
   187  			GhostRatio:  lru.Default2QGhostEntries,
   188  		}
   189  		_ = json.Unmarshal([]byte(opts.AdapterConfig), cfg)
   190  		c.cache, err = lru.New2QParams(cfg.Size, cfg.RecentRatio, cfg.GhostRatio)
   191  	} else {
   192  		c.cache, err = lru.New2Q(size)
   193  	}
   194  	c.interval = opts.Interval
   195  	if c.interval > 0 {
   196  		go c.startGC()
   197  	}
   198  	return err
   199  }
   200  
   201  // Ping tests if the cache is alive.
   202  func (c *TwoQueueCache) Ping() error {
   203  	return mc.GenericPing(c)
   204  }
   205  
   206  func init() {
   207  	mc.Register("twoqueue", &TwoQueueCache{})
   208  }