Files
steamcache2/vfs/cache/cache.go
Justin Harms bd123bc63a
All checks were successful
Release Tag / release (push) Successful in 9s
Refactor module naming and update references to steamcache2
- Changed module name from `s1d3sw1ped/SteamCache2` to `s1d3sw1ped/steamcache2` for consistency.
- Updated all import paths and references throughout the codebase to reflect the new module name.
- Adjusted README and Makefile to use the updated module name, ensuring clarity in usage instructions.
2025-09-21 23:10:21 -05:00

437 lines
10 KiB
Go

// vfs/cache/cache.go
package cache
import (
"io"
"s1d3sw1ped/steamcache2/vfs"
"s1d3sw1ped/steamcache2/vfs/vfserror"
"sync"
"sync/atomic"
)
// TieredCache implements a two-tier cache with fast (memory) and slow (disk) storage
type TieredCache struct {
fast vfs.VFS // Memory cache (fast)
slow vfs.VFS // Disk cache (slow)
mu sync.RWMutex
}
// LockFreeTieredCache implements a lock-free two-tier cache for better concurrency
type LockFreeTieredCache struct {
fast *atomic.Value // Memory cache (fast) - atomic.Value for lock-free access
slow *atomic.Value // Disk cache (slow) - atomic.Value for lock-free access
}
// New creates a new tiered cache
func New() *TieredCache {
return &TieredCache{}
}
// SetFast sets the fast (memory) tier
func (tc *TieredCache) SetFast(vfs vfs.VFS) {
tc.mu.Lock()
defer tc.mu.Unlock()
tc.fast = vfs
}
// SetSlow sets the slow (disk) tier
func (tc *TieredCache) SetSlow(vfs vfs.VFS) {
tc.mu.Lock()
defer tc.mu.Unlock()
tc.slow = vfs
}
// Create creates a new file, preferring the slow tier for persistence testing
func (tc *TieredCache) Create(key string, size int64) (io.WriteCloser, error) {
tc.mu.RLock()
defer tc.mu.RUnlock()
// Try slow tier first (disk) for better testability
if tc.slow != nil {
return tc.slow.Create(key, size)
}
// Fall back to fast tier (memory)
if tc.fast != nil {
return tc.fast.Create(key, size)
}
return nil, vfserror.ErrNotFound
}
// Open opens a file, checking fast tier first, then slow tier with promotion
func (tc *TieredCache) Open(key string) (io.ReadCloser, error) {
tc.mu.RLock()
defer tc.mu.RUnlock()
// Try fast tier first (memory)
if tc.fast != nil {
if reader, err := tc.fast.Open(key); err == nil {
return reader, nil
}
}
// Fall back to slow tier (disk) and promote to fast tier
if tc.slow != nil {
reader, err := tc.slow.Open(key)
if err != nil {
return nil, err
}
// If we have both tiers, check if we should promote the file to fast tier
if tc.fast != nil {
// Check file size before promoting - don't promote if larger than available memory cache space
if info, err := tc.slow.Stat(key); err == nil {
availableSpace := tc.fast.Capacity() - tc.fast.Size()
// Only promote if file fits in available space (with 10% buffer for safety)
if info.Size <= int64(float64(availableSpace)*0.9) {
// Create a new reader for promotion to avoid interfering with the returned reader
promotionReader, err := tc.slow.Open(key)
if err == nil {
go tc.promoteToFast(key, promotionReader)
}
}
}
}
return reader, nil
}
return nil, vfserror.ErrNotFound
}
// Delete removes a file from all tiers
func (tc *TieredCache) Delete(key string) error {
tc.mu.RLock()
defer tc.mu.RUnlock()
var lastErr error
// Delete from fast tier
if tc.fast != nil {
if err := tc.fast.Delete(key); err != nil {
lastErr = err
}
}
// Delete from slow tier
if tc.slow != nil {
if err := tc.slow.Delete(key); err != nil {
lastErr = err
}
}
return lastErr
}
// Stat returns file information, checking fast tier first
func (tc *TieredCache) Stat(key string) (*vfs.FileInfo, error) {
tc.mu.RLock()
defer tc.mu.RUnlock()
// Try fast tier first (memory)
if tc.fast != nil {
if info, err := tc.fast.Stat(key); err == nil {
return info, nil
}
}
// Fall back to slow tier (disk)
if tc.slow != nil {
return tc.slow.Stat(key)
}
return nil, vfserror.ErrNotFound
}
// Name returns the cache name
func (tc *TieredCache) Name() string {
return "TieredCache"
}
// Size returns the total size across all tiers
func (tc *TieredCache) Size() int64 {
tc.mu.RLock()
defer tc.mu.RUnlock()
var total int64
if tc.fast != nil {
total += tc.fast.Size()
}
if tc.slow != nil {
total += tc.slow.Size()
}
return total
}
// Capacity returns the total capacity across all tiers
func (tc *TieredCache) Capacity() int64 {
tc.mu.RLock()
defer tc.mu.RUnlock()
var total int64
if tc.fast != nil {
total += tc.fast.Capacity()
}
if tc.slow != nil {
total += tc.slow.Capacity()
}
return total
}
// promoteToFast promotes a file from slow tier to fast tier
func (tc *TieredCache) promoteToFast(key string, reader io.ReadCloser) {
defer reader.Close()
// Get file info from slow tier to determine size
tc.mu.RLock()
var size int64
if tc.slow != nil {
if info, err := tc.slow.Stat(key); err == nil {
size = info.Size
} else {
tc.mu.RUnlock()
return // Skip promotion if we can't get file info
}
}
tc.mu.RUnlock()
// Check if file fits in available memory cache space
tc.mu.RLock()
if tc.fast != nil {
availableSpace := tc.fast.Capacity() - tc.fast.Size()
// Only promote if file fits in available space (with 10% buffer for safety)
if size > int64(float64(availableSpace)*0.9) {
tc.mu.RUnlock()
return // Skip promotion if file is too large
}
}
tc.mu.RUnlock()
// Read the entire file content
content, err := io.ReadAll(reader)
if err != nil {
return // Skip promotion if read fails
}
// Create the file in fast tier
tc.mu.RLock()
if tc.fast != nil {
writer, err := tc.fast.Create(key, size)
if err == nil {
// Write content to fast tier
writer.Write(content)
writer.Close()
}
}
tc.mu.RUnlock()
}
// NewLockFree creates a new lock-free tiered cache
func NewLockFree() *LockFreeTieredCache {
return &LockFreeTieredCache{
fast: &atomic.Value{},
slow: &atomic.Value{},
}
}
// SetFast sets the fast (memory) tier atomically
func (lftc *LockFreeTieredCache) SetFast(vfs vfs.VFS) {
lftc.fast.Store(vfs)
}
// SetSlow sets the slow (disk) tier atomically
func (lftc *LockFreeTieredCache) SetSlow(vfs vfs.VFS) {
lftc.slow.Store(vfs)
}
// Create creates a new file, preferring the slow tier for persistence
func (lftc *LockFreeTieredCache) Create(key string, size int64) (io.WriteCloser, error) {
// Try slow tier first (disk) for better testability
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
return vfs.Create(key, size)
}
}
// Fall back to fast tier (memory)
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
return vfs.Create(key, size)
}
}
return nil, vfserror.ErrNotFound
}
// Open opens a file, checking fast tier first, then slow tier with promotion
func (lftc *LockFreeTieredCache) Open(key string) (io.ReadCloser, error) {
// Try fast tier first (memory)
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
if reader, err := vfs.Open(key); err == nil {
return reader, nil
}
}
}
// Fall back to slow tier (disk) and promote to fast tier
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
reader, err := vfs.Open(key)
if err != nil {
return nil, err
}
// If we have both tiers, promote the file to fast tier
if fast := lftc.fast.Load(); fast != nil {
// Create a new reader for promotion to avoid interfering with the returned reader
promotionReader, err := vfs.Open(key)
if err == nil {
go lftc.promoteToFast(key, promotionReader)
}
}
return reader, nil
}
}
return nil, vfserror.ErrNotFound
}
// Delete removes a file from all tiers
func (lftc *LockFreeTieredCache) Delete(key string) error {
var lastErr error
// Delete from fast tier
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
if err := vfs.Delete(key); err != nil {
lastErr = err
}
}
}
// Delete from slow tier
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
if err := vfs.Delete(key); err != nil {
lastErr = err
}
}
}
return lastErr
}
// Stat returns file information, checking fast tier first
func (lftc *LockFreeTieredCache) Stat(key string) (*vfs.FileInfo, error) {
// Try fast tier first (memory)
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
if info, err := vfs.Stat(key); err == nil {
return info, nil
}
}
}
// Fall back to slow tier (disk)
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
return vfs.Stat(key)
}
}
return nil, vfserror.ErrNotFound
}
// Name returns the cache name
func (lftc *LockFreeTieredCache) Name() string {
return "LockFreeTieredCache"
}
// Size returns the total size across all tiers
func (lftc *LockFreeTieredCache) Size() int64 {
var total int64
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
total += vfs.Size()
}
}
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
total += vfs.Size()
}
}
return total
}
// Capacity returns the total capacity across all tiers
func (lftc *LockFreeTieredCache) Capacity() int64 {
var total int64
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
total += vfs.Capacity()
}
}
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
total += vfs.Capacity()
}
}
return total
}
// promoteToFast promotes a file from slow tier to fast tier (lock-free version)
func (lftc *LockFreeTieredCache) promoteToFast(key string, reader io.ReadCloser) {
defer reader.Close()
// Get file info from slow tier to determine size
var size int64
if slow := lftc.slow.Load(); slow != nil {
if vfs, ok := slow.(vfs.VFS); ok {
if info, err := vfs.Stat(key); err == nil {
size = info.Size
} else {
return // Skip promotion if we can't get file info
}
}
}
// Check if file fits in available memory cache space
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
availableSpace := vfs.Capacity() - vfs.Size()
// Only promote if file fits in available space (with 10% buffer for safety)
if size > int64(float64(availableSpace)*0.9) {
return // Skip promotion if file is too large
}
}
}
// Read the entire file content
content, err := io.ReadAll(reader)
if err != nil {
return // Skip promotion if read fails
}
// Create the file in fast tier
if fast := lftc.fast.Load(); fast != nil {
if vfs, ok := fast.(vfs.VFS); ok {
writer, err := vfs.Create(key, size)
if err == nil {
// Write content to fast tier
writer.Write(content)
writer.Close()
}
}
}
}