Remove plans/ directory (P0/P1/P2 work complete)
This commit is contained in:
+164
-152
@@ -15,6 +15,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// maxEvictBatch bounds the candidate snapshot during RLock/Lock collect in Evict*.
|
||||
// Prevents holding lock for unbounded time under extreme pressure.
|
||||
const maxEvictBatch = 4096
|
||||
|
||||
// Ensure MemoryFS implements VFS.
|
||||
var _ vfs.VFS = (*MemoryFS)(nil)
|
||||
|
||||
@@ -300,226 +304,234 @@ func (m *MemoryFS) Stat(key string) (*types.FileInfo, error) {
|
||||
}
|
||||
|
||||
// EvictLRU evicts the least recently used files to free up space
|
||||
// Collect under short exclusive Lock (to serialize concurrent EvictLRU on the unsynchronized LRUList),
|
||||
// then batch delete under WLock. Regular mutation paths (Open/Create) use the normal locking.
|
||||
// already serialize via full Lock. The O(maxEvictBatch) walk is negligible vs. deletes.
|
||||
func (m *MemoryFS) EvictLRU(bytesNeeded uint) uint {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var evicted uint
|
||||
|
||||
// Evict from LRU list until we free enough space
|
||||
for m.size > m.capacity-int64(bytesNeeded) && m.LRU.Len() > 0 {
|
||||
// Get the least recently used item
|
||||
var toEvict []string
|
||||
need := int64(bytesNeeded)
|
||||
cur := m.size
|
||||
for cur > m.capacity-need && m.LRU.Len() > 0 && len(toEvict) < maxEvictBatch {
|
||||
elem := m.LRU.Back()
|
||||
if elem == nil {
|
||||
break
|
||||
}
|
||||
|
||||
fi := elem.Value.(*types.FileInfo)
|
||||
key := fi.Key
|
||||
toEvict = append(toEvict, fi.Key)
|
||||
cur -= fi.Size // local estimate; real size updated in W phase
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
// Remove from LRU
|
||||
m.LRU.Remove(key)
|
||||
|
||||
// Remove from maps
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
|
||||
// Update size
|
||||
m.size -= fi.Size
|
||||
evicted += uint(fi.Size)
|
||||
|
||||
// Clean up key lock
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
if len(toEvict) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
var evicted uint
|
||||
for _, key := range toEvict {
|
||||
if fi, exists := m.info[key]; exists {
|
||||
m.LRU.Remove(key)
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
m.size -= fi.Size
|
||||
evicted += uint(fi.Size)
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return evicted
|
||||
}
|
||||
|
||||
// EvictBySize evicts files by size (ascending = smallest first, descending = largest first)
|
||||
// Collect scalar snapshot (key+size) under RLock (no shared *FileInfo pointers),
|
||||
// sort on copy, brief WLock with live re-fetch for size subtract (fixes data race + accounting drift).
|
||||
type evictCandidate struct {
|
||||
key string
|
||||
size int64
|
||||
}
|
||||
|
||||
func (m *MemoryFS) EvictBySize(bytesNeeded uint, ascending bool) uint {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.mu.RLock()
|
||||
var candidates []evictCandidate
|
||||
for key, fi := range m.info {
|
||||
candidates = append(candidates, evictCandidate{key: key, size: fi.Size})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
var evicted uint
|
||||
var candidates []*types.FileInfo
|
||||
|
||||
// Collect all files
|
||||
for _, fi := range m.info {
|
||||
candidates = append(candidates, fi)
|
||||
if len(candidates) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Sort by size
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
if ascending {
|
||||
return candidates[i].Size < candidates[j].Size
|
||||
return candidates[i].size < candidates[j].size
|
||||
}
|
||||
return candidates[i].Size > candidates[j].Size
|
||||
return candidates[i].size > candidates[j].size
|
||||
})
|
||||
|
||||
// Evict files until we free enough space
|
||||
for _, fi := range candidates {
|
||||
m.mu.Lock()
|
||||
var evicted uint
|
||||
for _, c := range candidates {
|
||||
if m.size <= m.capacity-int64(bytesNeeded) {
|
||||
break
|
||||
}
|
||||
|
||||
key := fi.Key
|
||||
|
||||
// Remove from LRU
|
||||
m.LRU.Remove(key)
|
||||
|
||||
// Remove from maps
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
|
||||
// Update size
|
||||
m.size -= fi.Size
|
||||
evicted += uint(fi.Size)
|
||||
|
||||
// Clean up key lock
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
key := c.key
|
||||
if liveFi, exists := m.info[key]; exists {
|
||||
m.LRU.Remove(key)
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
m.size -= liveFi.Size
|
||||
evicted += uint(liveFi.Size)
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Unlock()
|
||||
return evicted
|
||||
}
|
||||
|
||||
// EvictFIFO evicts files using FIFO (oldest creation time first)
|
||||
// Collect scalar snapshot (key+ctime) under RLock, sort on copy, W phase with live re-fetch.
|
||||
func (m *MemoryFS) EvictFIFO(bytesNeeded uint) uint {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var evicted uint
|
||||
var candidates []*types.FileInfo
|
||||
|
||||
// Collect all files
|
||||
for _, fi := range m.info {
|
||||
candidates = append(candidates, fi)
|
||||
m.mu.RLock()
|
||||
var candidates []struct {
|
||||
key string
|
||||
cTime time.Time
|
||||
}
|
||||
for key, fi := range m.info {
|
||||
candidates = append(candidates, struct {
|
||||
key string
|
||||
cTime time.Time
|
||||
}{key: key, cTime: fi.CTime})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
// Sort by creation time (oldest first)
|
||||
if len(candidates) == 0 {
|
||||
return 0
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return candidates[i].CTime.Before(candidates[j].CTime)
|
||||
return candidates[i].cTime.Before(candidates[j].cTime)
|
||||
})
|
||||
|
||||
// Evict oldest files until we free enough space
|
||||
for _, fi := range candidates {
|
||||
m.mu.Lock()
|
||||
var evicted uint
|
||||
for _, c := range candidates {
|
||||
if m.size <= m.capacity-int64(bytesNeeded) {
|
||||
break
|
||||
}
|
||||
|
||||
key := fi.Key
|
||||
|
||||
// Remove from LRU
|
||||
m.LRU.Remove(key)
|
||||
|
||||
// Remove from maps
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
|
||||
// Update size
|
||||
m.size -= fi.Size
|
||||
evicted += uint(fi.Size)
|
||||
|
||||
// Clean up key lock
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
key := c.key
|
||||
if liveFi, exists := m.info[key]; exists {
|
||||
m.LRU.Remove(key)
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
m.size -= liveFi.Size
|
||||
evicted += uint(liveFi.Size)
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Unlock()
|
||||
return evicted
|
||||
}
|
||||
|
||||
// EvictLFU evicts least frequently used files first (by AccessCount asc; P1-03 real LFU using existing field).
|
||||
// Ties broken by ATime (older first).
|
||||
// EvictLFU evicts least frequently used files first (by AccessCount ascending).
|
||||
// Ties broken by ATime (older first). Uses scalar snapshot under RLock + live re-fetch under WLock.
|
||||
func (m *MemoryFS) EvictLFU(bytesNeeded uint) uint {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var evicted uint
|
||||
var candidates []*types.FileInfo
|
||||
|
||||
// Collect all files
|
||||
for _, fi := range m.info {
|
||||
candidates = append(candidates, fi)
|
||||
m.mu.RLock()
|
||||
var candidates []struct {
|
||||
key string
|
||||
accessCount int
|
||||
aTime time.Time
|
||||
}
|
||||
for key, fi := range m.info {
|
||||
candidates = append(candidates, struct {
|
||||
key string
|
||||
accessCount int
|
||||
aTime time.Time
|
||||
}{key: key, accessCount: fi.AccessCount, aTime: fi.ATime})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
// Sort by access count asc (LFU), then older ATime for ties
|
||||
if len(candidates) == 0 {
|
||||
return 0
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
if candidates[i].AccessCount != candidates[j].AccessCount {
|
||||
return candidates[i].AccessCount < candidates[j].AccessCount
|
||||
if candidates[i].accessCount != candidates[j].accessCount {
|
||||
return candidates[i].accessCount < candidates[j].accessCount
|
||||
}
|
||||
return candidates[i].ATime.Before(candidates[j].ATime)
|
||||
return candidates[i].aTime.Before(candidates[j].aTime)
|
||||
})
|
||||
|
||||
// Evict until enough space
|
||||
for _, fi := range candidates {
|
||||
m.mu.Lock()
|
||||
var evicted uint
|
||||
for _, c := range candidates {
|
||||
if m.size <= m.capacity-int64(bytesNeeded) {
|
||||
break
|
||||
}
|
||||
|
||||
key := fi.Key
|
||||
|
||||
// Remove from LRU
|
||||
m.LRU.Remove(key)
|
||||
|
||||
// Remove from maps
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
|
||||
// Update size
|
||||
m.size -= fi.Size
|
||||
evicted += uint(fi.Size)
|
||||
|
||||
// Clean up key lock
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
key := c.key
|
||||
if liveFi, exists := m.info[key]; exists {
|
||||
m.LRU.Remove(key)
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
m.size -= liveFi.Size
|
||||
evicted += uint(liveFi.Size)
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Unlock()
|
||||
return evicted
|
||||
}
|
||||
|
||||
// EvictHybrid evicts using time-decayed score (recency + frequency from GetTimeDecayedScore; lower value first).
|
||||
// This makes "hybrid" a meaningful size+recency+freq policy (P1-03).
|
||||
// This makes "hybrid" a meaningful size + recency + frequency policy.
|
||||
// Snapshot fields under RLock,
|
||||
// compute score from snapshot in sort (avoids live pointer + time race post-unlock).
|
||||
func (m *MemoryFS) EvictHybrid(bytesNeeded uint) uint {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var evicted uint
|
||||
var candidates []*types.FileInfo
|
||||
|
||||
// Collect all files
|
||||
for _, fi := range m.info {
|
||||
candidates = append(candidates, fi)
|
||||
m.mu.RLock()
|
||||
var candidates []struct {
|
||||
key string
|
||||
accessCount int
|
||||
aTime time.Time
|
||||
}
|
||||
for key, fi := range m.info {
|
||||
candidates = append(candidates, struct {
|
||||
key string
|
||||
accessCount int
|
||||
aTime time.Time
|
||||
}{key: key, accessCount: fi.AccessCount, aTime: fi.ATime})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
// Sort by ascending decayed score (least valuable = evict first)
|
||||
if len(candidates) == 0 {
|
||||
return 0
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return candidates[i].GetTimeDecayedScore() < candidates[j].GetTimeDecayedScore()
|
||||
// Compute from snapshot scalars using shared DecayedScore (single source of truth).
|
||||
scoreI := types.DecayedScore(candidates[i].aTime, candidates[i].accessCount)
|
||||
scoreJ := types.DecayedScore(candidates[j].aTime, candidates[j].accessCount)
|
||||
return scoreI < scoreJ
|
||||
})
|
||||
|
||||
// Evict until enough space
|
||||
for _, fi := range candidates {
|
||||
m.mu.Lock()
|
||||
var evicted uint
|
||||
for _, c := range candidates {
|
||||
if m.size <= m.capacity-int64(bytesNeeded) {
|
||||
break
|
||||
}
|
||||
|
||||
key := fi.Key
|
||||
|
||||
// Remove from LRU
|
||||
m.LRU.Remove(key)
|
||||
|
||||
// Remove from maps
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
|
||||
// Update size
|
||||
m.size -= fi.Size
|
||||
evicted += uint(fi.Size)
|
||||
|
||||
// Clean up key lock
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
key := c.key
|
||||
if liveFi, exists := m.info[key]; exists {
|
||||
m.LRU.Remove(key)
|
||||
delete(m.info, key)
|
||||
delete(m.data, key)
|
||||
m.size -= liveFi.Size
|
||||
evicted += uint(liveFi.Size)
|
||||
shardIndex := locks.GetShardIndex(key)
|
||||
m.keyLocks[shardIndex].Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Unlock()
|
||||
return evicted
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user