lock: checkForOtherLocks processes each lock at most once

If a lock could not be loaded, then restic would check all lock files
again. These repeated checks are not useful as the status of a lock file
cannot change unless its ID changes too. Thus, skip already check lock
files on retries.
This commit is contained in:
Michael Eischer 2024-01-20 22:28:10 +01:00
parent 6696195f38
commit f1f34eb3e5

View file

@ -163,9 +163,16 @@ func (l *Lock) fillUserInfo() error {
// exclusive lock is found. // exclusive lock is found.
func (l *Lock) checkForOtherLocks(ctx context.Context) error { func (l *Lock) checkForOtherLocks(ctx context.Context) error {
var err error var err error
checkedIDs := NewIDSet()
if l.lockID != nil {
checkedIDs.Insert(*l.lockID)
}
// retry locking a few times // retry locking a few times
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
err = ForAllLocks(ctx, l.repo, l.lockID, func(id ID, lock *Lock, err error) error { // Store updates in new IDSet to prevent data races
var m sync.Mutex
newCheckedIDs := NewIDSet(checkedIDs.List()...)
err = ForAllLocks(ctx, l.repo, checkedIDs, func(id ID, lock *Lock, err error) error {
if err != nil { if err != nil {
// if we cannot load a lock then it is unclear whether it can be ignored // if we cannot load a lock then it is unclear whether it can be ignored
// it could either be invalid or just unreadable due to network/permission problems // it could either be invalid or just unreadable due to network/permission problems
@ -181,8 +188,13 @@ func (l *Lock) checkForOtherLocks(ctx context.Context) error {
return &alreadyLockedError{otherLock: lock} return &alreadyLockedError{otherLock: lock}
} }
// valid locks will remain valid
m.Lock()
newCheckedIDs.Insert(id)
m.Unlock()
return nil return nil
}) })
checkedIDs = newCheckedIDs
// no lock detected // no lock detected
if err == nil { if err == nil {
return nil return nil
@ -417,12 +429,12 @@ func RemoveAllLocks(ctx context.Context, repo Repository) (uint, error) {
// It is guaranteed that the function is not run concurrently. If the // It is guaranteed that the function is not run concurrently. If the
// callback returns an error, this function is cancelled and also returns that error. // callback returns an error, this function is cancelled and also returns that error.
// If a lock ID is passed via excludeID, it will be ignored. // If a lock ID is passed via excludeID, it will be ignored.
func ForAllLocks(ctx context.Context, repo Repository, excludeID *ID, fn func(ID, *Lock, error) error) error { func ForAllLocks(ctx context.Context, repo Repository, excludeIDs IDSet, fn func(ID, *Lock, error) error) error {
var m sync.Mutex var m sync.Mutex
// For locks decoding is nearly for free, thus just assume were only limited by IO // For locks decoding is nearly for free, thus just assume were only limited by IO
return ParallelList(ctx, repo, LockFile, repo.Connections(), func(ctx context.Context, id ID, size int64) error { return ParallelList(ctx, repo, LockFile, repo.Connections(), func(ctx context.Context, id ID, size int64) error {
if excludeID != nil && id.Equal(*excludeID) { if excludeIDs.Has(id) {
return nil return nil
} }
if size == 0 { if size == 0 {