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  }