mirror of
https://github.com/restic/restic.git
synced 2025-08-11 10:17:44 +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:
@@ -10,12 +10,9 @@ import (
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"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"
|
||||
)
|
||||
|
||||
type memMap map[restic.Handle][]byte
|
||||
@@ -32,19 +29,12 @@ const connectionCount = 2
|
||||
type MemoryBackend struct {
|
||||
data memMap
|
||||
m sync.Mutex
|
||||
sem sema.Semaphore
|
||||
}
|
||||
|
||||
// New returns a new backend that saves all data in a map in memory.
|
||||
func New() *MemoryBackend {
|
||||
sem, err := sema.New(connectionCount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
be := &MemoryBackend{
|
||||
data: make(memMap),
|
||||
sem: sem,
|
||||
}
|
||||
|
||||
debug.Log("created new memory backend")
|
||||
@@ -59,13 +49,6 @@ func (be *MemoryBackend) IsNotExist(err error) bool {
|
||||
|
||||
// Save adds new Data to the backend.
|
||||
func (be *MemoryBackend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
||||
if err := h.Valid(); err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
be.sem.GetToken()
|
||||
defer be.sem.ReleaseToken()
|
||||
|
||||
be.m.Lock()
|
||||
defer be.m.Unlock()
|
||||
|
||||
@@ -113,8 +96,6 @@ func (be *MemoryBackend) Load(ctx context.Context, h restic.Handle, length int,
|
||||
}
|
||||
|
||||
func (be *MemoryBackend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
|
||||
be.sem.GetToken()
|
||||
be.m.Lock()
|
||||
defer be.m.Unlock()
|
||||
|
||||
@@ -123,21 +104,12 @@ func (be *MemoryBackend) openReader(ctx context.Context, h restic.Handle, length
|
||||
h.Name = ""
|
||||
}
|
||||
|
||||
debug.Log("Load %v offset %v len %v", h, offset, length)
|
||||
|
||||
if offset < 0 {
|
||||
be.sem.ReleaseToken()
|
||||
return nil, errors.New("offset is negative")
|
||||
}
|
||||
|
||||
if _, ok := be.data[h]; !ok {
|
||||
be.sem.ReleaseToken()
|
||||
return nil, errNotFound
|
||||
}
|
||||
|
||||
buf := be.data[h]
|
||||
if offset > int64(len(buf)) {
|
||||
be.sem.ReleaseToken()
|
||||
return nil, errors.New("offset beyond end of file")
|
||||
}
|
||||
|
||||
@@ -146,18 +118,11 @@ func (be *MemoryBackend) openReader(ctx context.Context, h restic.Handle, length
|
||||
buf = buf[:length]
|
||||
}
|
||||
|
||||
return be.sem.ReleaseTokenOnClose(io.NopCloser(bytes.NewReader(buf)), nil), ctx.Err()
|
||||
return io.NopCloser(bytes.NewReader(buf)), ctx.Err()
|
||||
}
|
||||
|
||||
// Stat returns information about a file in the backend.
|
||||
func (be *MemoryBackend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
|
||||
if err := h.Valid(); err != nil {
|
||||
return restic.FileInfo{}, backoff.Permanent(err)
|
||||
}
|
||||
|
||||
be.sem.GetToken()
|
||||
defer be.sem.ReleaseToken()
|
||||
|
||||
be.m.Lock()
|
||||
defer be.m.Unlock()
|
||||
|
||||
@@ -176,9 +141,6 @@ func (be *MemoryBackend) Stat(ctx context.Context, h restic.Handle) (restic.File
|
||||
|
||||
// Remove deletes a file from the backend.
|
||||
func (be *MemoryBackend) Remove(ctx context.Context, h restic.Handle) error {
|
||||
be.sem.GetToken()
|
||||
defer be.sem.ReleaseToken()
|
||||
|
||||
be.m.Lock()
|
||||
defer be.m.Unlock()
|
||||
|
||||
|
Reference in New Issue
Block a user