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