fs: fix race conditions in --max-delete and --max-delete-size

This commit is contained in:
Nick Craig-Wood 2023-03-08 18:40:37 +00:00
parent a35ee30d9f
commit e2984227bb
4 changed files with 38 additions and 22 deletions

View File

@ -3,6 +3,7 @@ package accounting
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
@ -591,20 +592,38 @@ func (s *StatsInfo) HadRetryError() bool {
return s.retryError return s.retryError
} }
// Deletes updates the stats for deletes var (
func (s *StatsInfo) Deletes(deletes int64) int64 { errMaxDelete = fserrors.FatalError(errors.New("--max-delete threshold reached"))
errMaxDeleteSize = fserrors.FatalError(errors.New("--max-delete-size threshold reached"))
)
// DeleteFile updates the stats for deleting a file
//
// It may return fatal errors if the threshold for --max-delete or
// --max-delete-size have been reached.
func (s *StatsInfo) DeleteFile(ctx context.Context, size int64) error {
ci := fs.GetConfig(ctx)
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
s.deletes += deletes if size < 0 {
return s.deletes size = 0
}
if ci.MaxDelete >= 0 && s.deletes+1 > ci.MaxDelete {
return errMaxDelete
}
if ci.MaxDeleteSize >= 0 && s.deletesSize+size > int64(ci.MaxDeleteSize) {
return errMaxDeleteSize
}
s.deletes++
s.deletesSize += size
return nil
} }
// DeletesSize updates the stats for deletes size // GetDeletes returns the number of deletes
func (s *StatsInfo) DeletesSize(deletesSize int64) int64 { func (s *StatsInfo) GetDeletes() int64 {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
s.deletesSize += deletesSize return s.deletes
return s.deletesSize
} }
// DeletedDirs updates the stats for deletedDirs // DeletedDirs updates the stats for deletedDirs

View File

@ -121,8 +121,10 @@ func TestStatsGroupOperations(t *testing.T) {
}) })
testGroupStatsInfo := NewStatsGroup(ctx, "test-group") testGroupStatsInfo := NewStatsGroup(ctx, "test-group")
testGroupStatsInfo.Deletes(1) require.NoError(t, testGroupStatsInfo.DeleteFile(ctx, 0))
GlobalStats().Deletes(41) for i := 0; i < 41; i++ {
require.NoError(t, GlobalStats().DeleteFile(ctx, 0))
}
t.Run("core/group-list", func(t *testing.T) { t.Run("core/group-list", func(t *testing.T) {
call := rc.Calls.Get("core/group-list") call := rc.Calls.Get("core/group-list")

View File

@ -632,20 +632,13 @@ func SuffixName(ctx context.Context, remote string) string {
// If backupDir is set then it moves the file to there instead of // If backupDir is set then it moves the file to there instead of
// deleting // deleting
func DeleteFileWithBackupDir(ctx context.Context, dst fs.Object, backupDir fs.Fs) (err error) { func DeleteFileWithBackupDir(ctx context.Context, dst fs.Object, backupDir fs.Fs) (err error) {
ci := fs.GetConfig(ctx)
tr := accounting.Stats(ctx).NewCheckingTransfer(dst, "deleting") tr := accounting.Stats(ctx).NewCheckingTransfer(dst, "deleting")
defer func() { defer func() {
tr.Done(ctx, err) tr.Done(ctx, err)
}() }()
deletesSize := accounting.Stats(ctx).DeletesSize(0) // file not yet deleted, we should not add at this time err = accounting.Stats(ctx).DeleteFile(ctx, dst.Size())
size := dst.Size() if err != nil {
if int64(ci.MaxDeleteSize) != -1 && (deletesSize+size) > int64(ci.MaxDeleteSize) { return err
return fserrors.FatalError(errors.New("--max-delete-size threshold reached"))
}
_ = accounting.Stats(ctx).DeletesSize(size) // here we count
numDeletes := accounting.Stats(ctx).Deletes(1)
if ci.MaxDelete != -1 && numDeletes > ci.MaxDelete {
return fserrors.FatalError(errors.New("--max-delete threshold reached"))
} }
action, actioned := "delete", "Deleted" action, actioned := "delete", "Deleted"
if backupDir != nil { if backupDir != nil {

View File

@ -587,7 +587,9 @@ func TestMetrics(t *testing.T) {
// Test changing a couple options // Test changing a couple options
stats.Bytes(500) stats.Bytes(500)
stats.Deletes(30) for i := 0; i < 30; i++ {
require.NoError(t, stats.DeleteFile(context.Background(), 0))
}
stats.Errors(2) stats.Errors(2)
stats.Bytes(324) stats.Bytes(324)
@ -619,7 +621,7 @@ func makeMetricsTestCases(stats *accounting.StatsInfo) (tests []testRun) {
URL: "/metrics", URL: "/metrics",
Method: "GET", Method: "GET",
Status: http.StatusOK, Status: http.StatusOK,
Contains: regexp.MustCompile(fmt.Sprintf("rclone_files_deleted_total %d", stats.Deletes(0))), Contains: regexp.MustCompile(fmt.Sprintf("rclone_files_deleted_total %d", stats.GetDeletes())),
}, { }, {
Name: "Files Transferred Metric", Name: "Files Transferred Metric",
URL: "/metrics", URL: "/metrics",