mirror of
https://github.com/restic/restic.git
synced 2025-12-03 21:51:47 +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:
@@ -12,12 +12,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
)
|
||||
|
||||
// make sure the rest backend implements restic.Backend
|
||||
@@ -27,7 +24,6 @@ var _ restic.Backend = &Backend{}
|
||||
type Backend struct {
|
||||
url *url.URL
|
||||
connections uint
|
||||
sem sema.Semaphore
|
||||
client http.Client
|
||||
layout.Layout
|
||||
}
|
||||
@@ -40,11 +36,6 @@ const (
|
||||
|
||||
// Open opens the REST backend with the given config.
|
||||
func Open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
sem, err := sema.New(cfg.Connections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use url without trailing slash for layout
|
||||
url := cfg.URL.String()
|
||||
if url[len(url)-1] == '/' {
|
||||
@@ -56,7 +47,6 @@ func Open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
client: http.Client{Transport: rt},
|
||||
Layout: &layout.RESTLayout{URL: url, Join: path.Join},
|
||||
connections: cfg.Connections,
|
||||
sem: sem,
|
||||
}
|
||||
|
||||
return be, nil
|
||||
@@ -123,10 +113,6 @@ func (b *Backend) HasAtomicReplace() bool {
|
||||
|
||||
// Save stores data in the backend at the handle.
|
||||
func (b *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
||||
if err := h.Valid(); err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -143,9 +129,7 @@ func (b *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindRea
|
||||
// let's the server know what's coming.
|
||||
req.ContentLength = rd.Length()
|
||||
|
||||
b.sem.GetToken()
|
||||
resp, err := b.client.Do(req)
|
||||
b.sem.ReleaseToken()
|
||||
|
||||
var cerr error
|
||||
if resp != nil {
|
||||
@@ -212,18 +196,6 @@ func (b *Backend) Load(ctx context.Context, h restic.Handle, length int, offset
|
||||
}
|
||||
|
||||
func (b *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, backoff.Permanent(err)
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
return nil, errors.New("offset is negative")
|
||||
}
|
||||
|
||||
if length < 0 {
|
||||
return nil, errors.Errorf("invalid length %d", length)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", b.Filename(h), nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
@@ -237,9 +209,7 @@ func (b *Backend) openReader(ctx context.Context, h restic.Handle, length int, o
|
||||
req.Header.Set("Accept", ContentTypeV2)
|
||||
debug.Log("Load(%v) send range %v", h, byteRange)
|
||||
|
||||
b.sem.GetToken()
|
||||
resp, err := b.client.Do(req)
|
||||
b.sem.ReleaseToken()
|
||||
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
@@ -264,19 +234,13 @@ func (b *Backend) openReader(ctx context.Context, h restic.Handle, length int, o
|
||||
|
||||
// Stat returns information about a blob.
|
||||
func (b *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
|
||||
if err := h.Valid(); err != nil {
|
||||
return restic.FileInfo{}, backoff.Permanent(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, b.Filename(h), nil)
|
||||
if err != nil {
|
||||
return restic.FileInfo{}, errors.WithStack(err)
|
||||
}
|
||||
req.Header.Set("Accept", ContentTypeV2)
|
||||
|
||||
b.sem.GetToken()
|
||||
resp, err := b.client.Do(req)
|
||||
b.sem.ReleaseToken()
|
||||
if err != nil {
|
||||
return restic.FileInfo{}, errors.WithStack(err)
|
||||
}
|
||||
@@ -309,19 +273,13 @@ func (b *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, e
|
||||
|
||||
// Remove removes the blob with the given name and type.
|
||||
func (b *Backend) Remove(ctx context.Context, h restic.Handle) error {
|
||||
if err := h.Valid(); err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "DELETE", b.Filename(h), nil)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
req.Header.Set("Accept", ContentTypeV2)
|
||||
|
||||
b.sem.GetToken()
|
||||
resp, err := b.client.Do(req)
|
||||
b.sem.ReleaseToken()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "client.Do")
|
||||
@@ -358,9 +316,7 @@ func (b *Backend) List(ctx context.Context, t restic.FileType, fn func(restic.Fi
|
||||
}
|
||||
req.Header.Set("Accept", ContentTypeV2)
|
||||
|
||||
b.sem.GetToken()
|
||||
resp, err := b.client.Do(req)
|
||||
b.sem.ReleaseToken()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "List")
|
||||
|
||||
Reference in New Issue
Block a user