Add lock conflict check
This commit is contained in:
parent
a217f51f2c
commit
fba912440d
2 changed files with 90 additions and 0 deletions
13
lock.go
13
lock.go
|
@ -52,6 +52,8 @@ func NewExclusiveLock(repo *repository.Repository) (*Lock, error) {
|
||||||
return newLock(repo, true)
|
return newLock(repo, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const waitBeforeLockCheck = 200 * time.Millisecond
|
||||||
|
|
||||||
func newLock(repo *repository.Repository, excl bool) (*Lock, error) {
|
func newLock(repo *repository.Repository, excl bool) (*Lock, error) {
|
||||||
lock := &Lock{
|
lock := &Lock{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
|
@ -78,6 +80,13 @@ func newLock(repo *repository.Repository, excl bool) (*Lock, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitBeforeLockCheck)
|
||||||
|
|
||||||
|
if err = lock.checkForOtherLocks(); err != nil {
|
||||||
|
lock.Unlock()
|
||||||
|
return nil, ErrAlreadyLocked
|
||||||
|
}
|
||||||
|
|
||||||
return lock, nil
|
return lock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +120,10 @@ func (l *Lock) fillUserInfo() error {
|
||||||
// exclusive lock is found.
|
// exclusive lock is found.
|
||||||
func (l *Lock) checkForOtherLocks() error {
|
func (l *Lock) checkForOtherLocks() error {
|
||||||
return eachLock(l.repo, func(id backend.ID, lock *Lock, err error) 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
|
// ignore locks that cannot be loaded
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
77
lock_test.go
77
lock_test.go
|
@ -2,6 +2,7 @@ package restic_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -168,3 +169,79 @@ func TestLockWithStaleLock(t *testing.T) {
|
||||||
|
|
||||||
OK(t, removeLock(repo, id2))
|
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue