mirror of
https://github.com/restic/restic.git
synced 2025-12-03 22:01:46 +00:00
backend: factor out connection limiting and parameter validation
The SemaphoreBackend now uniformly enforces the limit of concurrent backend operations. In addition, it unifies the parameter validation. The List() methods no longer uses a semaphore. Restic already never runs multiple list operations in parallel. By managing the semaphore in a wrapper backend, the sections that hold a semaphore token grow slightly. However, the main bottleneck is IO, so this shouldn't make much of a difference. The key insight that enables the SemaphoreBackend is that all of the complex semaphore handling in `openReader()` still happens within the original call to `Load()`. Thus, getting and releasing the semaphore tokens can be refactored to happen directly in `Load()`. This eliminates the need for wrapping the reader in `openReader()` to release the token.
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/backend/layout"
|
||||
"github.com/restic/restic/internal/backend/sema"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
@@ -35,7 +34,6 @@ type SFTP struct {
|
||||
|
||||
posixRename bool
|
||||
|
||||
sem sema.Semaphore
|
||||
layout.Layout
|
||||
Config
|
||||
backend.Modes
|
||||
@@ -140,11 +138,7 @@ func Open(ctx context.Context, cfg Config) (*SFTP, error) {
|
||||
}
|
||||
|
||||
func open(ctx context.Context, sftp *SFTP, cfg Config) (*SFTP, error) {
|
||||
sem, err := sema.New(cfg.Connections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
sftp.Layout, err = layout.ParseLayout(ctx, sftp, cfg.Layout, defaultLayout, cfg.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -158,7 +152,6 @@ func open(ctx context.Context, sftp *SFTP, cfg Config) (*SFTP, error) {
|
||||
|
||||
sftp.Config = cfg
|
||||
sftp.p = cfg.Path
|
||||
sftp.sem = sem
|
||||
sftp.Modes = m
|
||||
return sftp, nil
|
||||
}
|
||||
@@ -308,17 +301,10 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.Valid(); err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
filename := r.Filename(h)
|
||||
tmpFilename := filename + "-restic-temp-" + tempSuffix()
|
||||
dirname := r.Dirname(h)
|
||||
|
||||
r.sem.GetToken()
|
||||
defer r.sem.ReleaseToken()
|
||||
|
||||
// create new file
|
||||
f, err := r.c.OpenFile(tmpFilename, os.O_CREATE|os.O_EXCL|os.O_WRONLY)
|
||||
|
||||
@@ -414,52 +400,27 @@ func (r *SFTP) Load(ctx context.Context, h restic.Handle, length int, offset int
|
||||
return backend.DefaultLoad(ctx, h, length, offset, r.openReader, fn)
|
||||
}
|
||||
|
||||
// wrapReader wraps an io.ReadCloser to run an additional function on Close.
|
||||
type wrapReader struct {
|
||||
io.ReadCloser
|
||||
io.WriterTo
|
||||
f func()
|
||||
}
|
||||
|
||||
func (wr *wrapReader) Close() error {
|
||||
err := wr.ReadCloser.Close()
|
||||
wr.f()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *SFTP) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
r.sem.GetToken()
|
||||
f, err := r.c.Open(r.Filename(h))
|
||||
if err != nil {
|
||||
r.sem.ReleaseToken()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
_, err = f.Seek(offset, 0)
|
||||
if err != nil {
|
||||
r.sem.ReleaseToken()
|
||||
_ = f.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// use custom close wrapper to also provide WriteTo() on the wrapper
|
||||
rd := &wrapReader{
|
||||
ReadCloser: f,
|
||||
WriterTo: f,
|
||||
f: func() {
|
||||
r.sem.ReleaseToken()
|
||||
},
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
// unlimited reads usually use io.Copy which needs WriteTo support at the underlying reader
|
||||
// limited reads are usually combined with io.ReadFull which reads all required bytes into a buffer in one go
|
||||
return backend.LimitReadCloser(rd, int64(length)), nil
|
||||
return backend.LimitReadCloser(f, int64(length)), nil
|
||||
}
|
||||
|
||||
return rd, nil
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Stat returns information about a blob.
|
||||
@@ -468,13 +429,6 @@ func (r *SFTP) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, erro
|
||||
return restic.FileInfo{}, err
|
||||
}
|
||||
|
||||
if err := h.Valid(); err != nil {
|
||||
return restic.FileInfo{}, backoff.Permanent(err)
|
||||
}
|
||||
|
||||
r.sem.GetToken()
|
||||
defer r.sem.ReleaseToken()
|
||||
|
||||
fi, err := r.c.Lstat(r.Filename(h))
|
||||
if err != nil {
|
||||
return restic.FileInfo{}, errors.Wrap(err, "Lstat")
|
||||
@@ -489,9 +443,6 @@ func (r *SFTP) Remove(ctx context.Context, h restic.Handle) error {
|
||||
return err
|
||||
}
|
||||
|
||||
r.sem.GetToken()
|
||||
defer r.sem.ReleaseToken()
|
||||
|
||||
return r.c.Remove(r.Filename(h))
|
||||
}
|
||||
|
||||
@@ -501,9 +452,7 @@ func (r *SFTP) List(ctx context.Context, t restic.FileType, fn func(restic.FileI
|
||||
basedir, subdirs := r.Basedir(t)
|
||||
walker := r.c.Walk(basedir)
|
||||
for {
|
||||
r.sem.GetToken()
|
||||
ok := walker.Step()
|
||||
r.sem.ReleaseToken()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user