forked from TrueCloudLab/rclone
fs: fix race conditions in --max-delete and --max-delete-size
This commit is contained in:
parent
a35ee30d9f
commit
e2984227bb
4 changed files with 38 additions and 22 deletions
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Add table
Reference in a new issue