cmd/restic/globals.go already provides Printf, Println and Warnf wrapper which get their output streams from the globalOptions object. This allows for stream replacements when testing.
131 lines
2.9 KiB
Go
131 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/restic/restic/internal/debug"
|
|
"github.com/restic/restic/internal/errors"
|
|
"github.com/restic/restic/internal/repository"
|
|
"github.com/restic/restic/internal/restic"
|
|
)
|
|
|
|
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)
|
|
}
|
|
|
|
func lockRepoExclusive(repo *repository.Repository) (*restic.Lock, error) {
|
|
return lockRepository(repo, true)
|
|
}
|
|
|
|
func lockRepository(repo *repository.Repository, exclusive bool) (*restic.Lock, error) {
|
|
lockFn := restic.NewLock
|
|
if exclusive {
|
|
lockFn = restic.NewExclusiveLock
|
|
}
|
|
|
|
lock, err := lockFn(context.TODO(), repo)
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "unable to create lock in backend")
|
|
}
|
|
debug.Log("create lock %p (exclusive %v)", lock, exclusive)
|
|
|
|
globalLocks.Lock()
|
|
if globalLocks.cancelRefresh == nil {
|
|
debug.Log("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("start")
|
|
defer func() {
|
|
wg.Done()
|
|
globalLocks.Lock()
|
|
globalLocks.cancelRefresh = nil
|
|
globalLocks.Unlock()
|
|
}()
|
|
|
|
ticker := time.NewTicker(refreshInterval)
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
debug.Log("terminate")
|
|
return
|
|
case <-ticker.C:
|
|
debug.Log("refreshing locks")
|
|
globalLocks.Lock()
|
|
for _, lock := range globalLocks.locks {
|
|
err := lock.Refresh(context.TODO())
|
|
if err != nil {
|
|
Warnf("unable to refresh lock: %v\n", err)
|
|
}
|
|
}
|
|
globalLocks.Unlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
func unlockRepo(lock *restic.Lock) error {
|
|
globalLocks.Lock()
|
|
defer globalLocks.Unlock()
|
|
|
|
for i := 0; i < len(globalLocks.locks); i++ {
|
|
if lock == globalLocks.locks[i] {
|
|
// remove the lock from the repo
|
|
debug.Log("unlocking repository with lock %v", lock)
|
|
if err := lock.Unlock(); err != nil {
|
|
debug.Log("error while unlocking: %v", err)
|
|
return err
|
|
}
|
|
|
|
// remove the lock from the list of locks
|
|
globalLocks.locks = append(globalLocks.locks[:i], globalLocks.locks[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
debug.Log("unable to find lock %v in the global list of locks, ignoring", lock)
|
|
|
|
return nil
|
|
}
|
|
|
|
func unlockAll() error {
|
|
globalLocks.Lock()
|
|
defer globalLocks.Unlock()
|
|
|
|
debug.Log("unlocking %d locks", len(globalLocks.locks))
|
|
for _, lock := range globalLocks.locks {
|
|
if err := lock.Unlock(); err != nil {
|
|
debug.Log("error while unlocking: %v", err)
|
|
return err
|
|
}
|
|
debug.Log("successfully removed lock")
|
|
}
|
|
globalLocks.locks = globalLocks.locks[:0]
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
AddCleanupHandler(unlockAll)
|
|
}
|