2
2
mirror of https://github.com/octoleo/restic.git synced 2024-06-07 19:40:49 +00:00
restic/internal/archiver/blob_saver.go

151 lines
3.3 KiB
Go
Raw Normal View History

2018-03-30 20:43:18 +00:00
package archiver
import (
"context"
"github.com/restic/restic/internal/debug"
2018-03-30 20:43:18 +00:00
"github.com/restic/restic/internal/restic"
2022-05-27 17:08:50 +00:00
"golang.org/x/sync/errgroup"
2018-03-30 20:43:18 +00:00
)
// Saver allows saving a blob.
type Saver interface {
2022-05-01 12:26:57 +00:00
SaveBlob(ctx context.Context, t restic.BlobType, data []byte, id restic.ID, storeDuplicate bool) (restic.ID, bool, int, error)
2018-03-30 20:43:18 +00:00
}
// BlobSaver concurrently saves incoming blobs to the repo.
type BlobSaver struct {
repo Saver
ch chan<- saveBlobJob
2018-03-30 20:43:18 +00:00
}
// NewBlobSaver returns a new blob. A worker pool is started, it is stopped
// when ctx is cancelled.
2022-05-27 17:08:50 +00:00
func NewBlobSaver(ctx context.Context, wg *errgroup.Group, repo Saver, workers uint) *BlobSaver {
2018-04-30 13:13:03 +00:00
ch := make(chan saveBlobJob)
2018-03-30 20:43:18 +00:00
s := &BlobSaver{
repo: repo,
ch: ch,
2018-03-30 20:43:18 +00:00
}
for i := uint(0); i < workers; i++ {
2022-05-27 17:08:50 +00:00
wg.Go(func() error {
return s.worker(ctx, ch)
})
2018-03-30 20:43:18 +00:00
}
return s
}
2022-05-27 17:08:50 +00:00
func (s *BlobSaver) TriggerShutdown() {
close(s.ch)
}
2018-03-30 20:43:18 +00:00
// Save stores a blob in the repo. It checks the index and the known blobs
// before saving anything. It takes ownership of the buffer passed in.
2018-04-29 13:34:41 +00:00
func (s *BlobSaver) Save(ctx context.Context, t restic.BlobType, buf *Buffer) FutureBlob {
Fix data race in blob_saver After the `BlobSaver` job is submitted, the buffer can be released and reused by another `FileSaver` even before `BlobSaver.Save` returns. That FileSaver will the change `buf.Data` leading to wrong backup statistics. Found by `go test -race ./...`: WARNING: DATA RACE Write at 0x00c0000784a0 by goroutine 41: github.com/restic/restic/internal/archiver.(*FileSaver).saveFile() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:176 +0x789 github.com/restic/restic/internal/archiver.(*FileSaver).worker() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:242 +0x2af github.com/restic/restic/internal/archiver.NewFileSaver.func2() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:88 +0x5d golang.org/x/sync/errgroup.(*Group).Go.func1() /home/michael/go/pkg/mod/golang.org/x/sync@v0.0.0-20210220032951-036812b2e83c/errgroup/errgroup.go:57 +0x91 Previous read at 0x00c0000784a0 by goroutine 29: github.com/restic/restic/internal/archiver.(*BlobSaver).Save() /home/michael/Projekte/restic/restic/internal/archiver/blob_saver.go:57 +0x1dd github.com/restic/restic/internal/archiver.(*BlobSaver).Save-fm() <autogenerated>:1 +0xac github.com/restic/restic/internal/archiver.(*FileSaver).saveFile() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:191 +0x855 github.com/restic/restic/internal/archiver.(*FileSaver).worker() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:242 +0x2af github.com/restic/restic/internal/archiver.NewFileSaver.func2() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:88 +0x5d golang.org/x/sync/errgroup.(*Group).Go.func1() /home/michael/go/pkg/mod/golang.org/x/sync@v0.0.0-20210220032951-036812b2e83c/errgroup/errgroup.go:57 +0x91
2022-07-03 12:47:53 +00:00
// buf might be freed once the job was submitted, thus calculate the length now
length := len(buf.Data)
2018-03-30 20:43:18 +00:00
ch := make(chan saveBlobResponse, 1)
select {
case s.ch <- saveBlobJob{BlobType: t, buf: buf, ch: ch}:
case <-ctx.Done():
debug.Log("not sending job, context is cancelled")
close(ch)
return FutureBlob{ch: ch}
}
2018-03-30 20:43:18 +00:00
Fix data race in blob_saver After the `BlobSaver` job is submitted, the buffer can be released and reused by another `FileSaver` even before `BlobSaver.Save` returns. That FileSaver will the change `buf.Data` leading to wrong backup statistics. Found by `go test -race ./...`: WARNING: DATA RACE Write at 0x00c0000784a0 by goroutine 41: github.com/restic/restic/internal/archiver.(*FileSaver).saveFile() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:176 +0x789 github.com/restic/restic/internal/archiver.(*FileSaver).worker() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:242 +0x2af github.com/restic/restic/internal/archiver.NewFileSaver.func2() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:88 +0x5d golang.org/x/sync/errgroup.(*Group).Go.func1() /home/michael/go/pkg/mod/golang.org/x/sync@v0.0.0-20210220032951-036812b2e83c/errgroup/errgroup.go:57 +0x91 Previous read at 0x00c0000784a0 by goroutine 29: github.com/restic/restic/internal/archiver.(*BlobSaver).Save() /home/michael/Projekte/restic/restic/internal/archiver/blob_saver.go:57 +0x1dd github.com/restic/restic/internal/archiver.(*BlobSaver).Save-fm() <autogenerated>:1 +0xac github.com/restic/restic/internal/archiver.(*FileSaver).saveFile() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:191 +0x855 github.com/restic/restic/internal/archiver.(*FileSaver).worker() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:242 +0x2af github.com/restic/restic/internal/archiver.NewFileSaver.func2() /home/michael/Projekte/restic/restic/internal/archiver/file_saver.go:88 +0x5d golang.org/x/sync/errgroup.(*Group).Go.func1() /home/michael/go/pkg/mod/golang.org/x/sync@v0.0.0-20210220032951-036812b2e83c/errgroup/errgroup.go:57 +0x91
2022-07-03 12:47:53 +00:00
return FutureBlob{ch: ch, length: length}
2018-03-30 20:43:18 +00:00
}
// FutureBlob is returned by SaveBlob and will return the data once it has been processed.
type FutureBlob struct {
ch <-chan saveBlobResponse
length int
res saveBlobResponse
}
// Wait blocks until the result is available or the context is cancelled.
func (s *FutureBlob) Wait(ctx context.Context) {
select {
case <-ctx.Done():
return
case res, ok := <-s.ch:
if ok {
s.res = res
}
2018-03-30 20:43:18 +00:00
}
}
// ID returns the ID of the blob after it has been saved.
func (s *FutureBlob) ID() restic.ID {
return s.res.id
}
// Known returns whether or not the blob was already known.
func (s *FutureBlob) Known() bool {
return s.res.known
}
// Length returns the raw length of the blob.
2018-03-30 20:43:18 +00:00
func (s *FutureBlob) Length() int {
return s.length
}
// SizeInRepo returns the number of bytes added to the repo (including
// compression and crypto overhead).
func (s *FutureBlob) SizeInRepo() int {
return s.res.size
}
2018-03-30 20:43:18 +00:00
type saveBlobJob struct {
restic.BlobType
2018-04-29 13:34:41 +00:00
buf *Buffer
2018-03-30 20:43:18 +00:00
ch chan<- saveBlobResponse
}
type saveBlobResponse struct {
id restic.ID
known bool
2022-05-01 12:26:57 +00:00
size int
2018-03-30 20:43:18 +00:00
}
func (s *BlobSaver) saveBlob(ctx context.Context, t restic.BlobType, buf []byte) (saveBlobResponse, error) {
2022-05-01 12:26:57 +00:00
id, known, size, err := s.repo.SaveBlob(ctx, t, buf, restic.ID{}, false)
2018-03-30 20:43:18 +00:00
if err != nil {
return saveBlobResponse{}, err
}
2018-03-30 20:43:18 +00:00
return saveBlobResponse{
id: id,
known: known,
2022-05-01 12:26:57 +00:00
size: size,
}, nil
2018-03-30 20:43:18 +00:00
}
func (s *BlobSaver) worker(ctx context.Context, jobs <-chan saveBlobJob) error {
2018-03-30 20:43:18 +00:00
for {
var job saveBlobJob
2022-05-27 17:08:50 +00:00
var ok bool
2018-03-30 20:43:18 +00:00
select {
case <-ctx.Done():
return nil
2022-05-27 17:08:50 +00:00
case job, ok = <-jobs:
if !ok {
return nil
}
2018-03-30 20:43:18 +00:00
}
res, err := s.saveBlob(ctx, job.BlobType, job.buf.Data)
if err != nil {
debug.Log("saveBlob returned error, exiting: %v", err)
close(job.ch)
return err
}
job.ch <- res
2018-03-30 20:43:18 +00:00
close(job.ch)
job.buf.Release()
}
}