diff --git a/lock.go b/lock.go index 65d102323..d4fb990f5 100644 --- a/lock.go +++ b/lock.go @@ -20,6 +20,9 @@ import ( // There are two types of locks: exclusive and non-exclusive. There may be many // different non-exclusive locks, but at most one exclusive lock, which can // only be acquired while no non-exclusive lock is held. +// +// A lock must be refreshed regularly to not be considered stale, this must be +// triggered by regularly calling Refresh. type Lock struct { Time time.Time `json:"time"` Exclusive bool `json:"exclusive"` @@ -89,7 +92,7 @@ func newLock(repo *repository.Repository, excl bool) (*Lock, error) { return nil, err } - err = lock.createLock() + lock.lockID, err = lock.createLock() if err != nil { return nil, err } @@ -171,14 +174,13 @@ func eachLock(repo *repository.Repository, f func(backend.ID, *Lock, error) erro } // createLock acquires the lock by creating a file in the repository. -func (l *Lock) createLock() error { +func (l *Lock) createLock() (backend.ID, error) { id, err := l.repo.SaveJSONUnpacked(backend.Lock, l) if err != nil { - return err + return nil, err } - l.lockID = id - return nil + return id, nil } // Unlock removes the lock from the repository. @@ -220,6 +222,24 @@ func (l *Lock) Stale() bool { return false } +// Refresh refreshes the lock by creating a new file in the backend with a new +// timestamp. Afterwards the old lock is removed. +func (l *Lock) Refresh() error { + id, err := l.createLock() + if err != nil { + return err + } + + err = l.repo.Backend().Remove(backend.Lock, l.lockID.String()) + if err != nil { + return err + } + + l.lockID = id + + return nil +} + func (l Lock) String() string { text := fmt.Sprintf("PID %d on %s by %s (UID %d, GID %d)\nlock was created at %s (%s ago)\nstorage ID %v", l.PID, l.Hostname, l.Username, l.UID, l.GID, @@ -275,6 +295,7 @@ func RemoveStaleLocks(repo *repository.Repository) error { }) } +// RemoveAllLocks removes all locks forcefully. func RemoveAllLocks(repo *repository.Repository) error { return eachLock(repo, func(id backend.ID, lock *Lock, err error) error { return repo.Backend().Remove(backend.Lock, id.String()) diff --git a/lock_test.go b/lock_test.go index 988c5d440..b556cf9f9 100644 --- a/lock_test.go +++ b/lock_test.go @@ -195,3 +195,33 @@ func TestRemoveAllLocks(t *testing.T) { Assert(t, lockExists(repo, t, id3) == false, "lock still exists after RemoveAllLocks was called") } + +func TestLockRefresh(t *testing.T) { + repo := SetupRepo() + defer TeardownRepo(repo) + + lock, err := restic.NewLock(repo) + OK(t, err) + + var lockID backend.ID + for id := range repo.List(backend.Lock, nil) { + if lockID != nil { + t.Error("more than one lock found") + } + lockID = id + } + + OK(t, lock.Refresh()) + + var lockID2 backend.ID + for id := range repo.List(backend.Lock, nil) { + if lockID2 != nil { + t.Error("more than one lock found") + } + lockID2 = id + } + + Assert(t, !lockID.Equal(lockID2), + "expected a new ID after lock refresh, got the same") + OK(t, lock.Unlock()) +}