114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package cache
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// This package implements Second-Chance Algorithm, an approximate LRU algorithms.
|
|
// https://www.cs.jhu.edu/~yairamir/cs418/os6/tsld023.htm
|
|
|
|
// secondChanceCache holds key-value items with a limited size.
|
|
// When the number cached items exceeds the limit, victims are selected based on the
|
|
// Second-Chance Algorithm and get purged
|
|
type secondChanceCache struct {
|
|
// manages mapping between keys and items
|
|
table map[string]*cacheItem
|
|
|
|
// holds a list of cached items.
|
|
items []*cacheItem
|
|
|
|
// indicates the next candidate of a victim in the items list
|
|
position int
|
|
|
|
// read lock for get, and write lock for add
|
|
rwlock sync.RWMutex
|
|
}
|
|
|
|
type cacheItem struct {
|
|
key string
|
|
value interface{}
|
|
// set to 1 when get() is called. set to 0 when victim scan
|
|
referenced int32
|
|
}
|
|
|
|
func newSecondChanceCache(cacheSize int) *secondChanceCache {
|
|
var cache secondChanceCache
|
|
cache.position = 0
|
|
cache.items = make([]*cacheItem, cacheSize)
|
|
cache.table = make(map[string]*cacheItem)
|
|
|
|
return &cache
|
|
}
|
|
|
|
func (cache *secondChanceCache) len() int {
|
|
cache.rwlock.RLock()
|
|
defer cache.rwlock.RUnlock()
|
|
|
|
return len(cache.table)
|
|
}
|
|
|
|
func (cache *secondChanceCache) get(key string) (interface{}, bool) {
|
|
cache.rwlock.RLock()
|
|
defer cache.rwlock.RUnlock()
|
|
|
|
item, ok := cache.table[key]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
// referenced bit is set to true to indicate that this item is recently accessed.
|
|
atomic.StoreInt32(&item.referenced, 1)
|
|
|
|
return item.value, true
|
|
}
|
|
|
|
func (cache *secondChanceCache) add(key string, value interface{}) {
|
|
cache.rwlock.Lock()
|
|
defer cache.rwlock.Unlock()
|
|
|
|
if old, ok := cache.table[key]; ok {
|
|
old.value = value
|
|
atomic.StoreInt32(&old.referenced, 1)
|
|
return
|
|
}
|
|
|
|
var item cacheItem
|
|
item.key = key
|
|
item.value = value
|
|
|
|
size := len(cache.items)
|
|
num := len(cache.table)
|
|
if num < size {
|
|
// cache is not full, so just store the new item at the end of the list
|
|
cache.table[key] = &item
|
|
cache.items[num] = &item
|
|
return
|
|
}
|
|
|
|
// starts victim scan since cache is full
|
|
for {
|
|
// checks whether this item is recently accessed or not
|
|
victim := cache.items[cache.position]
|
|
if atomic.LoadInt32(&victim.referenced) == 0 {
|
|
// a victim is found. delete it, and store the new item here.
|
|
delete(cache.table, victim.key)
|
|
cache.table[key] = &item
|
|
cache.items[cache.position] = &item
|
|
cache.position = (cache.position + 1) % size
|
|
return
|
|
}
|
|
|
|
// referenced bit is set to false so that this item will be get purged
|
|
// unless it is accessed until a next victim scan
|
|
atomic.StoreInt32(&victim.referenced, 0)
|
|
cache.position = (cache.position + 1) % size
|
|
}
|
|
}
|