errcount: factor errcount abstraction from operations

This commit is contained in:
Nick Craig-Wood 2024-02-06 10:14:11 +00:00
parent 8f0e9f9f6b
commit 71a1bbb2be
3 changed files with 89 additions and 11 deletions

View file

@ -35,6 +35,7 @@ import (
"github.com/rclone/rclone/fs/object" "github.com/rclone/rclone/fs/object"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/atexit" "github.com/rclone/rclone/lib/atexit"
"github.com/rclone/rclone/lib/errcount"
"github.com/rclone/rclone/lib/random" "github.com/rclone/rclone/lib/random"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -1353,9 +1354,7 @@ func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error {
} }
var ( var (
errMu sync.Mutex errCount = errcount.New()
errCount int
lastError error
) )
// Delete all directories at the same level in parallel // Delete all directories at the same level in parallel
for level := len(toDelete) - 1; level >= 0; level-- { for level := len(toDelete) - 1; level >= 0; level-- {
@ -1378,10 +1377,7 @@ func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error {
if err != nil { if err != nil {
err = fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dir, "Failed to rmdir: %v", err) fs.Errorf(dir, "Failed to rmdir: %v", err)
errMu.Lock() errCount.Add(err)
lastError = err
errCount += 1
errMu.Unlock()
} }
return nil // don't return errors, just count them return nil // don't return errors, just count them
}) })
@ -1391,10 +1387,7 @@ func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error {
return err return err
} }
} }
if lastError != nil { return errCount.Err("failed to remove directories")
return fmt.Errorf("failed to remove %d directories: last error: %w", errCount, lastError)
}
return nil
} }
// GetCompareDest sets up --compare-dest // GetCompareDest sets up --compare-dest

58
lib/errcount/errcount.go Normal file
View file

@ -0,0 +1,58 @@
// Package errcount provides an easy to use error counter which
// returns error count and last error so as to not overwhelm the user
// with errors.
package errcount
import (
"fmt"
"sync"
)
// ErrCount stores the state of the error counter.
type ErrCount struct {
mu sync.Mutex
lastErr error
count int
}
// New makes a new error counter
func New() *ErrCount {
return new(ErrCount)
}
// Add an error to the error count.
//
// err may be nil.
//
// Thread safe.
func (ec *ErrCount) Add(err error) {
if err == nil {
return
}
ec.mu.Lock()
ec.count++
ec.lastErr = err
ec.mu.Unlock()
}
// Err returns the error summary so far - may be nil
//
// txt is put in front of the error summary
//
// txt: %d errors: last error: %w
//
// or this if only one error
//
// txt: %w
//
// Thread safe.
func (ec *ErrCount) Err(txt string) error {
ec.mu.Lock()
defer ec.mu.Unlock()
if ec.count == 0 {
return nil
} else if ec.count == 1 {
return fmt.Errorf("%s: %w", txt, ec.lastErr)
}
return fmt.Errorf("%s: %d errors: last error: %w", txt, ec.count, ec.lastErr)
}

View file

@ -0,0 +1,27 @@
package errcount
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestErrCount(t *testing.T) {
ec := New()
assert.Equal(t, nil, ec.Err("none"))
e1 := errors.New("one")
ec.Add(e1)
err := ec.Err("stuff")
assert.True(t, errors.Is(err, e1), err)
assert.Equal(t, "stuff: one", err.Error())
e2 := errors.New("two")
ec.Add(e2)
err = ec.Err("stuff")
assert.True(t, errors.Is(err, e2), err)
assert.Equal(t, "stuff: 2 errors: last error: two", err.Error())
}