forked from TrueCloudLab/restic
7d59df1ab8
Closes #1637
133 lines
3 KiB
Go
133 lines
3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"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.Fatalf("unable to create lock in backend: %v", err)
|
|
}
|
|
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 {
|
|
fmt.Fprintf(os.Stderr, "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)
|
|
}
|