diff --git a/changelog/unreleased/issue-4934 b/changelog/unreleased/issue-4934 new file mode 100644 index 000000000..6891ca204 --- /dev/null +++ b/changelog/unreleased/issue-4934 @@ -0,0 +1,8 @@ +Enhancement: Clear removed snapshots from local cache of the current host + +Restic only removed snapshots from the cache on the host that runs the `forget` command. +On other hosts that use the same repository, the old snapshots remained in the cache. +Restic now, automatically clears old snapshots from the local cache of the current host. + +https://github.com/restic/restic/issues/4934 +https://github.com/restic/restic/pull/4981 \ No newline at end of file diff --git a/internal/backend/cache/backend.go b/internal/backend/cache/backend.go index 94f648cf4..58b03dd38 100644 --- a/internal/backend/cache/backend.go +++ b/internal/backend/cache/backend.go @@ -2,11 +2,14 @@ package cache import ( "context" + "fmt" "io" + "os" "sync" "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/debug" + "github.com/restic/restic/internal/restic" ) // Backend wraps a restic.Backend and adds a cache. @@ -215,3 +218,44 @@ func (b *Backend) IsNotExist(err error) bool { func (b *Backend) Unwrap() backend.Backend { return b.Backend } + +func (b *Backend) List(ctx context.Context, t backend.FileType, fn func(f backend.FileInfo) error) error { + if !b.Cache.canBeCached(t) { + return b.Backend.List(ctx, t, fn) + } + + // will contain the IDs of the files that are in the repository + ids := restic.NewIDSet() + + // wrap the original function to also add the file to the ids set + wrapFn := func(f backend.FileInfo) error { + id, err := restic.ParseID(f.Name) + if err != nil { + // returning error here since, if we cannot parse the ID, the file + // is invalid and the list must exit. + return err + } + + ids.Insert(id) + + // execute the original function + return fn(f) + } + + err := b.Backend.List(ctx, t, wrapFn) + if err != nil { + return err + } + + if ctx.Err() != nil { + return ctx.Err() + } + + // clear the cache for files that are not in the repo anymore, ignore errors + err = b.Cache.Clear(t, ids) + if err != nil { + fmt.Fprintf(os.Stderr, "error clearing %s files in cache: %v\n", t.String(), err) + } + + return nil +} diff --git a/internal/backend/cache/backend_test.go b/internal/backend/cache/backend_test.go index 7addc275d..dca51c2bf 100644 --- a/internal/backend/cache/backend_test.go +++ b/internal/backend/cache/backend_test.go @@ -57,6 +57,13 @@ func randomData(n int) (backend.Handle, []byte) { return h, data } +func list(t testing.TB, be backend.Backend, fn func(backend.FileInfo) error) { + err := be.List(context.TODO(), backend.IndexFile, fn) + if err != nil { + t.Fatal(err) + } +} + func TestBackend(t *testing.T) { be := mem.New() c := TestNewCache(t) @@ -238,3 +245,54 @@ func TestErrorBackend(t *testing.T) { wg.Wait() } + +func TestAutomaticCacheClear(t *testing.T) { + be := mem.New() + c := TestNewCache(t) + wbe := c.Wrap(be) + + // add two handles h1 and h2 + h1, data := randomData(2000) + // save h1 directly to the backend + save(t, be, h1, data) + if c.Has(h1) { + t.Errorf("cache has file1 too early") + } + + h2, data2 := randomData(3000) + + // save h2 directly to the backend + save(t, be, h2, data2) + if c.Has(h2) { + t.Errorf("cache has file2 too early") + } + + loadAndCompare(t, wbe, h1, data) + if !c.Has(h1) { + t.Errorf("cache doesn't have file1 after load") + } + + loadAndCompare(t, wbe, h2, data2) + if !c.Has(h2) { + t.Errorf("cache doesn't have file2 after load") + } + + // remove h1 directly from the backend + remove(t, be, h1) + if !c.Has(h1) { + t.Errorf("file1 not in cache any more, should be removed from cache only after list") + } + + // list all files in the backend + list(t, wbe, func(_ backend.FileInfo) error { return nil }) + + // h1 should be removed from the cache + if c.Has(h1) { + t.Errorf("cache has file1 after remove") + } + + // h2 should still be in the cache + if !c.Has(h2) { + t.Errorf("cache doesn't have file2 after list") + } +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index f7fd65c71..d408e3105 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -706,19 +706,10 @@ func (r *Repository) prepareCache() error { return nil } - indexIDs := r.idx.IDs() - debug.Log("prepare cache with %d index files", len(indexIDs)) - - // clear old index files - err := r.Cache.Clear(restic.IndexFile, indexIDs) - if err != nil { - fmt.Fprintf(os.Stderr, "error clearing index files in cache: %v\n", err) - } - packs := r.idx.Packs(restic.NewIDSet()) // clear old packs - err = r.Cache.Clear(restic.PackFile, packs) + err := r.Cache.Clear(restic.PackFile, packs) if err != nil { fmt.Fprintf(os.Stderr, "error clearing pack files in cache: %v\n", err) }