Fix error counter - fixes #3650

For few commands, RClone counts a error multiple times. This was fixed by
creating a new error type which keeps a flag to remember if the error has
already been counted or not. The CountError function now wraps the original
error eith the above new error type and returns it.
This commit is contained in:
Ankur Gupta 2019-11-18 19:43:02 +05:30 committed by Nick Craig-Wood
parent 19229b1215
commit 75a6c49f87
18 changed files with 157 additions and 77 deletions

View File

@ -350,7 +350,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
err = errors.Wrapf(err, "failed to open directory %q", dir) err = errors.Wrapf(err, "failed to open directory %q", dir)
fs.Errorf(dir, "%v", err) fs.Errorf(dir, "%v", err)
if isPerm { if isPerm {
accounting.Stats(ctx).Error(fserrors.NoRetryError(err)) _ = accounting.Stats(ctx).Error(fserrors.NoRetryError(err))
err = nil // ignore error but fail sync err = nil // ignore error but fail sync
} }
return nil, err return nil, err
@ -386,7 +386,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
if fierr != nil { if fierr != nil {
err = errors.Wrapf(err, "failed to read directory %q", namepath) err = errors.Wrapf(err, "failed to read directory %q", namepath)
fs.Errorf(dir, "%v", fierr) fs.Errorf(dir, "%v", fierr)
accounting.Stats(ctx).Error(fserrors.NoRetryError(fierr)) // fail the sync _ = accounting.Stats(ctx).Error(fserrors.NoRetryError(fierr)) // fail the sync
continue continue
} }
fis = append(fis, fi) fis = append(fis, fi)
@ -409,7 +409,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
// Skip bad symlinks // Skip bad symlinks
err = fserrors.NoRetryError(errors.Wrap(err, "symlink")) err = fserrors.NoRetryError(errors.Wrap(err, "symlink"))
fs.Errorf(newRemote, "Listing error: %v", err) fs.Errorf(newRemote, "Listing error: %v", err)
accounting.Stats(ctx).Error(err) err = accounting.Stats(ctx).Error(err)
continue continue
} }
if err != nil { if err != nil {

View File

@ -82,7 +82,7 @@ func ShowVersion() {
func NewFsFile(remote string) (fs.Fs, string) { func NewFsFile(remote string) (fs.Fs, string) {
_, _, fsPath, err := fs.ParseRemote(remote) _, _, fsPath, err := fs.ParseRemote(remote)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatalf("Failed to create file system for %q: %v", remote, err) log.Fatalf("Failed to create file system for %q: %v", remote, err)
} }
f, err := cache.Get(remote) f, err := cache.Get(remote)
@ -92,7 +92,7 @@ func NewFsFile(remote string) (fs.Fs, string) {
case nil: case nil:
return f, "" return f, ""
default: default:
fs.CountError(err) err = fs.CountError(err)
log.Fatalf("Failed to create file system for %q: %v", remote, err) log.Fatalf("Failed to create file system for %q: %v", remote, err)
} }
return nil, "" return nil, ""
@ -107,13 +107,13 @@ func newFsFileAddFilter(remote string) (fs.Fs, string) {
if fileName != "" { if fileName != "" {
if !filter.Active.InActive() { if !filter.Active.InActive() {
err := errors.Errorf("Can't limit to single files when using filters: %v", remote) err := errors.Errorf("Can't limit to single files when using filters: %v", remote)
fs.CountError(err) err = fs.CountError(err)
log.Fatalf(err.Error()) log.Fatalf(err.Error())
} }
// Limit transfers to this file // Limit transfers to this file
err := filter.Active.AddFile(fileName) err := filter.Active.AddFile(fileName)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatalf("Failed to limit to single file %q: %v", remote, err) log.Fatalf("Failed to limit to single file %q: %v", remote, err)
} }
} }
@ -135,7 +135,7 @@ func NewFsSrc(args []string) fs.Fs {
func newFsDir(remote string) fs.Fs { func newFsDir(remote string) fs.Fs {
f, err := cache.Get(remote) f, err := cache.Get(remote)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatalf("Failed to create file system for %q: %v", remote, err) log.Fatalf("Failed to create file system for %q: %v", remote, err)
} }
return f return f
@ -189,11 +189,11 @@ func NewFsSrcDstFiles(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs
fdst, err := cache.Get(dstRemote) fdst, err := cache.Get(dstRemote)
switch err { switch err {
case fs.ErrorIsFile: case fs.ErrorIsFile:
fs.CountError(err) _ = fs.CountError(err)
log.Fatalf("Source doesn't exist or is a directory and destination is a file") log.Fatalf("Source doesn't exist or is a directory and destination is a file")
case nil: case nil:
default: default:
fs.CountError(err) _ = fs.CountError(err)
log.Fatalf("Failed to create file system for destination %q: %v", dstRemote, err) log.Fatalf("Failed to create file system for destination %q: %v", dstRemote, err)
} }
return return
@ -239,7 +239,7 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
SigInfoHandler() SigInfoHandler()
for try := 1; try <= *retries; try++ { for try := 1; try <= *retries; try++ {
err = f() err = f()
fs.CountError(err) err = fs.CountError(err)
lastErr := accounting.GlobalStats().GetLastError() lastErr := accounting.GlobalStats().GetLastError()
if err == nil { if err == nil {
err = lastErr err = lastErr
@ -386,12 +386,12 @@ func initConfig() {
fs.Infof(nil, "Creating CPU profile %q\n", *cpuProfile) fs.Infof(nil, "Creating CPU profile %q\n", *cpuProfile)
f, err := os.Create(*cpuProfile) f, err := os.Create(*cpuProfile)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatal(err) log.Fatal(err)
} }
err = pprof.StartCPUProfile(f) err = pprof.StartCPUProfile(f)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatal(err) log.Fatal(err)
} }
atexit.Register(func() { atexit.Register(func() {
@ -405,17 +405,17 @@ func initConfig() {
fs.Infof(nil, "Saving Memory profile %q\n", *memProfile) fs.Infof(nil, "Saving Memory profile %q\n", *memProfile)
f, err := os.Create(*memProfile) f, err := os.Create(*memProfile)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatal(err) log.Fatal(err)
} }
err = pprof.WriteHeapProfile(f) err = pprof.WriteHeapProfile(f)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatal(err) log.Fatal(err)
} }
err = f.Close() err = f.Close()
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
log.Fatal(err) log.Fatal(err)
} }
}) })

