github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/cache/backend.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"sync"
     7  
     8  	"github.com/pkg/errors"
     9  	"github.com/restic/restic/internal/debug"
    10  	"github.com/restic/restic/internal/restic"
    11  )
    12  
    13  // Backend wraps a restic.Backend and adds a cache.
    14  type Backend struct {
    15  	restic.Backend
    16  	*Cache
    17  
    18  	// inProgress contains the handle for all files that are currently
    19  	// downloaded. The channel in the value is closed as soon as the download
    20  	// is finished.
    21  	inProgressMutex sync.Mutex
    22  	inProgress      map[restic.Handle]chan struct{}
    23  }
    24  
    25  // ensure cachedBackend implements restic.Backend
    26  var _ restic.Backend = &Backend{}
    27  
    28  func newBackend(be restic.Backend, c *Cache) *Backend {
    29  	return &Backend{
    30  		Backend:    be,
    31  		Cache:      c,
    32  		inProgress: make(map[restic.Handle]chan struct{}),
    33  	}
    34  }
    35  
    36  // Remove deletes a file from the backend and the cache if it has been cached.
    37  func (b *Backend) Remove(ctx context.Context, h restic.Handle) error {
    38  	debug.Log("cache Remove(%v)", h)
    39  	err := b.Backend.Remove(ctx, h)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	return b.Cache.Remove(h)
    45  }
    46  
    47  var autoCacheTypes = map[restic.FileType]struct{}{
    48  	restic.IndexFile:    struct{}{},
    49  	restic.SnapshotFile: struct{}{},
    50  }
    51  
    52  // Save stores a new file in the backend and the cache.
    53  func (b *Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) {
    54  	if _, ok := autoCacheTypes[h.Type]; !ok {
    55  		return b.Backend.Save(ctx, h, rd)
    56  	}
    57  
    58  	debug.Log("Save(%v): auto-store in the cache", h)
    59  
    60  	seeker, ok := rd.(io.Seeker)
    61  	if !ok {
    62  		return errors.New("reader is not a seeker")
    63  	}
    64  
    65  	pos, err := seeker.Seek(0, io.SeekCurrent)
    66  	if err != nil {
    67  		return errors.Wrapf(err, "Seek")
    68  	}
    69  
    70  	if pos != 0 {
    71  		return errors.Errorf("reader is not rewind (pos %d)", pos)
    72  	}
    73  
    74  	err = b.Backend.Save(ctx, h, rd)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	_, err = seeker.Seek(pos, io.SeekStart)
    80  	if err != nil {
    81  		return errors.Wrapf(err, "Seek")
    82  	}
    83  
    84  	err = b.Cache.Save(h, rd)
    85  	if err != nil {
    86  		debug.Log("unable to save %v to cache: %v", h, err)
    87  		_ = b.Cache.Remove(h)
    88  		return nil
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  var autoCacheFiles = map[restic.FileType]bool{
    95  	restic.IndexFile:    true,
    96  	restic.SnapshotFile: true,
    97  }
    98  
    99  func (b *Backend) cacheFile(ctx context.Context, h restic.Handle) error {
   100  	finish := make(chan struct{})
   101  	defer func() {
   102  		close(finish)
   103  
   104  		// remove the finish channel from the map
   105  		b.inProgressMutex.Lock()
   106  		delete(b.inProgress, h)
   107  		b.inProgressMutex.Unlock()
   108  	}()
   109  
   110  	b.inProgressMutex.Lock()
   111  	other, alreadyDownloading := b.inProgress[h]
   112  	if !alreadyDownloading {
   113  		b.inProgress[h] = finish
   114  	}
   115  	b.inProgressMutex.Unlock()
   116  
   117  	if alreadyDownloading {
   118  		debug.Log("readahead %v is already performed by somebody else, delegating...", h)
   119  		<-other
   120  		debug.Log("download %v finished", h)
   121  		return nil
   122  	}
   123  
   124  	rd, err := b.Backend.Load(ctx, h, 0, 0)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	if err = b.Cache.Save(h, rd); err != nil {
   130  		return err
   131  	}
   132  
   133  	if err = rd.Close(); err != nil {
   134  		// try to remove from the cache, ignore errors
   135  		_ = b.Cache.Remove(h)
   136  		return err
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  // loadFromCacheOrDelegate will try to load the file from the cache, and fall
   143  // back to the backend if that fails.
   144  func (b *Backend) loadFromCacheOrDelegate(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
   145  	rd, err := b.Cache.Load(h, length, offset)
   146  	if err == nil {
   147  		return rd, nil
   148  	}
   149  
   150  	return b.Backend.Load(ctx, h, length, offset)
   151  }
   152  
   153  // Load loads a file from the cache or the backend.
   154  func (b *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
   155  	b.inProgressMutex.Lock()
   156  	waitForFinish, inProgress := b.inProgress[h]
   157  	b.inProgressMutex.Unlock()
   158  
   159  	if inProgress {
   160  		debug.Log("downloading %v is already in progress, waiting for finish", h)
   161  		<-waitForFinish
   162  		debug.Log("downloading %v finished", h)
   163  	}
   164  
   165  	if b.Cache.Has(h) {
   166  		debug.Log("Load(%v, %v, %v) from cache", h, length, offset)
   167  		rd, err := b.Cache.Load(h, length, offset)
   168  		if err == nil {
   169  			return rd, nil
   170  		}
   171  		debug.Log("error loading %v from cache: %v", h, err)
   172  	}
   173  
   174  	// partial file requested
   175  	if offset != 0 || length != 0 {
   176  		if b.Cache.PerformReadahead(h) {
   177  			debug.Log("performing readahead for %v", h)
   178  
   179  			err := b.cacheFile(ctx, h)
   180  			if err == nil {
   181  				return b.loadFromCacheOrDelegate(ctx, h, length, offset)
   182  			}
   183  
   184  			debug.Log("error caching %v: %v", h, err)
   185  		}
   186  
   187  		debug.Log("Load(%v, %v, %v): partial file requested, delegating to backend", h, length, offset)
   188  		return b.Backend.Load(ctx, h, length, offset)
   189  	}
   190  
   191  	// if we don't automatically cache this file type, fall back to the backend
   192  	if _, ok := autoCacheFiles[h.Type]; !ok {
   193  		debug.Log("Load(%v, %v, %v): delegating to backend", h, length, offset)
   194  		return b.Backend.Load(ctx, h, length, offset)
   195  	}
   196  
   197  	debug.Log("auto-store %v in the cache", h)
   198  	err := b.cacheFile(ctx, h)
   199  
   200  	if err == nil {
   201  		// load the cached version
   202  		return b.Cache.Load(h, 0, 0)
   203  	}
   204  
   205  	debug.Log("error caching %v: %v, falling back to backend", h, err)
   206  	return b.Backend.Load(ctx, h, length, offset)
   207  }
   208  
   209  // Stat tests whether the backend has a file. If it does not exist but still
   210  // exists in the cache, it is removed from the cache.
   211  func (b *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
   212  	debug.Log("cache Stat(%v)", h)
   213  
   214  	fi, err := b.Backend.Stat(ctx, h)
   215  	if err != nil {
   216  		if b.Backend.IsNotExist(err) {
   217  			// try to remove from the cache, ignore errors
   218  			_ = b.Cache.Remove(h)
   219  		}
   220  
   221  		return fi, err
   222  	}
   223  
   224  	return fi, err
   225  }
   226  
   227  // IsNotExist returns true if the error is caused by a non-existing file.
   228  func (b *Backend) IsNotExist(err error) bool {
   229  	return b.Backend.IsNotExist(err)
   230  }