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  }