View File

@ -88,7 +88,7 @@ func cryptCheck(ctx context.Context, fdst, fsrc fs.Fs) error {
underlyingDst := cryptDst.UnWrap() underlyingDst := cryptDst.UnWrap()
underlyingHash, err := underlyingDst.Hash(ctx, hashType) underlyingHash, err := underlyingDst.Hash(ctx, hashType)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dst, "Error reading hash from underlying %v: %v", underlyingDst, err) fs.Errorf(dst, "Error reading hash from underlying %v: %v", underlyingDst, err)
return true, false return true, false
} }
@ -97,7 +97,7 @@ func cryptCheck(ctx context.Context, fdst, fsrc fs.Fs) error {
} }
cryptHash, err := fcrypt.ComputeHash(ctx, cryptDst, src, hashType) cryptHash, err := fcrypt.ComputeHash(ctx, cryptDst, src, hashType)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dst, "Error computing hash: %v", err) fs.Errorf(dst, "Error computing hash: %v", err)
return true, false return true, false
} }
@ -106,7 +106,7 @@ func cryptCheck(ctx context.Context, fdst, fsrc fs.Fs) error {
} }
if cryptHash != underlyingHash { if cryptHash != underlyingHash {
err = errors.Errorf("hashes differ (%s:%s) %q vs (%s:%s) %q", fdst.Name(), fdst.Root(), cryptHash, fsrc.Name(), fsrc.Root(), underlyingHash) err = errors.Errorf("hashes differ (%s:%s) %q vs (%s:%s) %q", fdst.Name(), fdst.Root(), cryptHash, fsrc.Name(), fsrc.Root(), underlyingHash)
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(src, err.Error()) fs.Errorf(src, err.Error())
return true, false return true, false
} }

View File

