diff --git a/lock.go b/lock.go index 07869a28b..bf0725b02 100644 --- a/lock.go +++ b/lock.go @@ -52,6 +52,8 @@ func NewExclusiveLock(repo *repository.Repository) (*Lock, error) { return newLock(repo, true) } +const waitBeforeLockCheck = 200 * time.Millisecond + func newLock(repo *repository.Repository, excl bool) (*Lock, error) { lock := &Lock{ Time: time.Now(), @@ -78,6 +80,13 @@ func newLock(repo *repository.Repository, excl bool) (*Lock, error) { return nil, err } + time.Sleep(waitBeforeLockCheck) + + if err = lock.checkForOtherLocks(); err != nil { + lock.Unlock() + return nil, ErrAlreadyLocked + } + return lock, nil } @@ -111,6 +120,10 @@ func (l *Lock) fillUserInfo() error { // exclusive lock is found. func (l *Lock) checkForOtherLocks() error { return eachLock(l.repo, func(id backend.ID, lock *Lock, err error) error { + if id.Equal(l.lockID) { + return nil + } + // ignore locks that cannot be loaded if err != nil { return nil diff --git a/lock_test.go b/lock_test.go index a3a89c9e1..15e48623a 100644 --- a/lock_test.go +++ b/lock_test.go @@ -2,6 +2,7 @@ package restic_test import ( "os" + "sync" "testing" "time" @@ -168,3 +169,79 @@ func TestLockWithStaleLock(t *testing.T) { OK(t, removeLock(repo, id2)) } + +func TestLockConflictingExclusiveLocks(t *testing.T) { + repo := SetupRepo() + defer TeardownRepo(repo) + + for _, jobs := range []int{5, 23, 200} { + var wg sync.WaitGroup + errch := make(chan error, jobs) + + f := func() { + defer wg.Done() + + lock, err := restic.NewExclusiveLock(repo) + errch <- err + OK(t, lock.Unlock()) + } + + for i := 0; i < jobs; i++ { + wg.Add(1) + go f() + } + + errors := 0 + for i := 0; i < jobs; i++ { + err := <-errch + if err != nil { + errors++ + } + } + + wg.Wait() + + Assert(t, errors == jobs-1, + "Expected %d errors, got %d", jobs-1, errors) + } +} + +func TestLockConflictingLocks(t *testing.T) { + repo := SetupRepo() + defer TeardownRepo(repo) + + var wg sync.WaitGroup + + errch := make(chan error, 2) + + wg.Add(2) + + go func() { + defer wg.Done() + + lock, err := restic.NewExclusiveLock(repo) + errch <- err + OK(t, lock.Unlock()) + }() + + go func() { + defer wg.Done() + + lock, err := restic.NewLock(repo) + errch <- err + OK(t, lock.Unlock()) + }() + + errors := 0 + for i := 0; i < 2; i++ { + err := <-errch + if err != nil { + errors++ + } + } + + wg.Wait() + + Assert(t, errors == 1, + "Expected exactly one errors, got %d", errors) +}