azure-storage-fuse/component/file_cache/lfu_policy.go

500 строки
12 KiB
Go

/*
_____ _____ _____ ____ ______ _____ ------
| | | | | | | | | | | | |
| | | | | | | | | | | | |
| --- | | | | |-----| |---- | | |-----| |----- ------
| | | | | | | | | | | | |
| ____| |_____ | ____| | ____| | |_____| _____| |_____ |_____
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright © 2020-2022 Microsoft Corporation. All rights reserved.
Author : <blobfusedev@microsoft.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
*/
package file_cache
import (
"blobfuse2/common/log"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
)
type lfuPolicy struct {
sync.Mutex
cachePolicyConfig
list *lfuList
removeFiles chan string
closeChan chan int
}
var _ cachePolicy = &lfuPolicy{}
func (l *lfuPolicy) StartPolicy() error {
log.Trace("lfuPolicy::StartPolicy")
go l.clearCache()
return nil
}
func (l *lfuPolicy) ShutdownPolicy() error {
log.Trace("lfuPolicy::ShutdownPolicy")
l.closeChan <- 1
return nil
}
func (l *lfuPolicy) UpdateConfig(config cachePolicyConfig) error {
log.Trace("lfuPolicy::UpdateConfig")
l.maxSizeMB = config.maxSizeMB
l.highThreshold = config.highThreshold
l.lowThreshold = config.lowThreshold
l.maxEviction = config.maxEviction
l.list.maxSizeMB = config.maxSizeMB
l.list.upperThresh = config.highThreshold
l.list.lowerThresh = config.lowThreshold
l.list.cacheTimeout = config.cacheTimeout
l.policyTrace = config.policyTrace
return nil
}
func (l *lfuPolicy) CacheValid(name string) error {
log.Trace("lfuPolicy::CacheValid : %s", name)
l.list.Lock()
defer l.list.Unlock()
l.list.put(name)
return nil
}
func (l *lfuPolicy) CacheInvalidate(name string) error {
log.Trace("lfuPolicy::CacheInvalidate : %s", name)
if l.cacheTimeout == 0 {
return l.CachePurge(name)
}
return nil
}
func (l *lfuPolicy) CachePurge(name string) error {
log.Trace("lfuPolicy::CachePurge : %s", name)
l.list.Lock()
defer l.list.Unlock()
l.list.delete(name)
l.removeFiles <- name
return nil
}
func (l *lfuPolicy) IsCached(name string) bool {
log.Trace("lfuPolicy::IsCached : %s", name)
l.list.Lock()
defer l.list.Unlock()
val := l.list.get(name)
if val != nil {
log.Debug("lfuPolicy::IsCached : %s found", name)
return true
} else {
log.Debug("lfuPolicy::IsCached : %s not found", name)
return false
}
}
func (l *lfuPolicy) Name() string {
return "lfu"
}
func (l *lfuPolicy) clearItemFromCache(path string) {
azPath := strings.TrimPrefix(path, l.tmpPath)
if azPath[0] == '/' {
azPath = azPath[1:]
}
l.fileLocks.Lock(azPath)
defer l.fileLocks.Unlock(azPath)
f, err := os.OpenFile(path, os.O_RDWR, os.FileMode(0666))
if err != nil {
if !os.IsNotExist(err) {
log.Err("lfuPolicy::clearCache : error opening cached file [%s]", err)
}
} else {
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err == syscall.EWOULDBLOCK {
log.Err("lfuPolicy::clearCache : failed to acquire ex lock [%s]", path)
f.Close()
go rethrowOnUnblock(f, path, l.removeFiles)
} else {
os.Remove(path)
syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
f.Close()
log.Debug("lfuPolicy::clearCache : File %s deleted successfully", path)
dirPath := filepath.Dir(path)
if dirPath != l.tmpPath {
os.Remove(filepath.Dir(path))
}
}
}
}
func (l *lfuPolicy) clearCache() {
log.Trace("lfuPolicy::clearCache")
for {
select {
case path := <-l.removeFiles:
l.clearItemFromCache(path)
case <-l.closeChan:
return
}
}
}
func rethrowOnUnblock(f *os.File, path string, throwChan chan string) {
log.Trace("lfuPolicy::rethrowOnUnblock : %s", path)
log.Debug("lfuPolicy::rethrowOnUnblock : ex lock acquired [%s]", path)
throwChan <- path
}
func NewLFUPolicy(cfg cachePolicyConfig) cachePolicy {
pol := &lfuPolicy{
cachePolicyConfig: cfg,
removeFiles: make(chan string, 10),
closeChan: make(chan int, 10),
}
pol.list = newLFUList(cfg.maxSizeMB, cfg.lowThreshold, cfg.highThreshold, pol.removeFiles, cfg.tmpPath, cfg.cacheTimeout)
return pol
}
//Double DoublyLinkedList Implementation for O(1) lfu
type dataNode struct {
key string
frequency uint64
next *dataNode
prev *dataNode
timer *time.Timer
}
func newDataNode(key string) *dataNode {
return &dataNode{
key: key,
frequency: 1,
}
}
type dataNodeLinkedList struct {
size uint64
first *dataNode
last *dataNode
}
func (dl *dataNodeLinkedList) pop() *dataNode {
if dl.size == 0 {
return nil
}
return dl.remove(dl.first)
}
func (dl *dataNodeLinkedList) remove(node *dataNode) *dataNode {
if dl.size == 0 {
return nil
}
if dl.first == dl.last {
dl.first = nil
dl.last = nil
} else if dl.first == node {
temp := dl.first
dl.first = temp.next
temp.next = nil
dl.first.prev = nil
} else if dl.last == node {
temp := dl.last
dl.last = temp.prev
temp.prev = nil
dl.last.next = nil
} else {
nextNode := node.next
prevNode := node.prev
prevNode.next = nextNode
nextNode.prev = prevNode
node.next = nil
node.prev = nil
}
dl.size--
return node
}
func (dl *dataNodeLinkedList) push(node *dataNode) {
if dl.first == nil {
dl.first = node
dl.last = node
} else {
temp := dl.last
temp.next = node
node.prev = temp
dl.last = node
}
dl.size++
}
func newDataNodeLinkedList() *dataNodeLinkedList {
return &dataNodeLinkedList{}
}
type frequencyNode struct {
list *dataNodeLinkedList
next *frequencyNode
prev *frequencyNode
frequency uint64
}
func (fn *frequencyNode) pop() *dataNode {
return fn.list.pop()
}
func (fn *frequencyNode) remove(dn *dataNode) *dataNode {
return fn.list.remove(dn)
}
func (fn *frequencyNode) push(dn *dataNode) {
fn.list.push(dn)
}
func newFrequencyNode(freq uint64) *frequencyNode {
return &frequencyNode{
list: newDataNodeLinkedList(),
frequency: freq,
}
}
type lfuList struct {
sync.Mutex
first *frequencyNode
last *frequencyNode
dataNodeMap map[string]*dataNode
freqNodeMap map[uint64]*frequencyNode
size uint64
maxSizeMB float64
lowerThresh float64
upperThresh float64
deleteFiles chan string
cachePath string
cacheAge uint64
cacheTimeout uint32
}
func (list *lfuList) deleteFrequency(freq uint64) {
freqNode := list.freqNodeMap[freq]
if list.first == list.last {
list.first = nil
list.last = nil
} else if list.first == freqNode {
list.first = list.first.next
list.first.prev = nil
freqNode.next = nil
} else if list.last == freqNode {
list.last = list.last.prev
list.last.next = nil
freqNode.prev = nil
} else {
nextNode := freqNode.next
prevNode := freqNode.prev
nextNode.prev = prevNode
prevNode.next = nextNode
freqNode.next = nil
freqNode.prev = nil
}
list.size--
delete(list.freqNodeMap, freq)
}
func (list *lfuList) addFrequency(freq uint64, freqNode *frequencyNode, prevFreqNode *frequencyNode) {
if list.first == nil && list.last == nil {
list.first = freqNode
list.last = freqNode
list.freqNodeMap[freq] = freqNode
list.size++
return
}
if prevFreqNode == nil {
prevFreqNode = list.first
}
for prevFreqNode.next != nil && freq > prevFreqNode.next.frequency {
prevFreqNode = prevFreqNode.next
}
if prevFreqNode == nil {
freqNode.next = list.first
list.first.prev = freqNode
list.first = freqNode
} else if prevFreqNode == list.last {
prevFreqNode.next = freqNode
freqNode.prev = prevFreqNode
list.last = freqNode
} else {
nextNode := prevFreqNode.next
freqNode.next = nextNode
nextNode.prev = freqNode
prevFreqNode.next = freqNode
freqNode.prev = prevFreqNode
}
list.freqNodeMap[freq] = freqNode
list.size++
}
func (list *lfuList) promote(dataNode *dataNode) {
prevFreqNode := list.freqNodeMap[dataNode.frequency]
prevFreqNode.remove(dataNode)
dataNode.frequency += 1 + list.cacheAge
if newFreqNode, ok := list.freqNodeMap[dataNode.frequency]; ok {
newFreqNode.push(dataNode)
} else {
newFreqNode := newFrequencyNode(dataNode.frequency)
list.addFrequency(dataNode.frequency, newFreqNode, prevFreqNode)
list.freqNodeMap[dataNode.frequency] = newFreqNode
newFreqNode.push(dataNode)
}
if prevFreqNode.list.size == 0 {
list.deleteFrequency(prevFreqNode.frequency)
list.size--
}
}
func (list *lfuList) get(key string) *dataNode {
if node, ok := list.dataNodeMap[key]; ok {
if list.cacheTimeout > 0 {
node.timer.Stop()
}
list.promote(node)
list.setTimerIfValid(node)
return node
} else {
return nil
}
}
//Requires Lock()
func (list *lfuList) put(key string) {
if node, ok := list.dataNodeMap[key]; ok {
if list.cacheTimeout > 0 {
node.timer.Stop()
}
list.promote(node)
list.setTimerIfValid(node)
} else {
if usage := getUsagePercentage(list.cachePath, list.maxSizeMB); usage > list.upperThresh {
for usage > list.lowerThresh && list.first != nil {
toDeletePath := list.first.list.first.key
list.first.pop()
delete(list.dataNodeMap, toDeletePath)
if list.first.list.size == 0 {
list.deleteFrequency(list.first.frequency)
list.size--
usage = getUsagePercentage(list.cachePath, list.maxSizeMB)
}
list.deleteFiles <- toDeletePath
}
}
newNode := newDataNode(key)
list.dataNodeMap[key] = newNode
if freqNode, ok := list.freqNodeMap[newNode.frequency]; ok {
freqNode.push(newNode)
} else {
freqNode := newFrequencyNode(newNode.frequency)
list.freqNodeMap[newNode.frequency] = freqNode
freqNode.push(newNode)
list.addFrequency(newNode.frequency, freqNode, nil)
}
list.setTimerIfValid(newNode)
}
}
//Requires Lock()
func (list *lfuList) delete(key string) {
if node, ok := list.dataNodeMap[key]; ok {
if list.cacheTimeout > 0 {
node.timer.Stop()
}
freqNode := list.freqNodeMap[node.frequency]
freqNode.remove(node)
delete(list.dataNodeMap, key)
if freqNode.list.size == 0 {
list.deleteFrequency(node.frequency)
list.size--
}
list.deleteFiles <- node.key
list.cacheAge = node.frequency
}
}
func (list *lfuList) setTimerIfValid(node *dataNode) {
if list.cacheTimeout > 0 {
timer := time.AfterFunc(time.Duration(list.cacheTimeout)*time.Second, func() {
list.Lock()
list.delete(node.key)
list.Unlock()
})
node.timer = timer
}
}
func newLFUList(maxSizMB float64, lowerThresh float64, upperThresh float64, deleteChan chan string, cachePath string, cacheTimeout uint32) *lfuList {
return &lfuList{
dataNodeMap: make(map[string]*dataNode),
freqNodeMap: make(map[uint64]*frequencyNode),
size: 0,
maxSizeMB: maxSizMB,
lowerThresh: lowerThresh,
upperThresh: upperThresh,
deleteFiles: deleteChan,
cachePath: cachePath,
cacheTimeout: cacheTimeout,
}
}