Files
steamcache2/vfs/types/types.go
T
s1d3sw1ped_bot 8e09c89e24
CI / vulncheck (pull_request) Successful in 14s
CI / check-and-test (pull_request) Successful in 40s
vfs/cache: Fix promoteToFast race with WriteCloser Close
promoteToFast read FileInfo.Size from Stat after the VFS lock was
released, while WriteCloser.Close updated Size on the same live
object. Concurrent Create/Open on overlapping keys tripped the race
detector.

Stat now returns a FileInfo snapshot, and promotion uses the
ReadAll length for the fast-tier Create size. Promotion stays
best-effort.

#21
2026-09-01 20:30:40 +00:00

107 lines
3.1 KiB
Go

// vfs/types/types.go
package types
import (
"os"
"time"
)
// FileInfo contains metadata about a cached file
type FileInfo struct {
Key string `json:"key"`
Size int64 `json:"size"`
ATime time.Time `json:"atime"` // Last access time
CTime time.Time `json:"ctime"` // Creation time
AccessCount int `json:"access_count"`
}
// NewFileInfo creates a new FileInfo with the given key and current timestamp
func NewFileInfo(key string, size int64) *FileInfo {
now := time.Now()
return &FileInfo{
Key: key,
Size: size,
ATime: now,
CTime: now,
AccessCount: 1,
}
}
// Clone returns a snapshot copy of fi. Stat returns Clone() so callers can
// read Size and other fields without racing Close/Open mutations of the
// in-map FileInfo.
func (fi *FileInfo) Clone() *FileInfo {
if fi == nil {
return nil
}
cp := *fi
return &cp
}
// NewFileInfoFromOS creates a FileInfo from os.FileInfo
func NewFileInfoFromOS(info os.FileInfo, key string) *FileInfo {
return &FileInfo{
Key: key,
Size: info.Size(),
ATime: time.Now(), // We don't have access time from os.FileInfo
CTime: info.ModTime(),
AccessCount: 1,
}
}
// UpdateAccess updates the access time and increments the access count
func (fi *FileInfo) UpdateAccess() {
fi.ATime = time.Now()
fi.AccessCount++
}
// BatchedTimeUpdate provides a way to batch time updates for better performance
type BatchedTimeUpdate struct {
currentTime time.Time
lastUpdate time.Time
updateInterval time.Duration
}
// NewBatchedTimeUpdate creates a new batched time updater
func NewBatchedTimeUpdate(interval time.Duration) *BatchedTimeUpdate {
now := time.Now()
return &BatchedTimeUpdate{
currentTime: now,
lastUpdate: now,
updateInterval: interval,
}
}
// GetTime returns the current cached time, updating it if necessary
func (btu *BatchedTimeUpdate) GetTime() time.Time {
now := time.Now()
if now.Sub(btu.lastUpdate) >= btu.updateInterval {
btu.currentTime = now
btu.lastUpdate = now
}
return btu.currentTime
}
// UpdateAccessBatched updates the access time using batched time updates
func (fi *FileInfo) UpdateAccessBatched(btu *BatchedTimeUpdate) {
fi.ATime = btu.GetTime()
fi.AccessCount++
}
// DecayedScore computes the time-decayed eviction score from scalar snapshot values (aTime, accessCount).
// This is the canonical implementation of the decay formula (shared to eliminate duplication).
// Used by FileInfo.GetTimeDecayedScore and by EvictHybrid (memory/disk) for race-free scoring
// on values captured under RLock.
func DecayedScore(aTime time.Time, accessCount int) float64 {
timeSinceAccess := time.Since(aTime).Hours()
decayFactor := 1.0 / (1.0 + timeSinceAccess/24.0) // Decay over days
frequencyBonus := float64(accessCount) * 0.1
return decayFactor + frequencyBonus
}
// GetTimeDecayedScore calculates a score based on access time and frequency
// More recent and frequent accesses get higher scores.
func (fi *FileInfo) GetTimeDecayedScore() float64 {
return DecayedScore(fi.ATime, fi.AccessCount)
}