codeberg.org/gruf/go-cache/v3@v3.5.7/ttl/ttl.go (about)

     1  package ttl
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  	_ "unsafe"
     7  
     8  	"codeberg.org/gruf/go-maps"
     9  )
    10  
    11  // Entry represents an item in the cache, with it's currently calculated Expiry time.
    12  type Entry[Key comparable, Value any] struct {
    13  	Key    Key
    14  	Value  Value
    15  	Expiry uint64
    16  }
    17  
    18  // Cache is the underlying TTLCache implementation, providing both the base Cache interface and unsafe access to underlying map to allow flexibility in building your own.
    19  type Cache[Key comparable, Value any] struct {
    20  	// TTL is the cache item TTL.
    21  	TTL time.Duration
    22  
    23  	// Evict is the hook that is called when an item is evicted from the cache.
    24  	Evict func(Key, Value)
    25  
    26  	// Invalid is the hook that is called when an item's data in the cache is invalidated, includes Add/Set.
    27  	Invalid func(Key, Value)
    28  
    29  	// Cache is the underlying hashmap used for this cache.
    30  	Cache maps.LRUMap[Key, *Entry[Key, Value]]
    31  
    32  	// stop is the eviction routine cancel func.
    33  	stop func()
    34  
    35  	// pool is a memory pool of entry objects.
    36  	pool []*Entry[Key, Value]
    37  
    38  	// Embedded mutex.
    39  	sync.Mutex
    40  }
    41  
    42  // New returns a new initialized Cache with given initial length, maximum capacity and item TTL.
    43  func New[K comparable, V any](len, cap int, ttl time.Duration) *Cache[K, V] {
    44  	c := new(Cache[K, V])
    45  	c.Init(len, cap, ttl)
    46  	return c
    47  }
    48  
    49  // Init will initialize this cache with given initial length, maximum capacity and item TTL.
    50  func (c *Cache[K, V]) Init(len, cap int, ttl time.Duration) {
    51  	if ttl <= 0 {
    52  		// Default duration
    53  		ttl = time.Second * 5
    54  	}
    55  	c.TTL = ttl
    56  	c.SetEvictionCallback(nil)
    57  	c.SetInvalidateCallback(nil)
    58  	c.Cache.Init(len, cap)
    59  }
    60  
    61  // Start: implements cache.Cache's Start().
    62  func (c *Cache[K, V]) Start(freq time.Duration) (ok bool) {
    63  	// Nothing to start
    64  	if freq <= 0 {
    65  		return false
    66  	}
    67  
    68  	// Safely start
    69  	c.Lock()
    70  
    71  	if ok = (c.stop == nil); ok {
    72  		// Not yet running, schedule us
    73  		c.stop = schedule(c.Sweep, freq)
    74  	}
    75  
    76  	// Done with lock
    77  	c.Unlock()
    78  
    79  	return
    80  }
    81  
    82  // Stop: implements cache.Cache's Stop().
    83  func (c *Cache[K, V]) Stop() (ok bool) {
    84  	// Safely stop
    85  	c.Lock()
    86  
    87  	if ok = (c.stop != nil); ok {
    88  		// We're running, cancel evicts
    89  		c.stop()
    90  		c.stop = nil
    91  	}
    92  
    93  	// Done with lock
    94  	c.Unlock()
    95  
    96  	return
    97  }
    98  
    99  // Sweep attempts to evict expired items (with callback!) from cache.
   100  func (c *Cache[K, V]) Sweep(_ time.Time) {
   101  	var (
   102  		// evicted key-values.
   103  		kvs []kv[K, V]
   104  
   105  		// hook func ptrs.
   106  		evict func(K, V)
   107  
   108  		// get current nanoseconds.
   109  		now = runtime_nanotime()
   110  	)
   111  
   112  	c.locked(func() {
   113  		if c.TTL <= 0 {
   114  			// sweep is
   115  			// disabled
   116  			return
   117  		}
   118  
   119  		// Sentinel value
   120  		after := -1
   121  
   122  		// The cache will be ordered by expiry date, we iterate until we reach the index of
   123  		// the youngest item that hsa expired, as all succeeding items will also be expired.
   124  		c.Cache.RangeIf(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) bool {
   125  			if now > item.Expiry {
   126  				after = i
   127  
   128  				// evict all older items
   129  				// than this (inclusive)
   130  				return false
   131  			}
   132  
   133  			// cont. loop.
   134  			return true
   135  		})
   136  
   137  		if after == -1 {
   138  			// No Truncation needed
   139  			return
   140  		}
   141  
   142  		// Set hook func ptr.
   143  		evict = c.Evict
   144  
   145  		// Truncate determined size.
   146  		sz := c.Cache.Len() - after
   147  		kvs = c.truncate(sz, evict)
   148  	})
   149  
   150  	if evict != nil {
   151  		for x := range kvs {
   152  			// Pass to eviction hook.
   153  			evict(kvs[x].K, kvs[x].V)
   154  		}
   155  	}
   156  }
   157  
   158  // SetEvictionCallback: implements cache.Cache's SetEvictionCallback().
   159  func (c *Cache[K, V]) SetEvictionCallback(hook func(K, V)) {
   160  	c.locked(func() {
   161  		c.Evict = hook
   162  	})
   163  }
   164  
   165  // SetInvalidateCallback: implements cache.Cache's SetInvalidateCallback().
   166  func (c *Cache[K, V]) SetInvalidateCallback(hook func(K, V)) {
   167  	c.locked(func() {
   168  		c.Invalid = hook
   169  	})
   170  }
   171  
   172  // SetTTL: implements cache.Cache's SetTTL().
   173  func (c *Cache[K, V]) SetTTL(ttl time.Duration, update bool) {
   174  	c.locked(func() {
   175  		// Set updated TTL
   176  		diff := ttl - c.TTL
   177  		c.TTL = ttl
   178  
   179  		if update {
   180  			// Update existing cache entries with new expiry time
   181  			c.Cache.Range(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) {
   182  				item.Expiry += uint64(diff)
   183  			})
   184  		}
   185  	})
   186  }
   187  
   188  // Get: implements cache.Cache's Get().
   189  func (c *Cache[K, V]) Get(key K) (V, bool) {
   190  	var (
   191  		// did exist in cache?
   192  		ok bool
   193  
   194  		// cached value.
   195  		v V
   196  	)
   197  
   198  	c.locked(func() {
   199  		var item *Entry[K, V]
   200  
   201  		// Check for item in cache
   202  		item, ok = c.Cache.Get(key)
   203  		if !ok {
   204  			return
   205  		}
   206  
   207  		// Update fetched's expiry
   208  		item.Expiry = c.expiry()
   209  
   210  		// Set value.
   211  		v = item.Value
   212  	})
   213  
   214  	return v, ok
   215  }
   216  
   217  // Add: implements cache.Cache's Add().
   218  func (c *Cache[K, V]) Add(key K, value V) bool {
   219  	var (
   220  		// did exist in cache?
   221  		ok bool
   222  
   223  		// was entry evicted?
   224  		ev bool
   225  
   226  		// evicted key values.
   227  		evcK K
   228  		evcV V
   229  
   230  		// hook func ptrs.
   231  		evict func(K, V)
   232  	)
   233  
   234  	c.locked(func() {
   235  		// Check if in cache.
   236  		ok = c.Cache.Has(key)
   237  		if ok {
   238  			return
   239  		}
   240  
   241  		// Alloc new entry.
   242  		new := c.alloc()
   243  		new.Expiry = c.expiry()
   244  		new.Key = key
   245  		new.Value = value
   246  
   247  		// Add new entry to cache and catched any evicted item.
   248  		c.Cache.SetWithHook(key, new, func(_ K, item *Entry[K, V]) {
   249  			evcK = item.Key
   250  			evcV = item.Value
   251  			ev = true
   252  			c.free(item)
   253  		})
   254  
   255  		// Set hook func ptr.
   256  		evict = c.Evict
   257  	})
   258  
   259  	if ev && evict != nil {
   260  		// Pass to eviction hook.
   261  		evict(evcK, evcV)
   262  	}
   263  
   264  	return !ok
   265  }
   266  
   267  // Set: implements cache.Cache's Set().
   268  func (c *Cache[K, V]) Set(key K, value V) {
   269  	var (
   270  		// did exist in cache?
   271  		ok bool
   272  
   273  		// was entry evicted?
   274  		ev bool
   275  
   276  		// old value.
   277  		oldV V
   278  
   279  		// evicted key values.
   280  		evcK K
   281  		evcV V
   282  
   283  		// hook func ptrs.
   284  		invalid func(K, V)
   285  		evict   func(K, V)
   286  	)
   287  
   288  	c.locked(func() {
   289  		var item *Entry[K, V]
   290  
   291  		// Check for item in cache
   292  		item, ok = c.Cache.Get(key)
   293  
   294  		if ok {
   295  			// Set old value.
   296  			oldV = item.Value
   297  
   298  			// Update the existing item.
   299  			item.Expiry = c.expiry()
   300  			item.Value = value
   301  		} else {
   302  			// Alloc new entry.
   303  			new := c.alloc()
   304  			new.Expiry = c.expiry()
   305  			new.Key = key
   306  			new.Value = value
   307  
   308  			// Add new entry to cache and catched any evicted item.
   309  			c.Cache.SetWithHook(key, new, func(_ K, item *Entry[K, V]) {
   310  				evcK = item.Key
   311  				evcV = item.Value
   312  				ev = true
   313  				c.free(item)
   314  			})
   315  		}
   316  
   317  		// Set hook func ptrs.
   318  		invalid = c.Invalid
   319  		evict = c.Evict
   320  	})
   321  
   322  	if ok && invalid != nil {
   323  		// Pass to invalidate hook.
   324  		invalid(key, oldV)
   325  	}
   326  
   327  	if ev && evict != nil {
   328  		// Pass to eviction hook.
   329  		evict(evcK, evcV)
   330  	}
   331  }
   332  
   333  // CAS: implements cache.Cache's CAS().
   334  func (c *Cache[K, V]) CAS(key K, old V, new V, cmp func(V, V) bool) bool {
   335  	var (
   336  		// did exist in cache?
   337  		ok bool
   338  
   339  		// swapped value.
   340  		oldV V
   341  
   342  		// hook func ptrs.
   343  		invalid func(K, V)
   344  	)
   345  
   346  	c.locked(func() {
   347  		var item *Entry[K, V]
   348  
   349  		// Check for item in cache
   350  		item, ok = c.Cache.Get(key)
   351  		if !ok {
   352  			return
   353  		}
   354  
   355  		// Perform the comparison
   356  		if !cmp(old, item.Value) {
   357  			return
   358  		}
   359  
   360  		// Set old value.
   361  		oldV = item.Value
   362  
   363  		// Update value + expiry.
   364  		item.Expiry = c.expiry()
   365  		item.Value = new
   366  
   367  		// Set hook func ptr.
   368  		invalid = c.Invalid
   369  	})
   370  
   371  	if ok && invalid != nil {
   372  		// Pass to invalidate hook.
   373  		invalid(key, oldV)
   374  	}
   375  
   376  	return ok
   377  }
   378  
   379  // Swap: implements cache.Cache's Swap().
   380  func (c *Cache[K, V]) Swap(key K, swp V) V {
   381  	var (
   382  		// did exist in cache?
   383  		ok bool
   384  
   385  		// swapped value.
   386  		oldV V
   387  
   388  		// hook func ptrs.
   389  		invalid func(K, V)
   390  	)
   391  
   392  	c.locked(func() {
   393  		var item *Entry[K, V]
   394  
   395  		// Check for item in cache
   396  		item, ok = c.Cache.Get(key)
   397  		if !ok {
   398  			return
   399  		}
   400  
   401  		// Set old value.
   402  		oldV = item.Value
   403  
   404  		// Update value + expiry.
   405  		item.Expiry = c.expiry()
   406  		item.Value = swp
   407  
   408  		// Set hook func ptr.
   409  		invalid = c.Invalid
   410  	})
   411  
   412  	if ok && invalid != nil {
   413  		// Pass to invalidate hook.
   414  		invalid(key, oldV)
   415  	}
   416  
   417  	return oldV
   418  }
   419  
   420  // Has: implements cache.Cache's Has().
   421  func (c *Cache[K, V]) Has(key K) (ok bool) {
   422  	c.locked(func() {
   423  		ok = c.Cache.Has(key)
   424  	})
   425  	return
   426  }
   427  
   428  // Invalidate: implements cache.Cache's Invalidate().
   429  func (c *Cache[K, V]) Invalidate(key K) (ok bool) {
   430  	var (
   431  		// old value.
   432  		oldV V
   433  
   434  		// hook func ptrs.
   435  		invalid func(K, V)
   436  	)
   437  
   438  	c.locked(func() {
   439  		var item *Entry[K, V]
   440  
   441  		// Check for item in cache
   442  		item, ok = c.Cache.Get(key)
   443  		if !ok {
   444  			return
   445  		}
   446  
   447  		// Set old value.
   448  		oldV = item.Value
   449  
   450  		// Remove from cache map
   451  		_ = c.Cache.Delete(key)
   452  
   453  		// Free entry
   454  		c.free(item)
   455  
   456  		// Set hook func ptrs.
   457  		invalid = c.Invalid
   458  	})
   459  
   460  	if ok && invalid != nil {
   461  		// Pass to invalidate hook.
   462  		invalid(key, oldV)
   463  	}
   464  
   465  	return
   466  }
   467  
   468  // InvalidateAll: implements cache.Cache's InvalidateAll().
   469  func (c *Cache[K, V]) InvalidateAll(keys ...K) (ok bool) {
   470  	var (
   471  		// invalidated kvs.
   472  		kvs []kv[K, V]
   473  
   474  		// hook func ptrs.
   475  		invalid func(K, V)
   476  	)
   477  
   478  	// Allocate a slice for invalidated.
   479  	kvs = make([]kv[K, V], 0, len(keys))
   480  
   481  	c.locked(func() {
   482  		for x := range keys {
   483  			var item *Entry[K, V]
   484  
   485  			// Check for item in cache
   486  			item, ok = c.Cache.Get(keys[x])
   487  			if !ok {
   488  				continue
   489  			}
   490  
   491  			// Append this old value to slice
   492  			kvs = append(kvs, kv[K, V]{
   493  				K: keys[x],
   494  				V: item.Value,
   495  			})
   496  
   497  			// Remove from cache map
   498  			_ = c.Cache.Delete(keys[x])
   499  
   500  			// Free entry
   501  			c.free(item)
   502  		}
   503  
   504  		// Set hook func ptrs.
   505  		invalid = c.Invalid
   506  	})
   507  
   508  	if invalid != nil {
   509  		for x := range kvs {
   510  			// Pass to invalidate hook.
   511  			invalid(kvs[x].K, kvs[x].V)
   512  		}
   513  	}
   514  
   515  	return
   516  }
   517  
   518  // Clear: implements cache.Cache's Clear().
   519  func (c *Cache[K, V]) Clear() {
   520  	var (
   521  		// deleted key-values.
   522  		kvs []kv[K, V]
   523  
   524  		// hook func ptrs.
   525  		invalid func(K, V)
   526  	)
   527  
   528  	c.locked(func() {
   529  		// Set hook func ptr.
   530  		invalid = c.Invalid
   531  
   532  		// Truncate the entire cache length.
   533  		kvs = c.truncate(c.Cache.Len(), invalid)
   534  	})
   535  
   536  	if invalid != nil {
   537  		for x := range kvs {
   538  			// Pass to invalidate hook.
   539  			invalid(kvs[x].K, kvs[x].V)
   540  		}
   541  	}
   542  }
   543  
   544  // Len: implements cache.Cache's Len().
   545  func (c *Cache[K, V]) Len() (l int) {
   546  	c.locked(func() { l = c.Cache.Len() })
   547  	return
   548  }
   549  
   550  // Cap: implements cache.Cache's Cap().
   551  func (c *Cache[K, V]) Cap() (l int) {
   552  	c.locked(func() { l = c.Cache.Cap() })
   553  	return
   554  }
   555  
   556  // locked performs given function within mutex lock (NOTE: UNLOCK IS NOT DEFERRED).
   557  func (c *Cache[K, V]) locked(fn func()) {
   558  	c.Lock()
   559  	fn()
   560  	c.Unlock()
   561  }
   562  
   563  // truncate will truncate the cache by given size, returning deleted items.
   564  func (c *Cache[K, V]) truncate(sz int, hook func(K, V)) []kv[K, V] {
   565  	if hook == nil {
   566  		// No hook to execute, simply free all truncated entries.
   567  		c.Cache.Truncate(sz, func(_ K, e *Entry[K, V]) { c.free(e) })
   568  		return nil
   569  	}
   570  
   571  	// Allocate a slice for deleted k-v pairs.
   572  	deleted := make([]kv[K, V], 0, sz)
   573  
   574  	c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) {
   575  		// Store key-value pair for later access.
   576  		deleted = append(deleted, kv[K, V]{
   577  			K: item.Key,
   578  			V: item.Value,
   579  		})
   580  
   581  		// Free entry.
   582  		c.free(item)
   583  	})
   584  
   585  	return deleted
   586  }
   587  
   588  // alloc will acquire cache entry from pool, or allocate new.
   589  func (c *Cache[K, V]) alloc() *Entry[K, V] {
   590  	if len(c.pool) == 0 {
   591  		return &Entry[K, V]{}
   592  	}
   593  	idx := len(c.pool) - 1
   594  	e := c.pool[idx]
   595  	c.pool = c.pool[:idx]
   596  	return e
   597  }
   598  
   599  // clone allocates a new Entry and copies all info from passed Entry.
   600  func (c *Cache[K, V]) clone(e *Entry[K, V]) *Entry[K, V] {
   601  	e2 := c.alloc()
   602  	e2.Key = e.Key
   603  	e2.Value = e.Value
   604  	e2.Expiry = e.Expiry
   605  	return e2
   606  }
   607  
   608  // free will reset entry fields and place back in pool.
   609  func (c *Cache[K, V]) free(e *Entry[K, V]) {
   610  	var (
   611  		zk K
   612  		zv V
   613  	)
   614  	e.Expiry = 0
   615  	e.Key = zk
   616  	e.Value = zv
   617  	c.pool = append(c.pool, e)
   618  }
   619  
   620  //go:linkname runtime_nanotime runtime.nanotime
   621  func runtime_nanotime() uint64
   622  
   623  // expiry returns an the next expiry time to use for an entry,
   624  // which is equivalent to time.Now().Add(ttl), or zero if disabled.
   625  func (c *Cache[K, V]) expiry() uint64 {
   626  	if ttl := c.TTL; ttl > 0 {
   627  		return runtime_nanotime() +
   628  			uint64(c.TTL)
   629  	}
   630  	return 0
   631  }
   632  
   633  type kv[K comparable, V any] struct {
   634  	K K
   635  	V V
   636  }