Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9cef74d
Implemented initial design of ReleaseFile and corresponding tests
Jun 9, 2026
d51bfe8
Initial ReadInBuffer component
Jun 11, 2026
d805f91
Added initial ReadInBuffer tests
Jun 15, 2026
432d54d
Initial implementation of DeleteFile, no tests yet
Jun 15, 2026
628feaa
Initial LRU skeleton
Jun 25, 2026
2ccb80f
Initial LRU Functions
Jun 26, 2026
63b276c
LRU eviction initial implementation
Jun 29, 2026
ca744a6
LRU worker logic added
Jun 29, 2026
8e31b0c
Initial LRU Code w Workers/Eviction logic
Jun 29, 2026
ed56450
LRU capacity design fix
Jul 1, 2026
ea8b42a
Fixed deadlock bugs in channels
Jul 1, 2026
3ea1864
Initial test file setup
Jul 2, 2026
4b15841
Bug fixes and initial tests of lru eviction policy ready for pr draft
Jul 6, 2026
449e766
Merge remote-tracking branch 'upstream/feature/tiered_storage' into f…
Jul 9, 2026
3f3a1b8
Handeled Dirty File State Scenario, ReleaseFile bugs, added Tests for…
Jul 13, 2026
74cce48
Changed all instances of normal map to sync.map
Jul 14, 2026
adef71e
Changed all instances of normal map to sync.map in TEST FILE NOW
Jul 14, 2026
e9dcb70
LRU Concurrency Design Implementation
Jul 17, 2026
13c608c
Fixed infinite loop logic and StopPolicy, old tests now work also tes…
Jul 20, 2026
8ac166b
Initial wiring of LRU Policy
Jul 20, 2026
d6c9a30
Wired LRU Policy with Tiered Storage, Implemented Tests against funct…
Jul 21, 2026
7a58ff6
Fixed DeleteFile and added in logic for edge case in LRU Policy
Jul 31, 2026
776d9b3
Tests for DeleteFile, still working on LRU Test
Aug 3, 2026
5a99492
Potential Fix for deadlock in Enqueue and Dequeue, manually unlocked …
Aug 4, 2026
37507f9
Something might be wrong with my vsCode that commit might not have wo…
Aug 4, 2026
ed6d5ed
Initial implementation of Sync and Flush, they are identical
Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions component/tiered_storage/lru_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
package tiered_storage

import (
"fmt"
"os"
"path/filepath"
"sync"
"time"

"github.com/Seagate/cloudfuse/common"
"github.com/Seagate/cloudfuse/common/log"
)

type lruNode struct {
prev *lruNode
next *lruNode
name string
}

//upload 50 files and then check

type lruQueue struct {
mu sync.Mutex

nodeMap sync.Map

wg sync.WaitGroup
numWorkers int

head *lruNode
tail *lruNode

uploadChan chan string
doneChan chan struct{}

cachePath string
maxCacheSize float64

threshold float64
targetRatio float64

tickerUnit time.Duration

//Functions to wire later into tiered_storage package
//upload function from tiered storage WIRE THIS LATER in tiered storage because we are using a function from there
upload func(name string) error
Comment thread
HoJacob marked this conversation as resolved.
Outdated

FileHasOpenFileHandle func(name string) bool

//policy.isFileInUse = func(name string) bool {
// return c.fileLocks.Get(name).Count() > 0
//}

//we also need the filLock map to use

}

func (q *lruQueue) StartPolicy() error {
if q.upload == nil {
return fmt.Errorf("lruQueue: upload function not set")
}
if q.numWorkers <= 0 {
return fmt.Errorf("lruQueue: numWorkers must be > 0")
}
//initialize queue
q.head = nil
q.tail = nil
//channels
q.uploadChan = make(chan string, 1000)
q.doneChan = make(chan struct{})
//timer
//go routines
q.wg.Add(1)
go q.capacityChecker()

q.wg.Add(q.numWorkers)
for i := 0; i < q.numWorkers; i++ {
go q.worker()
}
return nil
}

func (q *lruQueue) StopPolicy() error {
close(q.doneChan)
q.wg.Wait()
return nil
}

func (q *lruQueue) Touch(name string) {
q.Enqueue(name)
}

