github.com/tetratelabs/wazero@v1.2.1/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  	file, err := os.Create(fc.path(key))
    73  	if err != nil {
    74  		return
    75  	}
    76  	defer file.Close()
    77  	_, err = io.Copy(file, content)
    78  	return
    79  }
    80  
    81  func (fc *fileCache) Delete(key Key) (err error) {
    82  	// TODO: take lock per key for more efficiency vs the complexity of impl.
    83  	fc.mux.Lock()
    84  	defer fc.mux.Unlock()
    85  
    86  	err = os.Remove(fc.path(key))
    87  	if errors.Is(err, os.ErrNotExist) {
    88  		err = nil
    89  	}
    90  	return
    91  }