github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/msp/cache/second_chance.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package cache
     8  
     9  import (
    10  	"sync"
    11  	"sync/atomic"
    12  )
    13  
    14  // This package implements Second-Chance Algorithm, an approximate LRU algorithms.
    15  // https://www.cs.jhu.edu/~yairamir/cs418/os6/tsld023.htm
    16  
    17  // secondChanceCache holds key-value items with a limited size.
    18  // When the number cached items exceeds the limit, victims are selected based on the
    19  // Second-Chance Algorithm and get purged
    20  type secondChanceCache struct {
    21  	// manages mapping between keys and items
    22  	table map[string]*cacheItem
    23  
    24  	// holds a list of cached items.
    25  	items []*cacheItem
    26  
    27  	// indicates the next candidate of a victim in the items list
    28  	position int
    29  
    30  	// read lock for get, and write lock for add
    31  	rwlock sync.RWMutex
    32  }
    33  
    34  type cacheItem struct {
    35  	key   string
    36  	value interface{}
    37  	// set to 1 when get() is called. set to 0 when victim scan
    38  	referenced int32
    39  }
    40  
    41  func newSecondChanceCache(cacheSize int) *secondChanceCache {
    42  	var cache secondChanceCache
    43  	cache.position = 0
    44  	cache.items = make([]*cacheItem, cacheSize)
    45  	cache.table = make(map[string]*cacheItem)
    46  
    47  	return &cache
    48  }
    49  
    50  func (cache *secondChanceCache) len() int {
    51  	cache.rwlock.RLock()
    52  	defer cache.rwlock.RUnlock()
    53  
    54  	return len(cache.table)
    55  }
    56  
    57  func (cache *secondChanceCache) get(key string) (interface{}, bool) {
    58  	cache.rwlock.RLock()
    59  	defer cache.rwlock.RUnlock()
    60  
    61  	item, ok := cache.table[key]
    62  	if !ok {
    63  		return nil, false
    64  	}
    65  
    66  	// referenced bit is set to true to indicate that this item is recently accessed.
    67  	atomic.StoreInt32(&item.referenced, 1)
    68  
    69  	return item.value, true
    70  }
    71  
    72  func (cache *secondChanceCache) add(key string, value interface{}) {
    73  	cache.rwlock.Lock()
    74  	defer cache.rwlock.Unlock()
    75  
    76  	if old, ok := cache.table[key]; ok {
    77  		old.value = value
    78  		atomic.StoreInt32(&old.referenced, 1)
    79  		return
    80  	}
    81  
    82  	var item cacheItem
    83  	item.key = key
    84  	item.value = value
    85  
    86  	size := len(cache.items)
    87  	num := len(cache.table)
    88  	if num < size {
    89  		// cache is not full, so just store the new item at the end of the list
    90  		cache.table[key] = &item
    91  		cache.items[num] = &item
    92  		return
    93  	}
    94  
    95  	// starts victim scan since cache is full
    96  	for {
    97  		// checks whether this item is recently accessed or not
    98  		victim := cache.items[cache.position]
    99  		if atomic.LoadInt32(&victim.referenced) == 0 {
   100  			// a victim is found. delete it, and store the new item here.
   101  			delete(cache.table, victim.key)
   102  			cache.table[key] = &item
   103  			cache.items[cache.position] = &item
   104  			cache.position = (cache.position + 1) % size
   105  			return
   106  		}
   107  
   108  		// referenced bit is set to false so that this item will be get purged
   109  		// unless it is accessed until a next victim scan
   110  		atomic.StoreInt32(&victim.referenced, 0)
   111  		cache.position = (cache.position + 1) % size
   112  	}
   113  }