func (q *lruQueue) Enqueue(name string) {
//Maybe have a duplicate , that touches essentially

//lock earlier
q.mu.Lock()
defer q.mu.Unlock()

//create node
newNode := &lruNode{name: name}
val, found := q.nodeMap.LoadOrStore(name, newNode)
node := val.(*lruNode)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, dereferencing / type asserting val before we know if val was found makes me nervous. I figure it's probably fine and just returns nil in practice, but from my experience with C, I see echoes of "nil pointer dereference" here.


if found {
// touch
q.extractNode(node)
} else {
// brand new node — update tail if list was empty
if q.tail == nil {
q.tail = node
}
}
q.setHead(node)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a new node before searching for an existing one. It still does what we expect in the end, but it smells off.

}

func (q *lruQueue) Dequeue(name string) {
log.Trace("lruPolicy::removeNode : %s", name)

q.mu.Lock()
defer q.mu.Unlock()

val, found := q.nodeMap.LoadAndDelete(name)
if !found || val == nil {
return
}

node := val.(*lruNode)

q.extractNode(node)
}

func (q *lruQueue) setHead(node *lruNode) {
// insert node at the head
node.prev = nil
node.next = q.head
if q.head != nil {
q.head.prev = node
}
q.head = node
}

func (q *lruQueue) extractNode(node *lruNode) {
// remove the node from its position in the list

// head case
if node == q.head {
q.head = node.next
}
//tail case
if node == q.tail {
q.tail = node.prev
}

if node.next != nil {
node.next.prev = node.prev
}
if node.prev != nil {
node.prev.next = node.next
}
node.prev = nil
node.next = nil
}

func (q *lruQueue) capacityChecker() {
defer q.wg.Done()
defer close(q.uploadChan)

ticker := time.NewTicker(q.tickerUnit)
defer ticker.Stop()

for {
select {
case <-ticker.C:
// eviction

//check du , do stat file before, based on difference between DU and

//1. check if we need eviction
curSize, err := common.GetUsage(q.cachePath)
if err != nil {
log.Err("lruPolicy::capacityChecker : failed to get usage: %v", err)
continue
}
if curSize/q.maxCacheSize <= q.threshold {
break
}

//targetRatio should always be less than thresholdRatio

//find difference to evict down to 60%
difference := curSize - q.maxCacheSize*q.targetRatio
curEvictedSpace := 0
for curEvictedSpace < int(difference) {
nodeSize, evicted := q.eviction()
if !evicted {
break
}
curEvictedSpace += int(nodeSize)
}
Comment on lines +182 to +256

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good!
Later on, when we're looking at concurrency, let's make sure this code can't run more than once at a time (if the ticker is faster than eviction, we don't begin another eviction loop in parallel).


case <-q.doneChan:
return
}
}
}

func (q *lruQueue) eviction() (int64, bool) {
q.mu.Lock()
nodeToEvict := q.tail
if nodeToEvict == nil {
q.mu.Unlock()
return 0, false
}

//find the first applicable node
for nodeToEvict != nil && q.FileHasOpenFileHandle(nodeToEvict.name) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there could be a race condition here. After this check, it is possible that a new request comes in that opens the file before it gets uploaded.

prevNode := nodeToEvict.prev
q.extractNode(nodeToEvict)
q.setHead(nodeToEvict)
nodeToEvict = prevNode
}
//means all files are in use, what if all files are in use so what do we evict then?????time??????
if nodeToEvict == nil {
q.mu.Unlock()
return 0, false
}

//right here is where we lock the file, where do we unlock it

//Get the node size that we evict
localPath := filepath.Join(q.cachePath, nodeToEvict.name)

fileInfo, err := os.Stat(localPath)
if err != nil {
log.Err("lruPolicy::capacityChecker : failed to stat file: %v", err)
q.mu.Unlock()
return 0, false
}
nodeSize := fileInfo.Size()

//remove node from queue and map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the eviction code here, you delete from the LRU, but the file will still remain in the local cache? Do we want the file to stay in the cache or should we delete it when we evict?

q.extractNode(nodeToEvict)
q.nodeMap.Delete(nodeToEvict.name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a rule we want to follow, for what node membership in the map and the linked list mean? In other words, should we update our records and then execute the action (upload & delete), or visa versa? Which is better for error handling?

q.mu.Unlock()

//send node to channel
select {
case q.uploadChan <- nodeToEvict.name:
case <-q.doneChan:
return 0, false
}

return nodeSize, true
}

func (q *lruQueue) worker() {
defer q.wg.Done()
for fileName := range q.uploadChan {
err := q.upload(fileName)
if err != nil {
log.Err("lruPolicy::worker : failed to upload file %s: %v", fileName, err)
}
}
}
Loading