// vfs/gc/gc.go package gc import ( "fmt" "io" "s1d3sw1ped/SteamCache2/steamcache/logger" "s1d3sw1ped/SteamCache2/vfs" "s1d3sw1ped/SteamCache2/vfs/cachestate" "s1d3sw1ped/SteamCache2/vfs/disk" "s1d3sw1ped/SteamCache2/vfs/memory" "s1d3sw1ped/SteamCache2/vfs/vfserror" "time" ) var ( // ErrInsufficientSpace is returned when there are no files to delete in the VFS. ErrInsufficientSpace = fmt.Errorf("no files to delete") ) // LRUGC deletes files in LRU order until enough space is reclaimed. func LRUGC(vfss vfs.VFS, size uint) error { logger.Logger.Debug().Uint("target", size).Msg("Attempting to reclaim space using LRU GC") var reclaimed uint // reclaimed space in bytes for { switch fs := vfss.(type) { case *disk.DiskFS: fi := fs.LRU.Back() if fi == nil { return ErrInsufficientSpace // No files to delete } sz := uint(fi.Size()) err := fs.Delete(fi.Name()) if err != nil { continue // If delete fails, try the next file } reclaimed += sz case *memory.MemoryFS: fi := fs.LRU.Back() if fi == nil { return ErrInsufficientSpace // No files to delete } sz := uint(fi.Size()) err := fs.Delete(fi.Name()) if err != nil { continue // If delete fails, try the next file } reclaimed += sz default: panic("unreachable: unsupported VFS type for LRU GC") // panic if the VFS is not disk or memory } if reclaimed >= size { logger.Logger.Debug().Uint("target", size).Uint("achieved", reclaimed).Msg("Reclaimed enough space using LRU GC") return nil // stop if enough space is reclaimed } } } func PromotionDecider(fi *vfs.FileInfo, cs cachestate.CacheState) bool { return time.Since(fi.AccessTime()) < time.Second*60 // Put hot files in the fast vfs if equipped } // Ensure GCFS implements VFS. var _ vfs.VFS = (*GCFS)(nil) // GCFS is a virtual file system that calls a GC handler when the disk is full. The GC handler is responsible for freeing up space on the disk. The GCFS is a wrapper around another VFS. type GCFS struct { vfs.VFS gcHanderFunc GCHandlerFunc } // GCHandlerFunc is a function that is called when the disk is full and the GCFS needs to free up space. It is passed the VFS and the size of the file that needs to be written. Its up to the implementation to free up space. How much space is freed is also up to the implementation. type GCHandlerFunc func(vfs vfs.VFS, size uint) error func New(vfs vfs.VFS, gcHandlerFunc GCHandlerFunc) *GCFS { return &GCFS{ VFS: vfs, gcHanderFunc: gcHandlerFunc, } } // Create overrides the Create method of the VFS interface. It tries to create the key, if it fails due to disk full error, it calls the GC handler and tries again. If it still fails it returns the error. func (g *GCFS) Create(key string, size int64) (io.WriteCloser, error) { w, err := g.VFS.Create(key, size) // try to create the key for err == vfserror.ErrDiskFull && g.gcHanderFunc != nil { // if the error is disk full and there is a GC handler errr := g.gcHanderFunc(g.VFS, uint(size)) // call the GC handler if errr == ErrInsufficientSpace { return nil, errr // if the GC handler returns no files to delete, return the error } w, err = g.VFS.Create(key, size) } if err != nil { if err == vfserror.ErrDiskFull { logger.Logger.Error().Str("key", key).Int64("size", size).Msg("Failed to create file due to disk full, even after GC") } else { logger.Logger.Error().Str("key", key).Int64("size", size).Err(err).Msg("Failed to create file") } } return w, err } func (g *GCFS) Name() string { return fmt.Sprintf("GCFS(%s)", g.VFS.Name()) // wrap the name of the VFS with GCFS so we can see that its a GCFS }