@ -214,7 +214,7 @@ func withHeader(name string, value string, next http.Handler) http.Handler {
// serveError returns an http.StatusInternalServerError and logs the error // serveError returns an http.StatusInternalServerError and logs the error
func serveError(what interface{}, w http.ResponseWriter, text string, err error) { func serveError(what interface{}, w http.ResponseWriter, text string, err error) {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(what, "%s: %v", text, err) fs.Errorf(what, "%s: %v", text, err)
http.Error(w, text+".", http.StatusInternalServerError) http.Error(w, text+".", http.StatusInternalServerError)
} }

View File

@ -68,7 +68,7 @@ func (d *Directory) AddEntry(remote string, isDir bool) {
// Error logs the error and if a ResponseWriter is given it writes a http.StatusInternalServerError // Error logs the error and if a ResponseWriter is given it writes a http.StatusInternalServerError
func Error(what interface{}, w http.ResponseWriter, text string, err error) { func Error(what interface{}, w http.ResponseWriter, text string, err error) {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(what, "%s: %v", text, err) fs.Errorf(what, "%s: %v", text, err)
if w != nil { if w != nil {
http.Error(w, text+".", http.StatusInternalServerError) http.Error(w, text+".", http.StatusInternalServerError)

View File

@ -271,7 +271,7 @@ func (s *server) postObject(w http.ResponseWriter, r *http.Request, remote strin
_, err := operations.RcatSize(r.Context(), s.f, remote, r.Body, r.ContentLength, time.Now()) _, err := operations.RcatSize(r.Context(), s.f, remote, r.Body, r.ContentLength, time.Now())
if err != nil { if err != nil {
accounting.Stats(r.Context()).Error(err) err = accounting.Stats(r.Context()).Error(err)
fs.Errorf(remote, "Post request rcat error: %v", err) fs.Errorf(remote, "Post request rcat error: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)

View File

@ -475,14 +475,16 @@ func (s *StatsInfo) Errored() bool {
} }
// Error adds a single error into the stats, assigns lastError and eventually sets fatalError or retryError // Error adds a single error into the stats, assigns lastError and eventually sets fatalError or retryError
func (s *StatsInfo) Error(err error) { func (s *StatsInfo) Error(err error) error {
if err == nil { if err == nil || fserrors.IsCounted(err) {
return return err
} }
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
s.errors++ s.errors++
s.lastError = err s.lastError = err
err = fserrors.FsError(err)
fserrors.Count(err)
switch { switch {
case fserrors.IsFatalError(err): case fserrors.IsFatalError(err):
s.fatalError = true s.fatalError = true
@ -495,6 +497,7 @@ func (s *StatsInfo) Error(err error) {
case !fserrors.IsNoRetryError(err): case !fserrors.IsNoRetryError(err):
s.retryError = true s.retryError = true
} }
return err
} }
// RetryAfter returns the time to retry after if it is set. It will // RetryAfter returns the time to retry after if it is set. It will

View File

@ -78,7 +78,7 @@ func TestStatsError(t *testing.T) {
t0 := time.Now() t0 := time.Now()
t1 := t0.Add(time.Second) t1 := t0.Add(time.Second)
s.Error(nil) _ = s.Error(nil)
assert.Equal(t, int64(0), s.GetErrors()) assert.Equal(t, int64(0), s.GetErrors())
assert.False(t, s.HadFatalError()) assert.False(t, s.HadFatalError())
assert.False(t, s.HadRetryError()) assert.False(t, s.HadRetryError())
@ -86,7 +86,7 @@ func TestStatsError(t *testing.T) {
assert.Equal(t, nil, s.GetLastError()) assert.Equal(t, nil, s.GetLastError())
assert.False(t, s.Errored()) assert.False(t, s.Errored())
s.Error(io.EOF) _ = s.Error(io.EOF)
assert.Equal(t, int64(1), s.GetErrors()) assert.Equal(t, int64(1), s.GetErrors())
assert.False(t, s.HadFatalError()) assert.False(t, s.HadFatalError())
assert.True(t, s.HadRetryError()) assert.True(t, s.HadRetryError())
@ -95,7 +95,7 @@ func TestStatsError(t *testing.T) {
assert.True(t, s.Errored()) assert.True(t, s.Errored())
e := fserrors.ErrorRetryAfter(t0) e := fserrors.ErrorRetryAfter(t0)
s.Error(e) _ = s.Error(e)
assert.Equal(t, int64(2), s.GetErrors()) assert.Equal(t, int64(2), s.GetErrors())
assert.False(t, s.HadFatalError()) assert.False(t, s.HadFatalError())
assert.True(t, s.HadRetryError()) assert.True(t, s.HadRetryError())
@ -103,14 +103,14 @@ func TestStatsError(t *testing.T) {
assert.Equal(t, e, s.GetLastError()) assert.Equal(t, e, s.GetLastError())
err := errors.Wrap(fserrors.ErrorRetryAfter(t1), "potato") err := errors.Wrap(fserrors.ErrorRetryAfter(t1), "potato")
s.Error(err) err = s.Error(err)
assert.Equal(t, int64(3), s.GetErrors()) assert.Equal(t, int64(3), s.GetErrors())
assert.False(t, s.HadFatalError()) assert.False(t, s.HadFatalError())
assert.True(t, s.HadRetryError()) assert.True(t, s.HadRetryError())
assert.Equal(t, t1, s.RetryAfter()) assert.Equal(t, t1, s.RetryAfter())
assert.Equal(t, t1, fserrors.RetryAfterErrorTime(err)) assert.Equal(t, t1, fserrors.RetryAfterErrorTime(err))
s.Error(fserrors.FatalError(io.EOF)) _ = s.Error(fserrors.FatalError(io.EOF))
assert.Equal(t, int64(4), s.GetErrors()) assert.Equal(t, int64(4), s.GetErrors())
assert.True(t, s.HadFatalError()) assert.True(t, s.HadFatalError())
assert.True(t, s.HadRetryError()) assert.True(t, s.HadRetryError())
@ -124,7 +124,7 @@ func TestStatsError(t *testing.T) {
assert.Equal(t, nil, s.GetLastError()) assert.Equal(t, nil, s.GetLastError())
assert.False(t, s.Errored()) assert.False(t, s.Errored())
s.Error(fserrors.NoRetryError(io.EOF)) _ = s.Error(fserrors.NoRetryError(io.EOF))
assert.Equal(t, int64(1), s.GetErrors()) assert.Equal(t, int64(1), s.GetErrors())
assert.False(t, s.HadFatalError()) assert.False(t, s.HadFatalError())
assert.False(t, s.HadRetryError()) assert.False(t, s.HadRetryError())

View File

@ -86,7 +86,7 @@ func newTransferRemoteSize(stats *StatsInfo, remote string, size int64, checking
// Must be called after transfer is finished to run proper cleanups. // Must be called after transfer is finished to run proper cleanups.
func (tr *Transfer) Done(err error) { func (tr *Transfer) Done(err error) {
if err != nil { if err != nil {
tr.stats.Error(err) err = tr.stats.Error(err)
tr.mu.Lock() tr.mu.Lock()
tr.err = err tr.err = err

View File

@ -32,7 +32,7 @@ var (
// //
// This is a function pointer to decouple the config // This is a function pointer to decouple the config
// implementation from the fs // implementation from the fs
CountError = func(err error) {} CountError = func(err error) error { return nil }
// ConfigProvider is the config key used for provider options // ConfigProvider is the config key used for provider options
ConfigProvider = "provider" ConfigProvider = "provider"

View File

@ -230,6 +230,64 @@ func IsRetryAfterError(err error) bool {
return !RetryAfterErrorTime(err).IsZero() return !RetryAfterErrorTime(err).IsZero()
} }
// CountableError is an optional interface for error. It stores a boolean
// which signifies if the error has already been counted or not
type CountableError interface {
error
Count()
IsCounted() bool
}
// wrappedFatalError is an error wrapped so it will satisfy the
// Retrier interface and return true
type wrappedCountableError struct {
error
isCounted bool
}
// CountableError interface
func (err *wrappedCountableError) Count() {
err.isCounted = true
}
// CountableError interface
func (err *wrappedCountableError) IsCounted() bool {
return err.isCounted
}
func (err *wrappedCountableError) Cause() error {
return err.error
}
// IsCounted returns true if err conforms to the CountableError interface
// and has already been counted
func IsCounted(err error) bool {
if r, ok := err.(CountableError); ok {
return r.IsCounted()
}
return false
}
// Count sets the isCounted variable on the error if it conforms to the
// CountableError interface
func Count(err error) {
if r, ok := err.(CountableError); ok {
r.Count()
}
}
// Check interface
var _ CountableError = &wrappedCountableError{error: error(nil)}
// FsError makes an error which can keep a record that it is already counted
// or not
func FsError(err error) error {
if err == nil {
err = errors.New("countable error")
}
return &wrappedCountableError{error: err}
}
// Cause is a souped up errors.Cause which can unwrap some standard // Cause is a souped up errors.Cause which can unwrap some standard
// library errors too. It returns true if any of the intermediate // library errors too. It returns true if any of the intermediate
// errors had a Timeout() or Temporary() method which returned true. // errors had a Timeout() or Temporary() method which returned true.

View File

@ -393,14 +393,14 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
wg.Wait() wg.Wait()
if srcListErr != nil { if srcListErr != nil {
fs.Errorf(job.srcRemote, "error reading source directory: %v", srcListErr) fs.Errorf(job.srcRemote, "error reading source directory: %v", srcListErr)
fs.CountError(srcListErr) srcListErr = fs.CountError(srcListErr)
return nil, srcListErr return nil, srcListErr
} }
if dstListErr == fs.ErrorDirNotFound { if dstListErr == fs.ErrorDirNotFound {
// Copy the stuff anyway // Copy the stuff anyway
} else if dstListErr != nil { } else if dstListErr != nil {
fs.Errorf(job.dstRemote, "error reading destination directory: %v", dstListErr) fs.Errorf(job.dstRemote, "error reading destination directory: %v", dstListErr)
fs.CountError(dstListErr) dstListErr = fs.CountError(dstListErr)
return nil, dstListErr return nil, dstListErr
} }

View File

@ -34,7 +34,7 @@ outer:
_, err := f.NewObject(ctx, newName) _, err := f.NewObject(ctx, newName)
for ; err != fs.ErrorObjectNotFound; suffix++ { for ; err != fs.ErrorObjectNotFound; suffix++ {
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(o, "Failed to check for existing object: %v", err) fs.Errorf(o, "Failed to check for existing object: %v", err)
continue outer continue outer
} }
@ -48,7 +48,7 @@ outer:
if !fs.Config.DryRun { if !fs.Config.DryRun {
newObj, err := doMove(ctx, o, newName) newObj, err := doMove(ctx, o, newName)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(o, "Failed to rename: %v", err) fs.Errorf(o, "Failed to rename: %v", err)
continue continue
} }

View File

@ -63,7 +63,7 @@ func checkHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object, ht hash.
g.Go(func() (err error) { g.Go(func() (err error) {
srcHash, err = src.Hash(ctx, ht) srcHash, err = src.Hash(ctx, ht)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(src, "Failed to calculate src hash: %v", err) fs.Errorf(src, "Failed to calculate src hash: %v", err)
} }
return err return err
@ -71,7 +71,7 @@ func checkHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object, ht hash.
g.Go(func() (err error) { g.Go(func() (err error) {
dstHash, err = dst.Hash(ctx, ht) dstHash, err = dst.Hash(ctx, ht)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dst, "Failed to calculate dst hash: %v", err) fs.Errorf(dst, "Failed to calculate dst hash: %v", err)
} }
return err return err
@ -234,7 +234,7 @@ func equal(ctx context.Context, src fs.ObjectInfo, dst fs.Object, opt equalOpt)
} }
return false return false
} else if err != nil { } else if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dst, "Failed to set modification time: %v", err) fs.Errorf(dst, "Failed to set modification time: %v", err)
} else { } else {
fs.Infof(src, "Updated modification time in destination") fs.Infof(src, "Updated modification time in destination")
@ -408,7 +408,7 @@ func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Obj
break break
} }
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(src, "Failed to copy: %v", err) fs.Errorf(src, "Failed to copy: %v", err)
return newDst, err return newDst, err
} }
@ -417,7 +417,7 @@ func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Obj
if sizeDiffers(src, dst) { if sizeDiffers(src, dst) {
err = errors.Errorf("corrupted on transfer: sizes differ %d vs %d", src.Size(), dst.Size()) err = errors.Errorf("corrupted on transfer: sizes differ %d vs %d", src.Size(), dst.Size())
fs.Errorf(dst, "%v", err) fs.Errorf(dst, "%v", err)
fs.CountError(err) err = fs.CountError(err)
removeFailedCopy(ctx, dst) removeFailedCopy(ctx, dst)
return newDst, err return newDst, err
} }
@ -429,7 +429,7 @@ func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Obj
if !equal { if !equal {
err = errors.Errorf("corrupted on transfer: %v hash differ %q vs %q", hashType, srcSum, dstSum) err = errors.Errorf("corrupted on transfer: %v hash differ %q vs %q", hashType, srcSum, dstSum)
fs.Errorf(dst, "%v", err) fs.Errorf(dst, "%v", err)
fs.CountError(err) err = fs.CountError(err)
removeFailedCopy(ctx, dst) removeFailedCopy(ctx, dst)
return newDst, err return newDst, err
} }
@ -492,7 +492,7 @@ func Move(ctx context.Context, fdst fs.Fs, dst fs.Object, remote string, src fs.
case fs.ErrorCantMove: case fs.ErrorCantMove:
fs.Debugf(src, "Can't move, switching to copy") fs.Debugf(src, "Can't move, switching to copy")
default: default:
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(src, "Couldn't move: %v", err) fs.Errorf(src, "Couldn't move: %v", err)
return newDst, err return newDst, err
} }
@ -558,8 +558,8 @@ func DeleteFileWithBackupDir(ctx context.Context, dst fs.Object, backupDir fs.Fs
err = dst.Remove(ctx) err = dst.Remove(ctx)
} }
if err != nil { if err != nil {
fs.CountError(err)
fs.Errorf(dst, "Couldn't %s: %v", action, err) fs.Errorf(dst, "Couldn't %s: %v", action, err)
err = fs.CountError(err)
} else if !fs.Config.DryRun { } else if !fs.Config.DryRun {
fs.Infof(dst, actioned) fs.Infof(dst, actioned)
} }
@ -685,7 +685,7 @@ func checkIdentical(ctx context.Context, dst, src fs.Object) (differ bool, noHas
if !same { if !same {
err = errors.Errorf("%v differ", ht) err = errors.Errorf("%v differ", ht)
fs.Errorf(src, "%v", err) fs.Errorf(src, "%v", err)
fs.CountError(err) _ = fs.CountError(err)
return true, false return true, false
} }
return false, false return false, false
@ -716,7 +716,7 @@ func (c *checkMarch) DstOnly(dst fs.DirEntry) (recurse bool) {
} }
err := errors.Errorf("File not in %v", c.fsrc) err := errors.Errorf("File not in %v", c.fsrc)
fs.Errorf(dst, "%v", err) fs.Errorf(dst, "%v", err)
fs.CountError(err) _ = fs.CountError(err)
atomic.AddInt32(&c.differences, 1) atomic.AddInt32(&c.differences, 1)
atomic.AddInt32(&c.srcFilesMissing, 1) atomic.AddInt32(&c.srcFilesMissing, 1)
case fs.Directory: case fs.Directory:
@ -734,7 +734,7 @@ func (c *checkMarch) SrcOnly(src fs.DirEntry) (recurse bool) {
case fs.Object: case fs.Object:
err := errors.Errorf("File not in %v", c.fdst) err := errors.Errorf("File not in %v", c.fdst)
fs.Errorf(src, "%v", err) fs.Errorf(src, "%v", err)
fs.CountError(err) _ = fs.CountError(err)
atomic.AddInt32(&c.differences, 1) atomic.AddInt32(&c.differences, 1)
atomic.AddInt32(&c.dstFilesMissing, 1) atomic.AddInt32(&c.dstFilesMissing, 1)
case fs.Directory: case fs.Directory:
@ -756,7 +756,6 @@ func (c *checkMarch) checkIdentical(ctx context.Context, dst, src fs.Object) (di
if sizeDiffers(src, dst) { if sizeDiffers(src, dst) {
err = errors.Errorf("Sizes differ") err = errors.Errorf("Sizes differ")
fs.Errorf(src, "%v", err) fs.Errorf(src, "%v", err)
fs.CountError(err)
return true, false return true, false
} }
if fs.Config.SizeOnly { if fs.Config.SizeOnly {
@ -784,7 +783,7 @@ func (c *checkMarch) Match(ctx context.Context, dst, src fs.DirEntry) (recurse b
} else { } else {
err := errors.Errorf("is file on %v but directory on %v", c.fsrc, c.fdst) err := errors.Errorf("is file on %v but directory on %v", c.fsrc, c.fdst)
fs.Errorf(src, "%v", err) fs.Errorf(src, "%v", err)
fs.CountError(err) _ = fs.CountError(err)
atomic.AddInt32(&c.differences, 1) atomic.AddInt32(&c.differences, 1)
atomic.AddInt32(&c.dstFilesMissing, 1) atomic.AddInt32(&c.dstFilesMissing, 1)
} }
@ -796,7 +795,7 @@ func (c *checkMarch) Match(ctx context.Context, dst, src fs.DirEntry) (recurse b
} }
err := errors.Errorf("is file on %v but directory on %v", c.fdst, c.fsrc) err := errors.Errorf("is file on %v but directory on %v", c.fdst, c.fsrc)
fs.Errorf(dst, "%v", err) fs.Errorf(dst, "%v", err)
fs.CountError(err) _ = fs.CountError(err)
atomic.AddInt32(&c.differences, 1) atomic.AddInt32(&c.differences, 1)
atomic.AddInt32(&c.srcFilesMissing, 1) atomic.AddInt32(&c.srcFilesMissing, 1)
@ -923,7 +922,7 @@ func CheckDownload(ctx context.Context, fdst, fsrc fs.Fs, oneway bool) error {
check := func(ctx context.Context, a, b fs.Object) (differ bool, noHash bool) { check := func(ctx context.Context, a, b fs.Object) (differ bool, noHash bool) {
differ, err := CheckIdentical(ctx, a, b) differ, err := CheckIdentical(ctx, a, b)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(a, "Failed to download: %v", err) fs.Errorf(a, "Failed to download: %v", err)
return true, true return true, true
} }
@ -1070,7 +1069,7 @@ func Mkdir(ctx context.Context, f fs.Fs, dir string) error {
fs.Debugf(fs.LogDirName(f, dir), "Making directory") fs.Debugf(fs.LogDirName(f, dir), "Making directory")
err := f.Mkdir(ctx, dir) err := f.Mkdir(ctx, dir)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
return err return err
} }
return nil return nil
@ -1091,7 +1090,7 @@ func TryRmdir(ctx context.Context, f fs.Fs, dir string) error {
func Rmdir(ctx context.Context, f fs.Fs, dir string) error { func Rmdir(ctx context.Context, f fs.Fs, dir string) error {
err := TryRmdir(ctx, f, dir) err := TryRmdir(ctx, f, dir)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
return err return err
} }
return err return err
@ -1124,7 +1123,7 @@ func Purge(ctx context.Context, f fs.Fs, dir string) error {
err = Rmdirs(ctx, f, dir, false) err = Rmdirs(ctx, f, dir, false)
} }
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
return err return err
} }
return nil return nil
@ -1167,7 +1166,7 @@ func listToChan(ctx context.Context, f fs.Fs, dir string) fs.ObjectsChan {
}) })
if err != nil && err != fs.ErrorDirNotFound { if err != nil && err != fs.ErrorDirNotFound {
err = errors.Wrap(err, "failed to list") err = errors.Wrap(err, "failed to list")
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(nil, "%v", err) fs.Errorf(nil, "%v", err)
} }
}() }()
@ -1223,7 +1222,7 @@ func Cat(ctx context.Context, f fs.Fs, w io.Writer, offset, count int64) error {
} }
in, err := o.Open(ctx, options...) in, err := o.Open(ctx, options...)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(o, "Failed to open: %v", err) fs.Errorf(o, "Failed to open: %v", err)
return return
} }
@ -1236,7 +1235,7 @@ func Cat(ctx context.Context, f fs.Fs, w io.Writer, offset, count int64) error {
defer mu.Unlock() defer mu.Unlock()
_, err = io.Copy(w, in) _, err = io.Copy(w, in)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(o, "Failed to send to output: %v", err) fs.Errorf(o, "Failed to send to output: %v", err)
} }
}) })
@ -1263,7 +1262,7 @@ func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser,
src := object.NewStaticObjectInfo(dstFileName, modTime, int64(readCounter.BytesRead()), false, hash.Sums(), fdst) src := object.NewStaticObjectInfo(dstFileName, modTime, int64(readCounter.BytesRead()), false, hash.Sums(), fdst)
if !Equal(ctx, src, dst) { if !Equal(ctx, src, dst) {
err = errors.Errorf("corrupted on transfer") err = errors.Errorf("corrupted on transfer")
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dst, "%v", err) fs.Errorf(dst, "%v", err)
return err return err
} }
@ -1338,7 +1337,7 @@ func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error {
dirEmpty[dir] = !leaveRoot dirEmpty[dir] = !leaveRoot
err := walk.Walk(ctx, f, dir, true, fs.Config.MaxDepth, func(dirPath string, entries fs.DirEntries, err error) error { err := walk.Walk(ctx, f, dir, true, fs.Config.MaxDepth, func(dirPath string, entries fs.DirEntries, err error) error {
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(f, "Failed to list %q: %v", dirPath, err) fs.Errorf(f, "Failed to list %q: %v", dirPath, err)
return nil return nil
} }
@ -1385,7 +1384,7 @@ func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error {
dir := toDelete[i] dir := toDelete[i]
err := TryRmdir(ctx, f, dir) err := TryRmdir(ctx, f, dir)
if err != nil { if err != nil {
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(dir, "Failed to rmdir: %v", err) fs.Errorf(dir, "Failed to rmdir: %v", err)
return err return err
} }

View File

@ -926,7 +926,7 @@ func MoveDir(ctx context.Context, fdst, fsrc fs.Fs, deleteEmptySrcDirs bool, cop
fs.Infof(fdst, "Server side directory move succeeded") fs.Infof(fdst, "Server side directory move succeeded")
return nil return nil
default: default:
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(fdst, "Server side directory move failed: %v", err) fs.Errorf(fdst, "Server side directory move failed: %v", err)
return err return err
} }

View File

@ -490,7 +490,7 @@ func TestSyncIgnoreErrors(t *testing.T) {
) )
accounting.GlobalStats().ResetCounters() accounting.GlobalStats().ResetCounters()
fs.CountError(errors.New("boom")) _ = fs.CountError(errors.New("boom"))
assert.NoError(t, Sync(context.Background(), r.Fremote, r.Flocal, false)) assert.NoError(t, Sync(context.Background(), r.Fremote, r.Flocal, false))
fstest.CheckListingWithPrecision( fstest.CheckListingWithPrecision(
@ -800,7 +800,7 @@ func TestSyncAfterRemovingAFileAndAddingAFileSubDirWithErrors(t *testing.T) {
) )
accounting.GlobalStats().ResetCounters() accounting.GlobalStats().ResetCounters()
fs.CountError(errors.New("boom")) _ = fs.CountError(errors.New("boom"))
err := Sync(context.Background(), r.Fremote, r.Flocal, false) err := Sync(context.Background(), r.Fremote, r.Flocal, false)
assert.Equal(t, fs.ErrorNotDeleting, err) assert.Equal(t, fs.ErrorNotDeleting, err)
@ -1763,5 +1763,7 @@ func TestAbort(t *testing.T) {
accounting.GlobalStats().ResetCounters() accounting.GlobalStats().ResetCounters()
err := Sync(context.Background(), r.Fremote, r.Flocal, false) err := Sync(context.Background(), r.Fremote, r.Flocal, false)
assert.Equal(t, accounting.ErrorMaxTransferLimitReached, err) expectedErr := fserrors.FsError(accounting.ErrorMaxTransferLimitReached)
fserrors.Count(expectedErr)
assert.Equal(t, expectedErr, err)
} }

View File

@ -159,7 +159,7 @@ func listRwalk(ctx context.Context, f fs.Fs, path string, includeAll bool, maxLe
// Carry on listing but return the error at the end // Carry on listing but return the error at the end
if err != nil { if err != nil {
listErr = err listErr = err
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(path, "error listing: %v", err) fs.Errorf(path, "error listing: %v", err)
return nil return nil
} }
@ -404,7 +404,7 @@ func walk(ctx context.Context, f fs.Fs, path string, includeAll bool, maxLevel i
// NB once we have passed entries to fn we mustn't touch it again // NB once we have passed entries to fn we mustn't touch it again
if err != nil && err != ErrorSkipDir { if err != nil && err != ErrorSkipDir {
traversing.Done() traversing.Done()
fs.CountError(err) err = fs.CountError(err)
fs.Errorf(job.remote, "error listing: %v", err) fs.Errorf(job.remote, "error listing: %v", err)
closeQuit() closeQuit()
// Send error to error channel if space // Send error to error channel if space

View File

@ -10,7 +10,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
_ "github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/filter" "github.com/rclone/rclone/fs/filter"
"github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fstest/mockdir" "github.com/rclone/rclone/fstest/mockdir"
"github.com/rclone/rclone/fstest/mockfs" "github.com/rclone/rclone/fstest/mockfs"
"github.com/rclone/rclone/fstest/mockobject" "github.com/rclone/rclone/fstest/mockobject"
@ -18,6 +20,15 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var errDirNotFound, errorBoom error
func init() {
errDirNotFound = fserrors.FsError(fs.ErrorDirNotFound)
fserrors.Count(errDirNotFound)
errorBoom = fserrors.FsError(errors.New("boom"))
fserrors.Count(errorBoom)
}
type ( type (
listResult struct { listResult struct {
entries fs.DirEntries entries fs.DirEntries
@ -196,12 +207,12 @@ func TestWalkREmptySkip(t *testing.T) { testWalkEmptySkip(t).WalkR() }
func testWalkNotFound(t *testing.T) *listDirs { func testWalkNotFound(t *testing.T) *listDirs {
return newListDirs(t, nil, true, return newListDirs(t, nil, true,
listResults{ listResults{
"": {err: fs.ErrorDirNotFound}, "": {err: errDirNotFound},
}, },
errorMap{ errorMap{
"": fs.ErrorDirNotFound, "": errDirNotFound,
}, },
fs.ErrorDirNotFound, errDirNotFound,
) )
} }
func TestWalkNotFound(t *testing.T) { testWalkNotFound(t).Walk() } func TestWalkNotFound(t *testing.T) { testWalkNotFound(t).Walk() }
@ -211,7 +222,7 @@ func TestWalkNotFoundMaskError(t *testing.T) {
// this doesn't work for WalkR // this doesn't work for WalkR
newListDirs(t, nil, true, newListDirs(t, nil, true,
listResults{ listResults{
"": {err: fs.ErrorDirNotFound}, "": {err: errDirNotFound},
}, },
errorMap{ errorMap{
"": nil, "": nil,
@ -224,7 +235,7 @@ func TestWalkNotFoundSkipError(t *testing.T) {
// this doesn't work for WalkR // this doesn't work for WalkR
newListDirs(t, nil, true, newListDirs(t, nil, true,
listResults{ listResults{
"": {err: fs.ErrorDirNotFound}, "": {err: errDirNotFound},
}, },
errorMap{ errorMap{
"": ErrorSkipDir, "": ErrorSkipDir,
@ -342,7 +353,7 @@ func testWalkSkip(t *testing.T) *listDirs {
func TestWalkSkip(t *testing.T) { testWalkSkip(t).Walk() } func TestWalkSkip(t *testing.T) { testWalkSkip(t).Walk() }
func TestWalkRSkip(t *testing.T) { testWalkSkip(t).WalkR() } func TestWalkRSkip(t *testing.T) { testWalkSkip(t).WalkR() }
func testWalkErrors(t *testing.T) *listDirs { func walkErrors(t *testing.T, expectedErr error) *listDirs {
lr := listResults{} lr := listResults{}
em := errorMap{} em := errorMap{}
de := make(fs.DirEntries, 10) de := make(fs.DirEntries, 10)
@ -357,13 +368,20 @@ func testWalkErrors(t *testing.T) *listDirs {
return newListDirs(t, nil, true, return newListDirs(t, nil, true,
lr, lr,
em, em,
fs.ErrorDirNotFound, expectedErr,
).NoCheckMaps() ).NoCheckMaps()
} }
func TestWalkErrors(t *testing.T) { testWalkErrors(t).Walk() }
func TestWalkRErrors(t *testing.T) { testWalkErrors(t).WalkR() }
var errorBoom = errors.New("boom") func testWalkErrors(t *testing.T) *listDirs {
return walkErrors(t, errDirNotFound)
}
func testWalkRErrors(t *testing.T) *listDirs {
return walkErrors(t, fs.ErrorDirNotFound)
}
func TestWalkErrors(t *testing.T) { testWalkErrors(t).Walk() }
func TestWalkRErrors(t *testing.T) { testWalkRErrors(t).WalkR() }
func makeTree(level int, terminalErrors bool) (listResults, errorMap) { func makeTree(level int, terminalErrors bool) (listResults, errorMap) {
lr := listResults{} lr := listResults{}