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 }