diff --git a/cmd/restic/lock.go b/cmd/restic/lock.go index 389241644..a5bdc3981 100644 --- a/cmd/restic/lock.go +++ b/cmd/restic/lock.go @@ -4,14 +4,21 @@ import ( "fmt" "os" "os/signal" + "sync" "syscall" + "time" "github.com/restic/restic" "github.com/restic/restic/debug" "github.com/restic/restic/repository" ) -var globalLocks []*restic.Lock +var globalLocks struct { + locks []*restic.Lock + cancelRefresh chan struct{} + refreshWG sync.WaitGroup + sync.Mutex +} func lockRepo(repo *repository.Repository) (*restic.Lock, error) { return lockRepository(repo, false) @@ -29,35 +36,69 @@ func lockRepository(repo *repository.Repository, exclusive bool) (*restic.Lock, lock, err := lockFn(repo) if err != nil { - if restic.IsAlreadyLocked(err) { - tpe := "" - if exclusive { - tpe = " exclusive" - } - fmt.Fprintf(os.Stderr, "unable to acquire%s lock for operation:\n", tpe) - fmt.Fprintln(os.Stderr, err) - fmt.Fprintf(os.Stderr, "\nthe `unlock` command can be used to remove stale locks\n") - os.Exit(1) - } - return nil, err } - globalLocks = append(globalLocks, lock) + globalLocks.Lock() + if globalLocks.cancelRefresh == nil { + debug.Log("main.lockRepository", "start goroutine for lock refresh") + globalLocks.cancelRefresh = make(chan struct{}) + globalLocks.refreshWG = sync.WaitGroup{} + globalLocks.refreshWG.Add(1) + go refreshLocks(&globalLocks.refreshWG, globalLocks.cancelRefresh) + } + + globalLocks.locks = append(globalLocks.locks, lock) + globalLocks.Unlock() return lock, err } +var refreshInterval = 5 * time.Minute + +func refreshLocks(wg *sync.WaitGroup, done <-chan struct{}) { + debug.Log("main.refreshLocks", "start") + defer func() { + wg.Done() + globalLocks.Lock() + globalLocks.cancelRefresh = nil + globalLocks.Unlock() + }() + + ticker := time.NewTicker(refreshInterval) + + for { + select { + case <-done: + debug.Log("main.refreshLocks", "terminate") + return + case <-ticker.C: + debug.Log("main.refreshLocks", "refreshing locks") + globalLocks.Lock() + for _, lock := range globalLocks.locks { + err := lock.Refresh() + if err != nil { + fmt.Fprintf(os.Stderr, "unable to refresh lock: %v\n", err) + } + } + globalLocks.Unlock() + } + } +} + func unlockRepo(lock *restic.Lock) error { + globalLocks.Lock() + defer globalLocks.Unlock() + debug.Log("unlockRepo", "unlocking repository") if err := lock.Unlock(); err != nil { debug.Log("unlockRepo", "error while unlocking: %v", err) return err } - for i := 0; i < len(globalLocks); i++ { - if lock == globalLocks[i] { - globalLocks = append(globalLocks[:i], globalLocks[i+1:]...) + for i := 0; i < len(globalLocks.locks); i++ { + if lock == globalLocks.locks[i] { + globalLocks.locks = append(globalLocks.locks[:i], globalLocks.locks[i+1:]...) return nil } } @@ -66,8 +107,11 @@ func unlockRepo(lock *restic.Lock) error { } func unlockAll() error { - debug.Log("unlockAll", "unlocking %d locks", len(globalLocks)) - for _, lock := range globalLocks { + globalLocks.Lock() + defer globalLocks.Unlock() + + debug.Log("unlockAll", "unlocking %d locks", len(globalLocks.locks)) + for _, lock := range globalLocks.locks { if err := lock.Unlock(); err != nil { debug.Log("unlockAll", "error while unlocking: %v", err) return err diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 8669ae8d6..3acab925e 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -1,10 +1,12 @@ package main import ( + "fmt" "os" "runtime" "github.com/jessevdk/go-flags" + "github.com/restic/restic" "github.com/restic/restic/debug" ) @@ -26,6 +28,10 @@ func main() { os.Exit(0) } + if restic.IsAlreadyLocked(err) { + fmt.Fprintf(os.Stderr, "\nthe `unlock` command can be used to remove stale locks\n") + } + if err != nil { os.Exit(1) } diff --git a/lock.go b/lock.go index d4fb990f5..2a30a98b4 100644 --- a/lock.go +++ b/lock.go @@ -225,6 +225,7 @@ func (l *Lock) Stale() bool { // 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 { + debug.Log("Lock.Refresh", "refreshing lock %v", l.lockID.Str()) id, err := l.createLock() if err != nil { return err @@ -235,6 +236,7 @@ func (l *Lock) Refresh() error { return err } + debug.Log("Lock.Refresh", "new lock ID %v", id.Str()) l.lockID = id return nil