github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/filecache/file_cache.go (about) 1 package filecache 2 3 import ( 4 "encoding/hex" 5 "errors" 6 "io" 7 "os" 8 "path" 9 "sync" 10 ) 11 12 // New returns a new Cache implemented by fileCache. 13 func New(dir string) Cache { 14 return newFileCache(dir) 15 } 16 17 func newFileCache(dir string) *fileCache { 18 return &fileCache{dirPath: dir} 19 } 20 21 // fileCache persists compiled functions into dirPath. 22 // 23 // Note: this can be expanded to do binary signing/verification, set TTL on each entry, etc. 24 type fileCache struct { 25 dirPath string 26 mux sync.RWMutex 27 } 28 29 type fileReadCloser struct { 30 *os.File 31 fc *fileCache 32 } 33 34 func (fc *fileCache) path(key Key) string { 35 return path.Join(fc.dirPath, hex.EncodeToString(key[:])) 36 } 37 38 func (fc *fileCache) Get(key Key) (content io.ReadCloser, ok bool, err error) { 39 // TODO: take lock per key for more efficiency vs the complexity of impl. 40 fc.mux.RLock() 41 unlock := fc.mux.RUnlock 42 defer func() { 43 if unlock != nil { 44 unlock() 45 } 46 }() 47 48 f, err := os.Open(fc.path(key)) 49 if errors.Is(err, os.ErrNotExist) { 50 return nil, false, nil 51 } else if err != nil { 52 return nil, false, err 53 } else { 54 // Unlock is done inside the content.Close() at the call site. 55 unlock = nil 56 return &fileReadCloser{File: f, fc: fc}, true, nil 57 } 58 } 59 60 // Close wraps the os.File Close to release the read lock on fileCache. 61 func (f *fileReadCloser) Close() (err error) { 62 defer f.fc.mux.RUnlock() 63 err = f.File.Close() 64 return 65 } 66 67 func (fc *fileCache) Add(key Key, content io.Reader) (err error) { 68 // TODO: take lock per key for more efficiency vs the complexity of impl. 69 fc.mux.Lock() 70 defer fc.mux.Unlock() 71 72 // Use rename for an atomic write 73 path := fc.path(key) 74 file, err := os.Create(path + ".tmp") 75 if err != nil { 76 return 77 } 78 defer func() { 79 if err != nil { 80 _ = os.Remove(file.Name()) 81 } 82 }() 83 defer file.Close() 84 if _, err = io.Copy(file, content); err != nil { 85 return 86 } 87 if err = file.Sync(); err != nil { 88 return 89 } 90 if err = file.Close(); err != nil { 91 return 92 } 93 err = os.Rename(file.Name(), path) 94 return 95 } 96 97 func (fc *fileCache) Delete(key Key) (err error) { 98 // TODO: take lock per key for more efficiency vs the complexity of impl. 99 fc.mux.Lock() 100 defer fc.mux.Unlock() 101 102 err = os.Remove(fc.path(key)) 103 if errors.Is(err, os.ErrNotExist) { 104 err = nil 105 } 106 return 107 }