github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/commons/caches/lru/lru.go (about)

     1  /*
     2   * Copyright 2023 Wang Min Xiang
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * 	http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package lru
    19  
    20  import (
    21  	"sync"
    22  	"time"
    23  )
    24  
    25  type EvictCallback[K comparable, V any] func(key K, value V)
    26  
    27  type LRU[K comparable, V any] struct {
    28  	size              int
    29  	evictList         *List[K, V]
    30  	items             map[K]*Entry[K, V]
    31  	onEvict           EvictCallback[K, V]
    32  	mu                sync.Mutex
    33  	ttl               time.Duration
    34  	done              chan struct{}
    35  	buckets           []bucket[K, V]
    36  	nextCleanupBucket uint8
    37  }
    38  
    39  type bucket[K comparable, V any] struct {
    40  	entries     map[K]*Entry[K, V]
    41  	newestEntry time.Time
    42  }
    43  
    44  const numBuckets = 128
    45  
    46  func NewWithExpire[K comparable, V any](size int, ttl time.Duration, onEvict EvictCallback[K, V]) *LRU[K, V] {
    47  	if size < 0 {
    48  		size = 0
    49  	}
    50  	res := LRU[K, V]{
    51  		ttl:       ttl,
    52  		size:      size,
    53  		evictList: NewList[K, V](),
    54  		items:     make(map[K]*Entry[K, V]),
    55  		onEvict:   onEvict,
    56  		done:      make(chan struct{}),
    57  	}
    58  	res.buckets = make([]bucket[K, V], numBuckets)
    59  	for i := 0; i < numBuckets; i++ {
    60  		res.buckets[i] = bucket[K, V]{entries: make(map[K]*Entry[K, V])}
    61  	}
    62  
    63  	if res.ttl > 0 {
    64  		go func(done <-chan struct{}) {
    65  			ticker := time.NewTicker(res.ttl / numBuckets)
    66  			defer ticker.Stop()
    67  			for {
    68  				select {
    69  				case <-done:
    70  					return
    71  				case <-ticker.C:
    72  					res.deleteExpired()
    73  				}
    74  			}
    75  		}(res.done)
    76  	}
    77  	return &res
    78  }
    79  
    80  func New[K comparable, V any](size int, onEvict EvictCallback[K, V]) *LRU[K, V] {
    81  	return NewWithExpire[K, V](size, 0, onEvict)
    82  }
    83  
    84  func (c *LRU[K, V]) Purge() {
    85  	c.mu.Lock()
    86  	defer c.mu.Unlock()
    87  	for k, v := range c.items {
    88  		if c.onEvict != nil {
    89  			c.onEvict(k, v.Value)
    90  		}
    91  		delete(c.items, k)
    92  	}
    93  	for _, b := range c.buckets {
    94  		for _, ent := range b.entries {
    95  			delete(b.entries, ent.Key)
    96  		}
    97  	}
    98  	c.evictList.Init()
    99  }
   100  
   101  func (c *LRU[K, V]) Add(key K, value V) (evicted bool) {
   102  	c.mu.Lock()
   103  	defer c.mu.Unlock()
   104  
   105  	if ent, ok := c.items[key]; ok {
   106  		c.evictList.MoveToFront(ent)
   107  		c.removeFromBucket(ent)
   108  		ent.Value = value
   109  		if c.ttl > 0 {
   110  			ent.ExpiresAt = time.Now().Add(c.ttl)
   111  		}
   112  		c.addToBucket(ent)
   113  		return false
   114  	}
   115  	expireAt := time.Time{}
   116  	if c.ttl > 0 {
   117  		expireAt = time.Now().Add(c.ttl)
   118  	}
   119  
   120  	ent := c.evictList.PushFrontExpirable(key, value, expireAt)
   121  	c.items[key] = ent
   122  	c.addToBucket(ent)
   123  
   124  	evict := c.size > 0 && c.evictList.Length() > c.size
   125  	if evict {
   126  		c.removeOldest()
   127  	}
   128  	return evict
   129  }
   130  
   131  func (c *LRU[K, V]) Get(key K) (value V, ok bool) {
   132  	c.mu.Lock()
   133  	defer c.mu.Unlock()
   134  	var ent *Entry[K, V]
   135  	if ent, ok = c.items[key]; ok {
   136  		if ent.ExpiresAt.IsZero() {
   137  			c.evictList.MoveToFront(ent)
   138  			return ent.Value, true
   139  		}
   140  		if time.Now().After(ent.ExpiresAt) {
   141  			return value, false
   142  		}
   143  		c.evictList.MoveToFront(ent)
   144  		return ent.Value, true
   145  	}
   146  	return
   147  }
   148  
   149  func (c *LRU[K, V]) Contains(key K) (ok bool) {
   150  	c.mu.Lock()
   151  	defer c.mu.Unlock()
   152  	_, ok = c.items[key]
   153  	return ok
   154  }
   155  
   156  func (c *LRU[K, V]) Peek(key K) (value V, ok bool) {
   157  	c.mu.Lock()
   158  	defer c.mu.Unlock()
   159  	var ent *Entry[K, V]
   160  	if ent, ok = c.items[key]; ok {
   161  		if ent.ExpiresAt.IsZero() {
   162  			c.evictList.MoveToFront(ent)
   163  			return ent.Value, true
   164  		}
   165  		if time.Now().After(ent.ExpiresAt) {
   166  			return value, false
   167  		}
   168  		return ent.Value, true
   169  	}
   170  	return
   171  }
   172  
   173  func (c *LRU[K, V]) Remove(key K) bool {
   174  	c.mu.Lock()
   175  	defer c.mu.Unlock()
   176  	if ent, ok := c.items[key]; ok {
   177  		c.removeElement(ent)
   178  		return true
   179  	}
   180  	return false
   181  }
   182  
   183  func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
   184  	c.mu.Lock()
   185  	defer c.mu.Unlock()
   186  	if ent := c.evictList.Back(); ent != nil {
   187  		c.removeElement(ent)
   188  		return ent.Key, ent.Value, true
   189  	}
   190  	return
   191  }
   192  
   193  func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) {
   194  	c.mu.Lock()
   195  	defer c.mu.Unlock()
   196  	if ent := c.evictList.Back(); ent != nil {
   197  		return ent.Key, ent.Value, true
   198  	}
   199  	return
   200  }
   201  
   202  func (c *LRU[K, V]) Keys() []K {
   203  	c.mu.Lock()
   204  	defer c.mu.Unlock()
   205  	keys := make([]K, 0, len(c.items))
   206  	now := time.Now()
   207  	for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
   208  		if !ent.ExpiresAt.IsZero() && now.After(ent.ExpiresAt) {
   209  			continue
   210  		}
   211  		keys = append(keys, ent.Key)
   212  	}
   213  	return keys
   214  }
   215  
   216  func (c *LRU[K, V]) Values() []V {
   217  	c.mu.Lock()
   218  	defer c.mu.Unlock()
   219  	values := make([]V, len(c.items))
   220  	i := 0
   221  	now := time.Now()
   222  	for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
   223  		if !ent.ExpiresAt.IsZero() && now.After(ent.ExpiresAt) {
   224  			continue
   225  		}
   226  		values[i] = ent.Value
   227  		i++
   228  	}
   229  	return values
   230  }
   231  
   232  func (c *LRU[K, V]) Len() int {
   233  	c.mu.Lock()
   234  	defer c.mu.Unlock()
   235  	return c.evictList.Length()
   236  }
   237  
   238  func (c *LRU[K, V]) Resize(size int) (evicted int) {
   239  	c.mu.Lock()
   240  	defer c.mu.Unlock()
   241  	if size <= 0 {
   242  		c.size = 0
   243  		return 0
   244  	}
   245  	diff := c.evictList.Length() - size
   246  	if diff < 0 {
   247  		diff = 0
   248  	}
   249  	for i := 0; i < diff; i++ {
   250  		c.removeOldest()
   251  	}
   252  	c.size = size
   253  	return diff
   254  }
   255  
   256  func (c *LRU[K, V]) removeOldest() {
   257  	if ent := c.evictList.Back(); ent != nil {
   258  		c.removeElement(ent)
   259  	}
   260  }
   261  
   262  func (c *LRU[K, V]) removeElement(e *Entry[K, V]) {
   263  	c.evictList.Remove(e)
   264  	delete(c.items, e.Key)
   265  	c.removeFromBucket(e)
   266  	if c.onEvict != nil {
   267  		c.onEvict(e.Key, e.Value)
   268  	}
   269  }
   270  
   271  func (c *LRU[K, V]) deleteExpired() {
   272  	if c.ttl < 1 {
   273  		return
   274  	}
   275  	c.mu.Lock()
   276  	bucketIdx := c.nextCleanupBucket
   277  	timeToExpire := time.Until(c.buckets[bucketIdx].newestEntry)
   278  	if timeToExpire > 0 {
   279  		c.mu.Unlock()
   280  		time.Sleep(timeToExpire)
   281  		c.mu.Lock()
   282  	}
   283  	for _, ent := range c.buckets[bucketIdx].entries {
   284  		c.removeElement(ent)
   285  	}
   286  	c.nextCleanupBucket = (c.nextCleanupBucket + 1) % numBuckets
   287  	c.mu.Unlock()
   288  }
   289  
   290  func (c *LRU[K, V]) addToBucket(e *Entry[K, V]) {
   291  	bucketId := (numBuckets + c.nextCleanupBucket - 1) % numBuckets
   292  	e.ExpireBucket = bucketId
   293  	c.buckets[bucketId].entries[e.Key] = e
   294  	if e.ExpiresAt.IsZero() || c.buckets[bucketId].newestEntry.Before(e.ExpiresAt) {
   295  		c.buckets[bucketId].newestEntry = e.ExpiresAt
   296  	}
   297  }
   298  
   299  func (c *LRU[K, V]) removeFromBucket(e *Entry[K, V]) {
   300  	delete(c.buckets[e.ExpireBucket].entries, e.Key)
   301  }