mirror of
https://github.com/restic/restic.git
synced 2026-10-12 04:57:12 +00:00
restore: cache file descriptors
This avoid opening and closing files after each single blob write
This commit is contained in:
1 parent
0ee9360f3e
commit
24fcfeafcb
3 files changed
+53
-4
No files matched your search
@@ -219,6 +219,8 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error {
|
|||||||
wg, ctx := errgroup.WithContext(ctx)
|
wg, ctx := errgroup.WithContext(ctx)
|
||||||
downloadCh := make(chan *packInfo)
|
downloadCh := make(chan *packInfo)
|
||||||
|
|
||||||
|
// close all files when finished
|
||||||
|
defer r.filesWriter.flush()
|
||||||
worker := func() error {
|
worker := func() error {
|
||||||
for pack := range downloadCh {
|
for pack := range downloadCh {
|
||||||
if err := r.downloadPack(ctx, pack); err != nil {
|
if err := r.downloadPack(ctx, pack); err != nil {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
@@ -20,6 +21,8 @@ import (
|
|||||||
type filesWriter struct {
|
type filesWriter struct {
|
||||||
buckets []filesWriterBucket
|
buckets []filesWriterBucket
|
||||||
allowRecursiveDelete bool
|
allowRecursiveDelete bool
|
||||||
|
cacheMu sync.Mutex
|
||||||
|
cache *simplelru.LRU[string, *partialFile]
|
||||||
}
|
}
|
||||||
|
|
||||||
type filesWriterBucket struct {
|
type filesWriterBucket struct {
|
||||||
@@ -38,9 +41,21 @@ func newFilesWriter(count int, allowRecursiveDelete bool) *filesWriter {
|
|||||||
for b := 0; b < count; b++ {
|
for b := 0; b < count; b++ {
|
||||||
buckets[b].files = make(map[string]*partialFile)
|
buckets[b].files = make(map[string]*partialFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache, err := simplelru.NewLRU[string, *partialFile](50, func(_ string, wr *partialFile) {
|
||||||
|
// close the file only when it is not in use
|
||||||
|
if wr.users == 0 {
|
||||||
|
_ = wr.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // can't happen
|
||||||
|
}
|
||||||
|
|
||||||
return &filesWriter{
|
return &filesWriter{
|
||||||
buckets: buckets,
|
buckets: buckets,
|
||||||
allowRecursiveDelete: allowRecursiveDelete,
|
allowRecursiveDelete: allowRecursiveDelete,
|
||||||
|
cache: cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +188,24 @@ func (w *filesWriter) writeToFile(path string, blob []byte, offset int64, create
|
|||||||
bucket.files[path].users++
|
bucket.files[path].users++
|
||||||
return wr, nil
|
return wr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the global LRU cache for a cached file handle
|
||||||
|
w.cacheMu.Lock()
|
||||||
|
cached, ok := w.cache.Get(path)
|
||||||
|
if ok {
|
||||||
|
// mark as in use to prevent closing on remove call below
|
||||||
|
cached.users++
|
||||||
|
|
||||||
|
w.cache.Remove(path)
|
||||||
|
w.cacheMu.Unlock()
|
||||||
|
|
||||||
|
// Use the cached file handle
|
||||||
|
bucket.files[path] = cached
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
|
w.cacheMu.Unlock()
|
||||||
|
|
||||||
|
// Not in cache, open/create the file
|
||||||
var f *os.File
|
var f *os.File
|
||||||
var err error
|
var err error
|
||||||
if createSize >= 0 {
|
if createSize >= 0 {
|
||||||
@@ -194,11 +227,14 @@ func (w *filesWriter) writeToFile(path string, blob []byte, offset int64, create
|
|||||||
bucket.lock.Lock()
|
bucket.lock.Lock()
|
||||||
defer bucket.lock.Unlock()
|
defer bucket.lock.Unlock()
|
||||||
|
|
||||||
if bucket.files[path].users == 1 {
|
|
||||||
delete(bucket.files, path)
|
|
||||||
return wr.Close()
|
|
||||||
}
|
|
||||||
bucket.files[path].users--
|
bucket.files[path].users--
|
||||||
|
if bucket.files[path].users == 0 {
|
||||||
|
delete(bucket.files, path)
|
||||||
|
// Add to cache to allow re-use. Cache will close files on overflow.
|
||||||
|
w.cacheMu.Lock()
|
||||||
|
w.cache.Add(path, wr)
|
||||||
|
w.cacheMu.Unlock()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,3 +253,10 @@ func (w *filesWriter) writeToFile(path string, blob []byte, offset int64, create
|
|||||||
|
|
||||||
return releaseWriter(wr)
|
return releaseWriter(wr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *filesWriter) flush() {
|
||||||
|
w.cacheMu.Lock()
|
||||||
|
defer w.cacheMu.Unlock()
|
||||||
|
|
||||||
|
w.cache.Purge()
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ func TestFilesWriterBasic(t *testing.T) {
|
|||||||
rtest.OK(t, w.writeToFile(f2, []byte{2}, 1, -1, false))
|
rtest.OK(t, w.writeToFile(f2, []byte{2}, 1, -1, false))
|
||||||
rtest.Equals(t, 0, len(w.buckets[0].files))
|
rtest.Equals(t, 0, len(w.buckets[0].files))
|
||||||
|
|
||||||
|
w.flush()
|
||||||
|
|
||||||
buf, err := os.ReadFile(f1)
|
buf, err := os.ReadFile(f1)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.Equals(t, []byte{1, 1}, buf)
|
rtest.Equals(t, []byte{1, 1}, buf)
|
||||||
@@ -51,11 +53,13 @@ func TestFilesWriterRecursiveOverwrite(t *testing.T) {
|
|||||||
err := w.writeToFile(path, []byte{1}, 0, 2, false)
|
err := w.writeToFile(path, []byte{1}, 0, 2, false)
|
||||||
rtest.Assert(t, errors.Is(err, notEmptyDirError()), "unexpected error got %v", err)
|
rtest.Assert(t, errors.Is(err, notEmptyDirError()), "unexpected error got %v", err)
|
||||||
rtest.Equals(t, 0, len(w.buckets[0].files))
|
rtest.Equals(t, 0, len(w.buckets[0].files))
|
||||||
|
w.flush()
|
||||||
|
|
||||||
// must replace directory
|
// must replace directory
|
||||||
w = newFilesWriter(1, true)
|
w = newFilesWriter(1, true)
|
||||||
rtest.OK(t, w.writeToFile(path, []byte{1, 1}, 0, 2, false))
|
rtest.OK(t, w.writeToFile(path, []byte{1, 1}, 0, 2, false))
|
||||||
rtest.Equals(t, 0, len(w.buckets[0].files))
|
rtest.Equals(t, 0, len(w.buckets[0].files))
|
||||||
|
w.flush()
|
||||||
|
|
||||||
buf, err := os.ReadFile(path)
|
buf, err := os.ReadFile(path)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|||||||
Reference in new issue
Block a user